JSX and Rendering: UI as an Expression
JSX is not magic markup — it's syntax sugar for plain function calls. Understanding the function calls it compiles to demystifies almost everything that confuses beginners about it.
What you'll learn
- Explain what JSX compiles to and why that explains its rules
- Predict what a piece of JSX would compile to as nested function calls
- Identify why JSX requires a single root element and camelCase attributes
Prerequisites
Explanation
JSX looks like HTML living inside JavaScript, and that resemblance is exactly what makes its rules feel arbitrary until you know the one fact that explains all of them: JSX is not HTML. It's syntax sugar that compiles down to plain function calls.
Write this JSX:
<h1 className="title">Hello, {name}</h1>
It compiles to something equivalent to this ordinary JavaScript function call:
createElement("h1", { className: "title" }, "Hello, ", name)
That single fact explains nearly every "weird" JSX rule beginners hit:
Why attributes use camelCase (className, not class). They're not HTML attributes at all — they're keys in a plain JavaScript object (the second argument), and class is a reserved word in JavaScript, so it can't be used as an identifier-like property the way HTML uses it.
Why JSX needs a single root element (or a Fragment). A function call returns exactly one value. createElement(...) returns one object describing one element — there's no way for a single function call to "return two things," so two sibling elements at the top level have nowhere to go without a wrapper.
Why {expression} works but {if (...) {...}} doesn't. The curly braces drop you into a JavaScript expression context — an argument being passed into that createElement call — and if is a statement, not an expression that produces a value. This is also why conditional rendering in JSX leans on expressions like the ternary operator or &&, covered in a later lesson, rather than if statements.
Rendering itself follows from this too: a component is a function that returns a description of UI (that tree of createElement calls, often called elements) — not the real DOM directly. React takes that description, compares it to what was there before, and updates only what actually changed. You never manually mutate the DOM in ordinary React code; you return what it should look like, and React handles turning that into real changes.
Example
A tiny, real implementation of createElement — exactly the function JSX compiles calls to, minus React's actual rendering logic.
function createElement(type, props, ...children) {
return { type, props: props || {}, children };
}
// Equivalent to the JSX: <h1 className="title">Hello, {name}</h1>
const name = "Ada";
const element = createElement("h1", { className: "title" }, "Hello, ", name);
console.log(JSON.stringify(element));Try it yourself
Add a second attribute (e.g. id: "greeting") to the props object and re-run.
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
Using the createElement function already defined, build the element that JSX `<p className="note">Total: {total}</p>` would compile to (with total = 42), and store it in noteElement.
Checks: correct element type · correct className prop · correct children in order
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
Write a function countRootChildren(jsxLikeArray) that takes an array meant to represent JSX top-level siblings and returns its length. Then write hasSingleRoot(jsxLikeArray) that returns true only if the array has exactly one element (a valid single JSX root), false otherwise -- modeling why JSX requires exactly one root element.
Checks: countRootChildren counts correctly · a single element is recognized as a valid root · two elements are recognized as invalid without a wrapper
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Trying to write an `if` statement directly inside JSX curly braces, not realizing the braces only accept expressions.
- Forgetting a wrapping element (or Fragment) around two sibling elements at a component's top level.
- Using `class` instead of `className`, not realizing JSX attributes are JavaScript object keys, where `class` is a reserved word.
Knowledge check
Takeaway
JSX compiles to plain function calls that describe UI as data — knowing this explains camelCase props, the single-root-element rule, and why only expressions (not statements) work inside curly braces.
Summary
This lesson demystified JSX by implementing the createElement function it compiles calls to, and used that to explain JSX's core syntax rules from first principles.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.