November 21, 2025
• 5 min readThe rendering process in React can be divided into three basic steps: importing the necessary dependencies, creating a React element, and rendering it into a selected container in the HTML document.
Introduction
In the following example, you can see how to render a simple element in React 19, which uses the new API for creating a root container using the createRoot function.
Take a look at the example from the Joy of React course from by Josh W. Comeau:
// 1. Import dependencies
import React from 'react';
import { createRoot } from 'react-dom/client';
// 2. Create a React element
const element = React.createElement(
'p',
{ id: 'foo' },
'Hello World!'
);
// 3. Render the application
const container = document.querySelector('#root');
const root = createRoot(container);
root.render(element);
This is roughly how you could render a simple element that would contain the text “Hello World!”, have HTML tag <p> and an id attribute with the value foo. Let’s break it down in more detail.
Importing dependencies
import React from 'react';
import { createRoot } from 'react-dom/client';
On the first line, we import the entire React core library, and then also createRoot from the react-dom/client package, which allows us to create a root container for our application.
Why is it necessary to select react-dom/client specifically? This is because React is a platform-agnostic library and therefore we need to specify which of the rendering packages we want to use. For web applications, this is react-dom, while for native applications (iOS, Android, Windows, macOS) it would be the react-native package. There’s also a react-three-fiber package that enables rendering 3D scenes using WebGL and the Three.js library.
Creating React.createElement
We can say that every time we use JSX notation, the React.createElement() function is called and returns an object that describes what will be rendered on the web page.
This function accepts three or more arguments:
- The type of the element to create (e.g.,
div,span,p, or a custom React component). - The properties we want this element to have (e.g.,
id,className,onClick). - The element’s contents (children of the element). We can omit this if the element should be empty.
It can looks like this:
{
type: "p",
key: null,
ref: null,
props: {
id: 'hello',
children: 'Hello World!',
},
_owner: null,
_store: { validated: false }
}
As you can see, this is an ordinary JavaScript object that contains various properties that describe the created element. The _owner and _store properties are internal React properties and are not intended to be used directly in applications.
JavaScript vs React
Imagine we want to render a div element with the text “Hello, world!” in the browser, which will have a class of container.
<div class="container">Hello, world!</div>
Using native JavaScript, we could do it like this:
const element = document.createElement('div');
element.textContent = 'Hello, world!';
element.className = 'container';
In React, we would then use this code for the same example, which does exactly the same thing:
const element = React.createElement(
'div',
{ className: 'container' },
'Hello, world!'
);
Fortunately, we don’t have to write React.createElement() manually, because JSX does it for us. JSX is an HTML-like syntax that React uses for creating elements. Using JSX, we could write the above example even more simply:
const element = (
<div className="container">
Hello, world!
</div>
);
JSX vs createElement
If we put all the information together, we can show how JSX works under the hood with the following examples.
<button className="primary" onClick={handleClick}>
Click me
</button>
This code in JSX is actually transpiled into a React.createElement() call, which looks like this:
React.createElement(
'button',
{ className: 'primary', onClick: handleClick },
'Click me'
)
And the resulting object that React.createElement() returns will look something like this:
{
type: 'button',
props: {
className: 'primary',
onClick: handleClick,
children: 'Click me'
},
key: null,
ref: null,
// ... other internal properties
}
Writing an entire application using React.createElement() function is entirely possible, but very impractical and complicated. That’s why JSX is almost always used instead, as it is much clearer and easier to work with.
Rendering
const container = document.querySelector('#root');
const root = createRoot(container);
root.render(element);
In the final step, we need to render the created React element into a container in the HTML. What is happening here is that we first select the container element from the HTML document using document.querySelector(). This container is usually a div element with a specific id, such as root.
Function createRoot(container) creates a root React container that will manage the rendering of our React elements inside the selected HTML element. Method render() creates the root and pass in the React element we want to render.
Method document.querySelector() capture a reference to a preexisting DOM element, and React takes over managing what is inside that element. Maybe you are fimiliar with document.getElementById(), it’s the same just modern version.
<div id="root">
<!-- React will render content here -->
</div>
We can think of the render function as a machine that converts React elements into DOM nodes (HTML). This code takes a JavaScript-based description of some HTML, and uses it to produce real-world DOM nodes.
Conclusion
With JSX, you almost never need to call React.createElement directly. However, understanding it helps you grasp what’s really happening in your React code and can be useful when you need to create elements dynamically or work in environments without JSX compilation.