React Elements

Chapter 8 21 mins

Learning outcomes:

  1. What are React elements
  2. Creating React elements using createElement()
  3. Inspecting React elements — the type and props properties
  4. Immutability of elements
  5. The children prop

What are elements?

At stated on React's (old) glossary page:

React elements are the building blocks of React applications.

Indeed, elements represent one of the most fundamental principles of React. A React app is nothing without elements.

React elements work pretty much the same way as HTML elements. They define certain pieces of content to have a special meaning to them, for e.g. a piece of content that denotes a 'chat box', a piece of content that denotes a 'slider', etc.

It's good to know that React elements are the building blocks of React apps. But still, this ain't a precise technical definition of what exactly is a React element.

Fortunately, once again, React's (old) website helps us in this regard. We can define a React element as follows:

A React element is an abstraction over a DOM element or a component instance.

This definition ain't that difficult to comprehend.

It says that a React element either represents an HTML DOM element (which is a concept of the HTML DOM) or a component instance (which is a concept of React).

For example, we all now know that the <h1> React element is simply an abstraction over the DOM <h1> element. So is the case with <div>. So is the case with <span>. So is the case with all HTML elements.

As for component instances, we learnt in the previous React Basics chapter that a component is a function returning a React node whereas a component instance is an actual element, instanting the component.

