You’ve undoubtedly heard of JSX if you’ve worked with React. In this blog, we’ll see how JSX translates to regular JavaScript. We’ll also discuss the benefits of JSX and what happens behind the scenes when you write JSX in React.
This article will cover what JSX is and what goes on behind the scenes when using JSX with React. I will go ahead and assume that this is not your first encounter with React. No deep knowledge is required, but fundamental knowledge is. You can refer to the Getting Started page of the React documentation for a refresher.
React is an open-source JavaScript library developed by the team at Facebook. It is used to build rich graphical user interfaces (UIs). It is based on the concept of reusable components, which allows you to create complex UIs out of small isolated pieces of code called components.
JSX stands for JavaScript syntax extension. It is a JavaScript extension that allows us to describe React’s object tree using a syntax that resembles that of an HTML template. It is just an XML-like extension that allows us to write JavaScript that looks like markup and have it returned from a component.
Because JSX is not valid JavaScript, browsers can’t read it directly; they do not know what to do with it, so we need a transpiler to translate it to React.createElement()
calls. We need transpilers (a compiler that translates one form of syntax into another) like Babel or TypeScript to compile JSX into a browser-compatible version.
This occurs during the build process, so the browser will never know JSX was present in the first place. The browser, in turn, receives a tree of objects that have been described using the React API.
In addition, when we write modern JavaScript, especially some of the features introduced in ECMAScript 6, some older browsers can’t make sense of these features. We need to use a transpiler to convert ES6 to ES5.
That’s exactly how it works with JSX as well. Consider this example:
import React from 'react'
function Greet(){
return <h1>Hello World!</h1>
}
This is a simple component that renders “Hello World” in the browser, and it returns what appears to be HTML, but it isn’t. The h1
tag rendered by the Greet
component is a pure JavaScript function call to React.createElement()
.
The above example would compile into this before being used by the browser.
import React from 'react'
function Greet() {
return React.createElement("h1", {}, "Hello, World!")
}
Notice how in the JSX example above, we didn’t exactly reference the instance of React we imported, but, when compiled, it calls the React.createElement() function. So we need to have React in scope for JavaScript to know what to do with the compiled code.
The createElement()
function accepts three parameters and returns a React element:
React.createElement(
type,
[props],
[...children]
)
It is not required to use JSX while writing React, but it makes the development and debugging process easier for developers.
Let’s make a React component using JSX and see how it translates to regular JavaScript function calls.
import React from 'react'
function App (){
return (
<div>
<p>This is a list</p>
<ul>
<li>List item 1</li>
<li>List item 2</li>
</ul>
</div>
);
};
The compiled code should look like this:
import React from 'react'
function App() {
return React.createElement(
"div",
null,
React.createElement("p", null, "This is a list"),
React.createElement(
"ul",
null,
React.createElement("li", null, "List item 1"),
React.createElement("li", null, "List item 2")));
}
This is also how you would write React without JSX. With a bit of nesting, we can see that it is beginning to get unreadable and ugly. Not only does it look difficult to code, but it also looks difficult to maintain. That’s where JSX comes in, combining the beauty of HTML and the power of JavaScript.
React.createElement()
function in the example above would return an object like this:
{
"type": "div",
"key": null,
"ref": null,
"props": {
"children": [
{
"type": "p",
"key": null,
"ref": null,
"props": {
"children": "This is a list"
},
"_owner": null
},
{
"type": "ul",
"key": null,
"ref": null,
"props": {
"children": [
{
"type": "li",
"props": {
"children": "List item 1"
},
// truncated for brevity
},
{
"type": "li",
"props": {
"children": "List item 2"
},
// truncated for brevity
}
]
},
"_owner": null
}
]
},
"_owner": null
}
These objects are known as React elements, but they are just plain JavaScript objects. They describe what you want to see on the screen. They represent HTML elements, and they do not live on the page (the “real” DOM)—they live on the virtual DOM. React reads these objects and uses them to create HTML elements on the virtual DOM, after which it gets synced with the real DOM.
So we’ll have trees of objects on the virtual DOM and trees of objects on the real DOM. React automatically updates the associated DOM element when we change data on a React element.
Here are some of the DOM elements you’ll encounter:
type
: Allows us to specify the type of React element to be rendered. This can either be a string (“div”, “h1”), a React component (class or function) or a React fragment.
props
: Can be null or an object containing properties (referred to as “props” in React) that are passed to the component.
children
: The children you want to be passed into that element. If this is a quoted string, as seen above, the content will be treated as text. When adding multiple children, we use an array, and we can nest as many children as we desire.
key
: Is used to uniquely identify elements among siblings while mapping over an array (else React will scream at you).
ref
: Is a reference to an actual DOM node. It allows you to get direct access to a DOM element or an instance of a component.
$$typeof
: This property identifies the object as a React element. It is used for protection against Cross-site Scripting (XSS) attacks.
As seen earlier, when we use JSX, the compiler converts it into React function calls that the browser can comprehend. Still, following the release of React 17, the Facebook team collaborated with Babel to enhance the JSX transform without breaking any of the existing configurations.
This update does not have any effect on the JSX syntax and is not compulsory. The previous JSX transform will continue to function normally, and there are no plans to discontinue support for it.
Because JSX was compiled into React.createElement() calls, you had to have React in scope if you used JSX. With the new transform, you can skip the (required) import React from 'react'
statement in each component file. It is possible to write JSX without importing the React library at the top level or having React in scope.
React 17 adds two new entry points to the React package that will only be used by compilers such as Babel and TypeScript, so instead of converting JSX to React.createElement(), the new JSX transform automatically imports special functions from those new entry points in the React package and calls them.
So if we have this:
function Greet(){
return <h1>Hello World!</h1>;
}
With the new transform, you can write your component with JSX without importing React manually.
The new JSX transform will compile to:
// Inserted by a compiler (don't import it yourself!)
import {jsx as _jsx} from 'react/jsx-runtime';
function App() {
return _jsx('h1', { children: 'Hello world' });
}
Now we can see that we didn’t need to import React because of the new JSX transform. The import statement in the example above isn’t supposed to be done manually—the compiler will do it for you.
This improves our code and allows us to make speed improvements that React.createElement() does not enable; however, we would still need to import React to use Hooks and other React exports. This change is entirely compatible with any current JSX code, so you won’t need to change your components.
The functions included under react/jsx-runtime and react/jsx-dev-runtime can only be used by the compiler transform.
If you need to create elements manually in your code, you should continue to use React.createElement.
You can check the React documentation for more information on how to upgrade to the new JSX transform.
You can only return one top-level element from a given component. This is usually known as a parent element and is used to group the content. Remember, JavaScript is the backbone of React, and in JavaScript a function can only return one value.
Some elements in HTML do not have a closing tag. In React JSX, every tag, including those with no closing tags, must be closed. If you have an element that doesn’t have a closing tag, you have to add a slash at the end (e.g., <hr/>
).
A React component must be capitalized. Component names that do not begin with a capital letter are treated like built-in components, and it results in strings (“div”, “span”…). When the component name is capitalized, it is treated as an identifier instead of a string.
To include JavaScript expressions in JSX, you need to wrap them in curly braces. Content between the opening and closing curly braces will be evaluated as JavaScript.
The term “class” is a reserved keyword in JavaScript. In React, we must substitute className for class.
In this article we’ve learned that JSX is just a nice way to write plain JavaScript objects describing the React elements that make up your application. Try not to treat it like a black box. I hope you found this post useful, and discovered some interesting ways to harness the power of JSX.