React Elements
Learning outcomes:
- What are React elements
- Creating React elements using
createElement()
- Inspecting React elements — the
type
andprops
properties - Immutability of elements
- The
children
prop
What are elements?
At stated on React's (old) glossary page:
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:
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, instantiating the component.
For example, the <Greeting>
element represents a component instance of the component Greeting
(remember, Greeting
is a function). So is the case with the 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 representing 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:
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:
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:
createElement('section', null,
createElement('h1', null, 'A heading'),
createElement('p', null, 'A paragraph'),
createElement('div', null,
'A div with a ',
createElement('span', null, 'span'),
'.'
),
);
If typing createElement()
seems too long, we could easily simplify it to a shorter identifier using a name alias when importing it, as shown below:
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'),
'.'
),
);
Nonetheless, while createElement()
isn't any difficult to use, it certainly isn't that elegant.
Developers almost never develop complex React apps directly by creating the UI as sequences of createElement()
calls. Instead, they always prefer the syntactic sweetness of JSX over this.
Remember JSX from the previous chapters?
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 to work with.
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>
:
<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>
example above, containing a set of elements nested inside of it, 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>
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:
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/>
When we have just one tag, it's important for it to have the /
at its end. Without the /
, the JSX parser will throw an error:
// Tag doesn't have an ending '/'
<Greeting>
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 time 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 JSX code:
const element = (
<h1 className="heading">Hello World!</h1>
);
console.log(element);
which is equivalent to the following createElement()
code:
const element = 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'
, a string, because the React element represents an<h1>
DOM element.props
contains all the props provided to the element at the time of creation.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 add a prop x
manually to the element via the props
object on element
. However, since all elements are immutable in React, we get an error in doing so:
Even if the prop x
already existed on the element, we still couldn't have modified its value using element.props.x
.
If we wish to change the data (the props) of an element in React for some reason, we ought to create a new element with the new set of data. An existing element can't be mutated in any way. 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.
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, reduces the chances of errors in the program and makes development and testing much 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 to render()
or to createElement()
— NOT store the element in any identifier.
To be more specific, we don't need to store the object returned by the JSX <h1>
element in element
in the following code:
import React from 'react';
import ReactDOM from 'react-dom/client';
// To need to store the element in `element`
const element = (
<h1>Hello World! (A non-standard approach)</h1>
);
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(element);
As a matter of fact, but not related to the idea of elements, we don't even need the constant root
, for we can directly call render()
on ReactDOM.createRoot()
.
Here's how the code above can be simplified, letting go off the constants:
import React from 'react';
import ReactDOM from 'react-dom/client';
ReactDOM.createRoot(document.querySelector('#root')).render(
<h1>Hello World! (A non-standard approach)</h1>
);
Do note that even though this works absolutely fine, still we haven't touched upon the very convention that React encourages us to stick to render an app — to instantiate an App
component and provide to the component instance to render()
.
We'll consider this convention in detail in the chapter, React Components.
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, the one assigning to element.props.x
, in React's production build, we would NOT 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. A big no! We must always treat elements in React as immutable, no matter which build we're currently in.
The reason of making React elements mutable in the production build is to result in a better performance. In production, React can assert that all its guidelines are being abided by and, likewise, doesn't need to put strict measures in place, unlike during development.
The children
prop
While we're learning about elements in React, 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 come up with the corresponding createElement()
code for this JSX, it'll be something along the following lines:
createElement('section', null,
createElement('h1', null, 'A heading'),
createElement('p', null, 'A paragraph')
);
Clearly, not anything difficult to understand. Now, let's slightly modify it.
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):
createElement('section', {
children: [
createElement('h1', null, 'A heading'),
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 the <section>
.
Running this code produces the exact same output as before.
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, which in this case is an array holding two JSX elements, <h1>
and <p>
.
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:
children
prop of a React element holds a collection of all its children.When we do something like the following,
createElement('section', null,
createElement('h1', null, 'A heading'),
createElement('p', null, 'A paragraph')
);
the createElement()
function itself collects all of the arguments starting from the third one 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 is <p>
.
In other words, what we were manually doing by assigning a value to the children
prop when invoking createElement()
in the code snippets above, it 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
every now and then.
And we shall indeed do so as well once we discover components in detail in the upcoming chapter, React Components.
Spread the word
Think that the content was awesome? Share it with your friends!
Join the community
Can't understand something related to the content? Get help from the community.