For example, the <Greeting> element represents a component instance of the component Greeting (it's actually a function Greeting()). So is the case with an element <ChatBox>, instantiating the component ChatBox.

Besides this, every single element in React is mainly comprised of 2 things:

  • A type
  • A set of props

The type specifies whether the element represents a DOM element or a component instance. If the type is a string, e.g 'h1', the element represents a DOM element. Otherwise, asserting that the type is a function or a class, the element represents a component instance.

Props works similar to attributes of HTML elements and properties of DOM elements. That is, they hold additional data for the element, essentially serving to describe its content, styles, and interactions.

Let's now review how to create elements in React.

Creating elements in React

A React element is created via the createElement() method exported by the react library, as we saw in the previous chapters.

Here's its syntax:

createElement(type, props, ...children)
  • The first type argument specifies the type of the element (which we've already discussed above ).
  • The second props arguments provides a set of props.
  • From the third argument onwards, every single argument specifies a child React node to be put inside the element being created. Any of these arguments can also be an array of React nodes (more on that later).

As is obvious, since React elements are also valid React nodes, the third and later arguments can be elements themselves.

Let's consider a handful of examples.

React elements represeting HTML DOM elements

We'll start off with seeing how to create React elements representing HTML DOM elements.

To get the following HTML element using React,

<h1>Hello World!</h1>

we'd write the following code:

React.createElement('h1', null, 'Hello World!')

To create the <div> element below,

<div class="intro-sect" id="d1">The intro section<div>

we'd write the following:

React.createElement(
   'div',
   { className: 'intro-sect', id: 'd1' },
   'The intro section'
);

Similarly, to create the nested markup below,

<section>
   <h1>A heading</h1>
   <p>A paragraph</p>
   <div>A div with a <span>span</span>.</div>
</section>

we'd write the following:

React.createElement('section', null,
   React.createElement('h1', null, 'A heading'),
   React.createElement('p', null, 'A paragraph'),
   React.createElement('div', null,
      'A div with a ',
      React.createElement('span', null, 'span'),
      '.'
   ),
);

If typing React.createElement() seems too long, we could easily simplify it to a shorter identifier, using a named import and then an alias for it:

import { createElement as e } from 'react';

e('section', null,
   e('h1', null, 'A heading'),
   e('p', null, 'A paragraph'),
   e('div', null,
      'A div with a ',
      e('span', null, 'span'),
      '.'
   ),
);

Live Example

Anyways, frankly speaking, createElement() isn't really difficult to use. But it isn't even that intuitive.

Developers almost never develop complex React apps purely by typing the code as sequences of createElement() calls. Instead, they always prefer the syntactic sweetness of JSX over this.

Remember JSX from our previous discussions?

JSX elements are purely simplifications over manually invoking createElement(). They resemble HTML/XML elements, which all web developers are well-accustomed to writing, and that's what makes JSX so easy and intuitive.

For instance, the following element,

<h1>Hello World!</h1>

could be represented in JSX as follows:

<h1>Hello World!</h1>

On the same lines, the following:

<div class="intro-sect" id="d1">The intro section<div>

could be represented in JSX as follows:

<div className="intro-sect" id="d1">The intro section<div>

And the same <section> element above could be represented in JSX as follows:

<section>
   <h1>A heading</h1>
   <p>A paragraph</p>
   <div>A div with a <span>span</span>.</div>
</section>

Live Example

Absolutely amazing, isn't this?

React elements representing component instances

Now, let's see how to create React elements representing instances of given components.

In the code below, we define a Greeting component that returns a very basic greeting text:

function Greeting() {
   return 'Hello World!'
}

To obtain an instance of this component, we'll again call createElement(), albeit this time its first argument will be the component function, not any kind of a string:

React.createElement(Greeting, null);

Using JSX, we can simplify this as follows:

<Greeting></Greeting>

In fact, as we learnt in React Basics and as we'll learn again in the upcoming chapters, since <Greeting> doesn't have any children here, it can be further simplied to just one tag:

<Greeting/>

This convention follow from denoting void elements in HTML/XML.

Now that we have a good understanding of how to create React elements using createElement() and JSX (which merely converts back to createElement()), it's a great idea to look into what exactly is returned by a createElement() call.

Inspecting React elements

createElement() returns back a pure JavaScript object (i.e. inheriting from the Object interface) with a handful of properties.

The ones that are particularly important are:

  • type — the type of the element.
  • props — an object holding the props of the element.

Consider the following code:

const element = (
   <h1 className="heading">Hello World!</h1>
);

console.log(element);

which is equivalent to the following:

const element = React.createElement(
   'h1',
   { className: 'heading' },
   'Hello World!'
);

console.log(element);

Here's what the console displays:

{
   $$typeof:  Symbol(react.element)
   key:  null
   props:  {className: 'heading', children: 'Hello World'}
   ref: null
   type: 'h1'
   _owner:  null
}

Alright, so there are a handful of properties to digest here. Let's go one-by-one over them.

type is 'h1' because the element is a DOM element abstracting the <h1> element.

props contains the attributes set on the JSX element, or the properties set on the object provided as the second argument to createElement() (the previous JSX also gets converted to this kind of a code).

ref contains an object holding the refs of the element. We'll learn more about refs in the React Refs chapter.

key defines a unique key for the element, used to signal to the reconciler when an element has changed or hasn't changed. We'll learn more about keys in React Keys.

The properties $$typeof and _owner are meant for internal use by React and so we won't be going over them here.

Immutability of elements

React elements feature an extremely important idea that aligns with the very design ideology of React — immutability.

In particular, React elements are immutable in nature. This means that once a React element is created, it can NOT be mutated, i.e. it can't be modified.

Here's an illustration:

const element = (
   <h1>Exploring immutability</h1>
);

element.props.x = 10;

We're trying to change the props property of the object stored in element (as we explored just a while ago in the previous section). However, since the object is made immutable by React, we get an error in doing so.

Uncaught TypeError: Cannot add property x, object is not extensible ...

Live Example

To change an element, we ought to create a new element. As simple as that.

As you may know, this isn't how DOM elements work. In the DOM, we can mutate element nodes; in fact, this is the very design of the DOM — to be able to mutate elements.

In effect, React elements are merely descriptions of how actual DOM elements should look on the webpage, supplemented with the idea of props. There's little to almost no sense in going on and changing this description of an element in React.

It's all upto React and the rendering engine being used (React DOM in our case) to be able to understand these descriptions and turn them into interactive trees of DOM nodes that work just as we are used to seeing.

Why are elements in React immutable?

Well, the answer to this is pretty much the same as for the answer to why strings are immutable in JavaScript.

Immutability prevents mutation and, likewise, prevents side effects from happening in a program. This, in turn, prevents the chances of errors in the program, and thus makes development easier.

As we saw above, React elements don't have any methods available on them (obviously apart from the methods inherited from the Object interface) and so, in this way, they are of little use to us if we want to store them.

You won't almost ever — in fact, never — find real-world apps storing React elements in identifiers and then working with those identifiers.

The de facto approach in React is to directly pass a React element, instantiating a component, to render() and put all other elements inside that one single root element.

To be more specific, we don't need to do the following:

import React from 'react';
import ReactDOM from 'react-dom/client';

const element = (
   <h1>Hello World! (A non-standard approach)</h1>
);

const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(element);

We don't need to store the element inside element. In fact, the element shouldn't even represent a DOM element; it should instead represent a component instance of something called App (more on that later).

Plus, we don't even need the constant root, for we can directly call render() on ReactDOM.createRoot().

We've been using these non–de facto approaches uptil now in this course to help explain everything clearly and because we haven't yet explored React components to the core.

As this course progresses and we learn about components, we'll begin to switch gears and transition to the de facto approach of structuring React code.

Immutability only in React's development build

It's only when working with the development build of React that elements are immutable. The code above was executed in the development build and, as a consequence, we got an error thrown, reading that it ain't possible to mutate the React element.

In the production build, however, React elements are mutable. Hence, if we run the same code above in React's production build, we won't run into any errors.

But this doesn't mean that we should mutate elements and stick to using the production build to be able to do so. No. We must always treat elements in React as immutable, no matter which build we are in.

The reason of making React elements mutable in the production build is to allow for a better performance. In production, React can assert that all its guidelines would be abided by and, likewise, doesn't need to put strict measures in place.

The children prop

While we're learning about elements, it is germane to talk about the children prop and what purpose it serves.

But before that, let's take a look at an example.

Consider the following JSX code:

<section>
   <h1>A heading</h1>
   <p>A paragraph</p>
</section>

Notice the children of <section> here carefully. They are <h1> and <p>, in this very order, right?

If we explore the corresponding createElement() for this JSX, it'll be something along the following lines:

React.createElement('section', null,
   React.createElement('h1', null, 'A heading'),
   React.createElement('p', null, 'A paragraph')
);

Clearly, not anything difficult to understand.

Now this same code could be expressed as follows, leveraging the children prop (don't worry, we'll be explaining children in depth very shortly below):

React.createElement('section', {
   children: [
      React.createElement('h1', null, 'A heading'),
      React.createElement('p', null, 'A paragraph')
   ]
});

The children prop is set to an array holding the same two React elements, in the exact same order, that we provided in the code above while creating a <section> React element.

When we run the code, we get the exact same output as before.

Live Example

The corresponding JSX could obviously leverage children as well, as shown below:

<section children={[
   <h1>A heading</h1>,
   <p>A paragraph</p>
]}></section>

The children attribute is followed by a pair of curly braces ({}) to indicate that a JavaScript expression follows. This expression turns out to be an array holding two JSX elements.

Of course, it isn't desirable to use children in this way in the JSX, but at least the idea of how children can be used is made clear.

The question is: What exactly is children?

Well, as the name suggests:

The children prop of a React element holds a collection of all its children.

When we do something like the following,

React.createElement('section', null,
   React.createElement('h1', null, 'A heading'),
   React.createElement('p', null, 'A paragraph')
);

the createElement() method itself groups all of the arguments starting from the third one onwards inside an array, and then assigns this array to the children prop of the element created.

This can be confirmed by manually inspecting the children prop of the <section> element created above:

{
   $$typeof:  Symbol(react.element)
   key:  null
   props:  {
      children: [
         { $$typeof:  Symbol(react.element), type: 'h1', key:  null, ... }
         { $$typeof:  Symbol(react.element), type: 'p', key:  null, ... }
      ]
   }
   ref: null
   type: 'section'
   _owner:  null
}

As can be seen, the props property of the returned element has a children prop pointing to an array containing two React elements, the first of which is <h1> and the second one is <p>.

In other words, what we were manually doing by assigning a value to the children prop, in the code snippets above, is automatically done by createElement() behind the scenes.

So what's the significance of children?

Well, when any given React element is rendered, React uses the children prop to traverse down the entire tree originating from that element and then rendering all of it.

In other words, children is key to React itself in order to determine what is contained within an element so that it could go over all of those contained elements, and then repeat this process for those elements themselves, until the entire app/root is rendered.

To boil it down, whenever we nest elements inside other elements in React, we are in effect consuming the children prop without us knowing of it.

But children isn't always meant to be an implicit feature; sometimes we can explicitly use it as well. As a matter of fact, it's quite common for real-world React apps to use children.

And we shall indeed do so as well once we discover components in detail in the upcoming chapter, React Components.