Exploring How React Converts JSX Markup to HTML Code

Exploring How React Converts JSX Markup to HTML Code

Spoiler: It doesn't...at least not directly

Mar 15, 2023·

3 min read

Ever wonder how React converts JSX markup into HTML code?

Your first thought might be that React is doing something magical behind the scenes, but the truth is a little less interesting. The reality is that React doesn't directly convert JSX into HTML.

A build tool like Babel or TypeScript converts JSX into function calls

Most React applications that you run in the browser need to be built. A build tool like Vite with the react template can automatically set this up for you. Tools take advantage of this build step to convert JSX into function calls that the browser can properly execute.

Depending on your app's version of React and your build tool, the function that's used may vary. For this post, we'll stick with the function React.createElement because it's an API that you can also use directly in your application code.

You can check out the React blog to learn more about the newer JSX transform and the reasons behind it.

Ok!

So back to React.createElement.

Let's look at an example.

function MyComponent() {
    return <div id="my-id">My Component</div>;
}

// The code above gets converted to:
function MyComponent() {
    return React.createElement(
        'div',
        { id: 'my-id' },
        'My Component'
    );
}

Each instance of JSX (i.e. <div></div>) is converted into a function call like the above. Here's the same example shown in the babel repl. Once all the JSX is converted, the browser won't have any trouble running the code because it's just executing functions.

Ok great. But what the heck does createElement return and how does that get turned into HTML?

Great question.

Here's what the React.createElement call from above gets turned into.

// The createElement function returns the following object
const myElement = {
    'type': 'div',
    'key': null,
    'ref': null,
    'props': {
        'id': 'my-id',
        'children': 'My Component'
    },
    '_owner': null,
    '_store': {}
}

The createElement function returns an object that describes a React element. The first argument is set to the type, the properties in the second argument are stored in the props and the remaining arguments are stored in the props under the children property.

That last bit about the remaining arguments is a bit tricky.

React components can have multiple children that could be nested.

Here's a more complex example that shows what that might look like:

function MyComponent() {
    return <div id="my-id">My <span>Cool</span> Component</div>;
}

// The jsx above gets converted to:
React.createElement(
    'div',
    { id: 'my-id' },
    'My ',
    React.createElement('span', null, 'Cool'),
    ' Component'
);

// The createElement function returns the following object
const myElement = {
    'type': 'div',
    'key': null,
    'ref': null,
    'props': {
        'id': 'my-id',
        'children': [
            'My ',
            {
                'type': 'span',
                'key': null,
                'ref': null,
                'props': {
                    'children': 'Cool'
                },
                '_owner': null,
                '_store': {}
            },
            ' Component'
        ]
    },
    '_owner': null,
    '_store': {}
}

You can see that the children property in the div element's props is an array this time because there are multiple children. Also, one of the items in the children array is a nested object that describes the span element.

React can convert these object descriptors into HTML

What you're left with is a series of nested objects that describe the HTML. We can take these objects and use standard browser DOM APIs to handle the creation of these elements.

The code to do this is a bit complex because React has to handle updates, account for edge cases, handle performance concerns, and implement additional features to improve the developer experience.

That said, a very basic implementation can be written to help get the point across.

Why don't you try to see if you can figure it out below?

The solution is provided if you get stuck.

Here are some API docs that may help: