Runnable lesson · press Try it on the examples to edit and run them here.
Understanding what JSX turns into explains why its rules exist. You do not need this to write React, but it removes a lot of mystery.
JSX compiles to function calls
This JSX:
const element = <h1 className="title">Hello</h1>;
becomes, with the classic transform, this JavaScript:
const element = React.createElement("h1", { className: "title" }, "Hello");
and with the automatic runtime that Vite and current React projects use:
import { jsx as _jsx } from "react/jsx-runtime";
const element = _jsx("h1", { className: "title", children: "Hello" });
The automatic runtime is why you no longer need import React from "react" at the top of every file. (The playground uses the classic transform, which is why a global React exists there; the result is the same.)
What the function returns
Both calls return a plain object that describes the element. It is not a DOM node. You can look at it:
function App() {
const element = <div className="card" id="c1"><span>Hi</span></div>;
return (
<pre>
{`type: ${element.type}\nprops keys: ${Object.keys(element.props).join(", ")}\nclassName: ${element.props.className}\nchildren is a ${element.props.children.type}`}
</pre>
);
}
The element has a type ("div" for HTML tags, or the component function), a props object that includes children, and a few internal fields. This object is the Virtual DOM description you met in Understanding the Virtual DOM.
Why the JSX rules make sense now
| Rule | Reason |
|---|---|
| Return one root | A function returns one value: one element object |
className, htmlFor | Attributes become keys of a JavaScript object, and class/for are reserved words |
| Capital letter for components | createElement("div") makes an HTML tag; createElement(Greeting) calls a function. The compiler needs the capital to tell them apart |
{} takes expressions only | The braces are arguments of a function call, and arguments must be expressions |
style is an object | Props are JavaScript values, so style is an object |
| Close every tag | JSX is a stricter language than HTML and must be parsed unambiguously |
Components are called by React, not by you
<Greeting name="Ada" /> becomes createElement(Greeting, { name: "Ada" }). That object just records that a Greeting is wanted with those props. React calls the Greeting function later, during rendering. That is why you write <Greeting /> and never Greeting(): calling the function yourself would skip React and break features like state.
function Greeting({ name }) {
return <p>Hi, {name}</p>;
}
function App() {
const element = <Greeting name="Ada" />;
// element is only a description: { type: Greeting, props: { name: "Ada" } }
return (
<div>
{element}
<small>element.type === Greeting: {String(element.type === Greeting)}</small>
</div>
);
}
Immutable descriptions
Elements are lightweight and read-only. You do not edit an element; you describe a new one in the next render. That is the same declarative idea from the first lessons.
Key points
- JSX compiles to
createElementorjsxcalls that return plain description objects. - The JSX rules follow from how JavaScript functions and objects work.
- React, not you, calls your components, so always use them as tags.