React components

Learn the basics of creating DOM elements using JSX and React components

React makes dealing with the DOM in JavaScript more like writing HTML. It helps package up elements into "components" so you can divide your UI up into reusable pieces.

React elements

Interacting with the DOM can be a frustrating experience. It requires lots of awkward lines of code where you tell the browser exactly how to create an element with the right properties.

const title = document.createElement("h1");
title.className = "title";
title.textContent = "Hello world!";

Even if we create our own function to handle some of the repetitive parts it's a little hard to read:

const title = createEl("h1", { className: "title" }, "Hello world!");

This is frustrating because there is a simpler, more declarative way to create elements: HTML.

<h1 class="title">Hello world!</h1>

Unfortunately we can't use HTML inside JavaScript files. HTML can't create elements dynamically as a user interacts with our app. This is where React comes in:

const title = <h1 className="title">Hello world!</h1>;

This variable is a React element. It's created using a special syntax called JSX that lets us write HTML-like elements within our JavaScript.

The example above will be transformed into this normal JS:

const title = React.createElement("h1", { className: "title" }, "Hello world!");

This function call returns an object that describes your element:

// over-simplified for examples sake
const title = {
  type: "h1",
  props: {
    className: "title",
    children: "Hello world!",
  },
};

React builds up one big tree structure of all these element objects that represents your entire app. It then uses this tree to create the actual DOM elements for you. (This is called the virtual DOM, but you don't need to worry about that right now)

It can be helpful to remember that the HTML-like syntax is really normal function calls that return objects.

Templating dynamic values

JSX supports inserting dynamic values into your elements. It uses a similar syntax to JS template literals: anything inside curly brackets will be evaluated as a JS expression, and the result will be rendered. For example:

const title = <h1>Hello {5 * 5}</h1>;
// <h1>Hello 25</h1>

You can do all kinds of JS stuff inside the curly brackets, like referencing other variables, or conditional expressions.

const name = "oli";
const title = <h1>Hello {name}</h1>;
// <h1>Hello oli</h1>
const number = Math.random();
const result = <div>{number > 0.5 ? "You won!" : "You lost"}</div>;
// 50% of the time: <div>You won!</div>
// the other 50%: <div>You lost</div>

Note on expressions

You can put any valid JS expression inside the curly brackets. An expression is code that resolves to a value. I.e. you can assign it to a variable. These are all valid expressions:

const number = 5 + 4 * 9;
const isEven = number % 2 === 0;
const message = isEven ? "It is even" : "It is odd";

This is not a valid expression:

const message = if (isEven) { "It is even" } else { "It is odd" };
// this is not valid JS and will cause an error

if blocks are statements, not expressions. The main impact of this is that you have to use ternaries instead of if statements inside JSX.


React components

React elements aren't very useful on their own. They're just static objects. To build an interface we need something reusable and dynamic, like functions.

A React component is a function that returns a React element.

function Title() {
  return <h1 className="title">Hello world!</h1>;
}

Valid elements

A React element can be a JSX element, or a string, number, boolean or array of JSX elements. Returning null, undefined, false or "" (empty string) will cause your component to render nothing.

Composing components

Components are useful because JSX allows us to compose them together just like HTML elements. We can use our Title component as JSX within another component. It's like making your own custom HTML tags.

function Title() {
  return <h1 className="title">Hello world!</h1>;
}

function Page() {
  return (
    <div className="page">
      <Title />
    </div>
  );
}

When we use a component in JSX (<Title />) React will find the corresponding Title function, call it, and use whatever element it returns.

Customising components

A component where everything is hard-coded isn't very useful. It will always return the exact same thing, so there's almost no point being a function. Functions are most useful when they take arguments. Passing different arguments lets us change what the function returns each time we call it.

JSX supports passing arguments to your components. It does this using the same syntax as HTML:

<Title name="oli" />

React component functions only ever receive one argument: an object containing all of the arguments passed to it. React will gather up any key="value" arguments from the JSX and create this object.

This object is commonly named "props" (short for properties). Using an object like this means you don't have to worry the order of arguments. So in this case our Title function will receive a single argument: an object with a "name" property.

function Title(props) {
  console.log(props); // { name: "oli" } (assuming <Title name="oli" /> was used)
  return <div className="title">Hello world</div>;
}

You can use these props within your components to customise them. For example we can interpolate them into our JSX to change the rendered HTML:

function Title(props) {
  return <div className="title">Hello {props.name}</div>;
}

Now we can re-use our Title component to render different DOM elements:

function Page() {
  return (
    <div className="page">
      <Title name="oli" />
      <Title name="sam" />
    </div>
  );
}
// <div class="page"><h1 class="title">Hello oli</h1><h1 class="title">Hello sam</h1></div>

Non-string props

Since JSX is JavaScript it supports passing any valid JS expression to your components, not just strings. To pass JS values as props you use curly brackets, just like interpolating expressions inside tags.

function Page() {
  const customName = "oliver" + " phillips";
  return (
    <div className="page">
      <Title name={customName} />
      <Title name={5 * 5} />
    </div>
  );
}
// <div class="page"><h1 class="title">Hello oliver phillips</h1><h1 class="title">Hello 25</h1></div>

Children

It would be nice if we could nest our components just like HTML. Right now this won't work, since we hard-coded the text inside our <h1>:

<Title>Hello oli</Title>

JSX supports a special prop to achieve this: children. Whatever value you put between JSX tags will be passed to the component function as a prop named children. You can then access and use it exactly like any other prop.

function Title(props) {
  return <div className="title">{props.children}</div>;
}

Now this JSX will work as we expect:

<Title>Hello oli</Title>
// <h1 class="title">Hello oli</h1>

This is quite powerful, as you can now nest your components to build up more complex DOM elements.

// pretend we have defined Image and BigText components above
<Title>
  <Image src="hand-wave.svg" />
  <BigText>Hello oli</BigText>
</Title>

Rendering to the page

You may be wondering how we get these React components to actually show up on the page.

React consists of two libraries—the main React library and a specific ReactDOM library for rendering to the DOM (since React can also to render Virtual Reality or Native mobile apps).

We use the ReactDOM.render() function to render a component to the DOM. It takes an element as the first argument and a DOM node as the second.

It's common practice to have a single top-level App component that contains all the rest of the UI.

function App() {
  return (
    <Page>
      <Title>Hello world!</Title>
      <p>Welcome to my page</p>
    </Page>
  );
}

const rootNode = document.querySelector("#root");
ReactDOM.render(<App />, rootNode);

Challenge

Time to create some components! Open up challenge.html in your editor. You should see the components we created above. Open this file in your browser too to see the components rendered to the page.

Create a new component called Card. It should take 3 props: title, image and children, that render into h2, img and p elements respectively.

Replace the p in the App component with a Card. Pass whatever you like as the 3 props (although here's an image URL you can use: https://source.unsplash.com/400x300/?burger).

Last updated