Last updated
Basic JSX Element Conversion
The simplest JSX — a single HTML element — compiles to a React.createElement call. Input JSX:
const element = <h1>Hello, World!</h1>;
Compiled JavaScript (classic transform):
const element = React.createElement("h1", null, "Hello, World!");
The three arguments are: the element type (tag name as a string), the props object (null when there are no props), and the children (the text content).
JSX with Props
When an element has attributes, they become the props object. Input JSX:
const button = <button type="submit" className="btn btn-primary" disabled={false}>Submit</button>;
Compiled JavaScript:
const button = React.createElement(
"button",
{ type: "submit", className: "btn btn-primary", disabled: false },
"Submit"
);
Notice that className is used instead of class because class is a reserved keyword in JavaScript.
Nested JSX Elements
Nested elements become nested createElement calls. Input JSX:
const card = (
<div className="card">
<h2>Title</h2>
<p>Description text here.</p>
);
Compiled JavaScript:
const card = React.createElement(
"div",
{ className: "card" },
React.createElement("h2", null, "Title"),
React.createElement("p", null, "Description text here.")
);
Each child element is passed as an additional argument after the props object.
Custom Component JSX
When a JSX tag starts with an uppercase letter, it refers to a custom component. Input JSX:
const app = (
<UserProfile
userId={42}
showAvatar={true}
onLogout={() => console.log("logout")}
/>
);
Compiled JavaScript:
const app = React.createElement(UserProfile, {
userId: 42,
showAvatar: true,
onLogout: () => console.log("logout")
});
The component reference is passed directly (not as a string), which is why React requires custom components to start with an uppercase letter — lowercase tags are treated as HTML strings.
Conditional Rendering with Ternary
Ternary operators in JSX compile cleanly to JavaScript. Input JSX:
const message = (
<div>
{isLoggedIn ? <UserGreeting name={user.name} /> : <GuestGreeting />}
</div>
);
Compiled JavaScript:
const message = React.createElement(
"div",
null,
isLoggedIn
? React.createElement(UserGreeting, { name: user.name })
: React.createElement(GuestGreeting, null)
);
This shows why you cannot use if statements directly inside JSX — the compiled output is a function call expression, and expressions cannot contain statements.
Conditional Rendering with Logical AND
The && pattern for optional rendering. Input JSX:
const notification = (
<div>
{hasError && <ErrorBanner message={errorMessage} />}
<MainContent />
</div>
);
Compiled JavaScript:
const notification = React.createElement(
"div",
null,
hasError && React.createElement(ErrorBanner, { message: errorMessage }),
React.createElement(MainContent, null)
);
List Rendering with map()
Rendering lists using Array.map(). Input JSX:
const list = (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
Compiled JavaScript:
const list = React.createElement(
"ul",
null,
items.map(item =>
React.createElement("li", { key: item.id }, item.name)
)
);
The key prop is passed as part of the props object in the compiled output, though React uses it internally and does not pass it to the component.
JSX Fragments
Fragments let you return multiple elements without a wrapper. Input JSX:
const rows = (
<>
<tr><td>Row 1</td></tr>
<tr><td>Row 2</td></tr>
</>
);
Compiled JavaScript:
const rows = React.createElement(
React.Fragment,
null,
React.createElement("tr", null, React.createElement("td", null, "Row 1")),
React.createElement("tr", null, React.createElement("td", null, "Row 2"))
);
React.Fragment is a special component that renders its children without adding any DOM nodes.
Spread Props
Spreading a props object onto a component. Input JSX:
const buttonProps = { type: "button", disabled: false };
const btn = <Button {...buttonProps} label="Click me" />;
Compiled JavaScript:
const buttonProps = { type: "button", disabled: false };
const btn = React.createElement(Button, Object.assign({}, buttonProps, { label: "Click me" }));
The spread is compiled to Object.assign (or object spread syntax in modern output), merging the spread object with any additional explicit props.
Automatic JSX Transform (React 17+)
With the newer automatic transform, React no longer needs to be imported in every file. The same button JSX:
const button = <button className="btn">Click</button>;
Compiles to (automatic transform):
import { jsx as _jsx } from "react/jsx-runtime";
const button = _jsx("button", { className: "btn", children: "Click" });
The compiler automatically imports _jsx from react/jsx-runtime, eliminating the need for import React from 'react' at the top of every file. This is why React 17+ projects can omit the React import.
Self-Closing Tags
Self-closing JSX tags compile to createElement calls with no children argument. Input JSX:
const input = <input type="text" placeholder="Enter name" onChange={handleChange} />;
Compiled JavaScript:
const input = React.createElement("input", {
type: "text",
placeholder: "Enter name",
onChange: handleChange
});
No children argument is passed because self-closing elements have no children.