The essence of JSX
What JSX is and how it turns into JavaScript function calls, and the rules around expressions, attributes, and children — all in one place.
In Chapter 2, while building your first project with Vite, you naturally saw code like this:
function App() {
return (
<div>
<h1>Hello, React!</h1>
</div>
);
}Inside a function, we just return something that looks like HTML. HTML inside JavaScript — feels a bit odd, doesn’t it? In this chapter we look at exactly what this syntax — JSX — is, how it works, and the basic rules to internalize.
What is JSX? #
JSX (JavaScript XML) is an extension that adds markup similar to HTML into JavaScript. It is not part of official JavaScript syntax, but Facebook proposed it alongside the release of React, and today almost every React project uses JSX.
You can write React code without JSX. The following two snippets produce exactly the same result.
function App() {
return <h1>Hello</h1>;
}function App() {
return React.createElement('h1', null, 'Hello');
}The first one is much easier to read. That is why every React project uses JSX.
The browser does not understand JSX #
The JavaScript engine in the browser does not understand JSX directly. Code we write in JSX gets transformed into plain JavaScript at build time before reaching the browser. Vite, which we set up in Chapter 2, handles this transformation automatically, so we can write JSX freely without worrying about it.
The big picture of the transformation:
src/App.jsx (JSX)
↓ Vite (esbuild / SWC)
JS bundle (React.createElement calls)
↓ browser
DOM elementBoth key from Chapter 8 and reconciliation from Chapter 14 operate on the object tree built out of these React.createElement calls. In this chapter we focus on the syntax rules at the layer above.
The basic rules of JSX #
JSX looks similar to HTML but has a few different rules. These are where beginners stumble most often, so let us walk through them carefully.
Rule 1. Wrap everything in a single parent #
JSX is ultimately something a function returns, so it can only return one value at a time. To return multiple elements at once, you must wrap them in a single parent.
function App() {
return (
<h1>Hello</h1>
<p>Have a great day</p>
);
}function App() {
return (
<div>
<h1>Hello</h1>
<p>Have a great day</p>
</div>
);
}Wrapping in a <div> works, but there are times when an empty <div> for no reason feels wrong. For those cases, use an empty tag — a Fragment.
function App() {
return (
<>
<h1>Hello</h1>
<p>Have a great day</p>
</>
);
}<>...</> is the short form of React.Fragment. It groups multiple children without producing any element in the real DOM.
Rule 2. Every tag must be closed #
In HTML you could leave tags like <img>, <br>, and <input> unclosed. In JSX, every tag must be closed. Tags with no children get a / at the end and become self-closing.
<img src="/cat.png" alt="cat" />
<br />
<input type="text" />Rule 3. className instead of class
#
The class attribute used in HTML to assign CSS classes clashes with a JavaScript reserved word, so in JSX we use className.
<div className="card">This is a card</div>For the same reason, the for attribute on <label> becomes htmlFor.
<label htmlFor="email">Email</label>
<input id="email" type="email" />Rule 4. Attribute names are camelCase #
In HTML, attribute names were all lowercase (onclick, tabindex, readonly), but in JSX we use camelCase.
<button onClick={handleClick} tabIndex={0}>Click</button>
<input readOnly value="fixed value" />This rule shows up in earnest again in Chapter 7 (Event handling). onClick, onChange, onSubmit — all camelCase.
Embedding JavaScript expressions inside JSX #
The real power of JSX is that you can freely embed JavaScript expressions inside the markup. Every JavaScript expression inside curly braces { } is evaluated, and the result is rendered on screen.
function App() {
const name = 'Cheolsu';
const age = 30;
return (
<div>
<h1>Hello, {name}!</h1>
<p>Age: {age}</p>
<p>In 10 years: {age + 10}</p>
</div>
);
}Function calls, ternary expressions, and array methods all work too as long as they are expressions.
function App() {
const isLoggedIn = true;
const items = ['apple', 'banana', 'cherry'];
return (
<div>
<h1>{isLoggedIn ? 'Welcome!' : 'Please log in'}</h1>
<ul>
{items.map(item => <li key={item}>{item}</li>)}
</ul>
</div>
);
}if or for loops cannot go in directly. For conditional output, use the ternary operator or the && operator; for repeated output, use map. We cover these in Chapter 7 (Conditional rendering) and Chapter 8 (Lists and key) in detail.Inline styles #
To assign a style directly in JSX, pass an object. CSS property names are camelCase as well.
function App() {
return (
<h1 style={{ color: 'tomato', fontSize: '32px' }}>
A styled heading
</h1>
);
}The double curly braces look strange at first, but the outer { } means “a JavaScript expression inside JSX,” and the inner { } means “a JavaScript object literal.”
In real-world work, instead of inline styles, tools like CSS Modules / Tailwind / styled-components show up more often. This book uses inline styles in examples for readability, but design-system-level styling belongs to a separate book.
Comments inside JSX #
To leave a comment inside JSX, you need to wrap it in curly braces, since it is a JavaScript expression.
function App() {
return (
<div>
{/* This is a JSX comment */}
<h1>Title</h1>
</div>
);
}Try it yourself #
Change src/App.jsx from the project you made in Chapter 2 to the following.
function App() {
const name = 'Cheolsu';
const fruits = ['apple', 'banana', 'cherry'];
return (
<>
<h1>Hello, {name}!</h1>
<p>Today we have {fruits.length} fruits in total:</p>
<ul>
{fruits.map(fruit => <li key={fruit}>{fruit}</li>)}
</ul>
</>
);
}
export default App;On save, HMR updates the screen immediately. Try changing the variables or adding new fruits to the array, and see how the screen reacts.
className instead of class?”, and “Did I use camelCase for attributes?” in that order — that resolves most cases.Exercises #
- Add three more fruits you like to the
fruitsarray in the “Try it yourself” code, and confirm that all six show up the moment you save. Then check that thetotal N itemspart in the<p>also updates to 6 automatically. - Change the
<h1>style to use an inline style — colortomato, font size48px. Pass it as an object, likestyle={{ color: 'tomato', fontSize: '48px' }}. - Convert the
fruitsarray into an array of objects. Each item should be an object of shape{ id, name, emoji }, and inside<li>display them like{fruit.emoji} {fruit.name}. Usefruit.idaskey. Handling object arrays is the foundation of Chapter 8 (Lists and key).
In one line: JSX is a syntax that compiles down to JavaScript function calls (
React.createElement). Wrap in a parent / close every tag /className·htmlFor/ camelCase attributes — internalize those four rules. With curly braces{ }you can freely embed JavaScript expressions inside JSX.
Next chapter #
In the next chapter, Chapter 4: Components and props, we point out that functions like App from the example above are in fact React’s most important unit — components — and then look at props, the channel components use to pass data between each other. This becomes the foundation for solidifying these interfaces with TypeScript in Chapter 17 (Typing props and children).