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, 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'),
'.'
),
);
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>
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.
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.
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:
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.