JSX In Depth
Reviewed & published by Brayan K
JSX is a syntax extension for JavaScript that lets you write HTML-like markup which compiles to React.createElement calls returning plain JavaScript objects. Because it is really JavaScript, you embed values with {}, write attributes in camelCase like className, and must return a single root element.
Part of the free React course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- How JSX compiles to React.createElement objects
- Embed any JS expression with curly braces {}
- Why attributes are camelCase (className, htmlFor)
- Return one root element — or a fragment
- Self-closing tags and JSX comments
- Render conditions and lists as expressions
1️⃣ JSX Is Sugar for createElement
Every JSX tag is rewritten into a React.createElement(type, props, ...children) call, which returns a plain object describing what to render. JSX never touches the real DOM directly — it just builds these lightweight descriptions.
// You write JSX:
const heading = <h1 className="title">Hello</h1>;
// Babel compiles it to:
const heading = React.createElement(
"h1",
{ className: "title" },
"Hello"
);// JSX is just sugar. This JSX...
// <h1 className="title">Hello</h1>
// ...compiles to this plain JS:
function h1Element() {
return {
type: "h1",
props: { className: "title", children: "Hello" },
};
}
const el = h1Element();
console.log(el.type); // h1
console.log(el.props.className); // title
console.log(el.props.children); // Hello
// React.createElement("h1", { className: "title" }, "Hello") builds the same object.2️⃣ Expressions in {} and camelCase Attributes
Curly braces embed a single JavaScript expression — a value like {user.name}, {2 + 2}, or {items.map(...)}. Statements such as if or for are not allowed there. Attributes are written in camelCase because they map to DOM properties: HTML class becomes className and for becomes htmlFor (both are reserved words in JavaScript).
function Label({ id, text }) {
return (
<label className="field-label" htmlFor={id}>
{text.toUpperCase()}
</label>
);
}// Inside JSX, {} holds ONE JavaScript expression.
// Expressions return a value; statements (if, for) do not.
const user = { first: "Ada", last: "Lovelace" };
const price = 42;
function fullName(u) { return u.first + " " + u.last; }
console.log(fullName(user)); // Ada Lovelace
console.log("$" + price.toFixed(2)); // $42.00
console.log([1, 2, 3].map(n => n * 2).join(", ")); // 2, 4, 6
// In JSX you would write: <h2>{fullName(user)}</h2> and <p>{"$" + price}</p>Your turn — mimic what JSX builds:
// 🎯 YOUR TURN — fill in each ___ then press "Try it Yourself".
// 1) Return an OBJECT that mimics what JSX compiles to for <button>.
// The tag goes in "type" and the label goes in props.children.
// 2) Use a {} EXPRESSION idea: build the className by joining two words.
function buttonElement(label) {
return {
type: ___, // 👉 "button"
props: { className: ["btn", "primary"].join(___), children: label }, // 👉 " "
};
}
const el = buttonElement("Save");
console.log(el.type); // button
console.log(el.props.className); // btn primary
console.log(el.props.children); // Save
// ✅ Expected output:
// button
// btn primary
// Save3️⃣ One Root, Self-Closing Tags & Comments
A component returns a single expression, so sibling elements must share one parent. Use a real wrapper when you need one, or a fragment <>...</> when you do not want an extra DOM node. Void tags like <img> and <br> must be self-closed (<img />), and comments use the form. Whitespace and newlines between tags collapse, so add explicit spaces with {' '} when needed.
function Card() {
return (
<>
<img src="logo.png" alt="logo" />
<h2>Title</h2>
<p>Body{' '}text</p>
</>
);
}🧩 Reorder Challenge
These lines build a valid component that returns two elements inside a fragment. Put them in the right order:
A. <h1>{title}</h1>
B. function Header({ title }) {
C. return (
D. </>
E. <>Common Errors (and the fix)
- "Adjacent JSX elements must be wrapped." — You returned two siblings. Wrap them in a parent or a fragment <>...</>.
- "Unknown prop class on tag." — Use className, not class; and htmlFor, not for.
- Comment renders as text. — A plain // note between tags shows up. Use .
- "Unterminated JSX" on <img>. — Void elements must self-close: <img />.
📋 Quick Reference
| In HTML / want | In JSX |
|---|---|
| class attribute | className="..." |
| for attribute | htmlFor="..." |
| embed a value | {expression} |
| a comment | |
| two siblings | <>...</> |
🧠 Quick Recall
1. What does a JSX tag compile to?
A React.createElement(type, props, children) call that returns a plain JavaScript object describing the element.
2. Why className instead of class?
Because class is a reserved word in JavaScript, and JSX attributes map to DOM properties. Likewise for becomes htmlFor.
3. Can you put an if in {}?
No — braces hold an expression, not a statement. Use a ternary, &&, or compute a variable above the return.
Mini-Challenge: Render a List
Map an array of fruits into list-item strings the same way {items.map(...)} produces an array of elements. Run it and check your output.
// 🎯 MINI-CHALLENGE: render a list the way JSX does
//
// JSX turns {items.map(...)} into an array of elements.
// 1. Map each fruit to a string "<li>NAME</li>".
// 2. Join them with newlines to simulate the rendered output.
// 3. console.log the whole list.
//
// ✅ Expected output:
// <li>apple</li>
// <li>banana</li>
// <li>cherry</li>
const fruits = ["apple", "banana", "cherry"];
// your code here🎉 Lesson Complete
- ✅ JSX is sugar for createElement objects
- ✅ {} embeds any JavaScript expression
- ✅ Attributes are camelCase: className, htmlFor
- ✅ Return one root (or a fragment), self-close void tags, comment with
Practice quiz
What does a JSX tag compile to?
- A raw HTML string
- A CSS rule
- A React.createElement(...) call returning a plain JavaScript object
- A DOM node directly
Answer: A React.createElement(...) call returning a plain JavaScript object. Every JSX tag is rewritten into React.createElement(type, props, ...children), which returns a plain object.
Is JSX the same as HTML?
- No — it looks like HTML but compiles to JavaScript and uses camelCase attributes
- Yes, exactly
- Yes, but only for divs
- No, it is actually XML
Answer: No — it looks like HTML but compiles to JavaScript and uses camelCase attributes. JSX resembles HTML but is JavaScript underneath, which is why it uses className and htmlFor.
What goes inside JSX curly braces { }?
- Any statement like if or for
- Only string literals
- CSS rules
- A single JavaScript expression
Answer: A single JavaScript expression. Braces hold one expression (a value); statements like if/for/switch are not allowed there.
Why can't you write an if statement directly inside JSX braces?
- if is not supported in JavaScript
- Braces only hold an expression, not a statement
- It would be too slow
- React forbids all conditionals
Answer: Braces only hold an expression, not a statement. Braces evaluate an expression. Use a ternary, &&, or compute a variable above the return instead of an if.
What is the JSX attribute for the HTML 'class' attribute?
- className
- class
- cssClass
- classList
Answer: className. class is a reserved JS word, so JSX uses className. Likewise the HTML 'for' becomes htmlFor.
Why must a component return a single root element?
- The DOM allows only one element
- React renders only the first child
- A JSX expression compiles to one element, and a function returns one value
- To save memory
Answer: A JSX expression compiles to one element, and a function returns one value. A return is one expression evaluating to one element; wrap siblings in a parent or a fragment.
How do you write a comment inside JSX?
- // comment
- {/* comment */}
- /* comment */
- <!-- comment -->
Answer: {/* comment */}. Use an expression comment {/* like this */}; a plain // between tags would render as text or error.
How must void/self-closing tags like img be written in JSX?
- <img>
- <img></img> only
- </img>
- <img />
Answer: <img />. Void elements must be self-closed in JSX: <img />, <br />, etc.
What is the JSX attribute equivalent of the HTML 'for' attribute on a label?
- for
- htmlFor
- forHtml
- labelFor
Answer: htmlFor. Because 'for' is a reserved JS word, JSX uses htmlFor for the label's for attribute.
What does {items.map(...)} produce in JSX?
- A single string
- Nothing
- An array of elements that React renders as a list
- A DOM fragment node
Answer: An array of elements that React renders as a list. map returns an array of elements; React renders each one, which is how lists are built.
Continue this course
- Previous: Introduction to React
- Next: Components and Props — Build reusable UI pieces and pass data between them using props
- Quick reference: React cheat sheet
Frequently asked questions
Is JSX the same as HTML?
No. JSX looks like HTML but compiles to React.createElement(...) calls that return JavaScript objects. That is why it uses className instead of class and htmlFor instead of for — those are reserved words in JavaScript.
Why do I need one root element?
Because a function can only return one value, and a JSX expression compiles to a single element. Wrap siblings in one parent — or, to avoid an extra div, in a fragment <>...</>.
Why can't I write an if statement inside the curly braces?
Braces in JSX hold a single JavaScript expression, not a statement. if/for/switch are statements. Use a ternary, &&, or compute a variable above the return instead.
How do I write a comment inside JSX?
Use an expression comment: {/* like this */}. A plain // or /* */ between tags will be rendered as text or cause an error.