Section 1 · Getting Started
JSX Rules of the Road
17 min3 min readCode playground
Lesson at a glance
Read and write JSX without fighting the compiler by knowing what it compiles to, how curly braces work, and which attribute and rendering rules differ from HTML.
- 1JSX is function calls
- 2Curly braces hold expressions
- 3Attributes are JavaScript names
- 4Close every tag
- 5What can be rendered
No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.
JSX looks like HTML, so your first instinct is to write HTML. Most of the time that works. Then you write class="card" and get a console warning, or put an if inside curly braces and get a compile error that points at the wrong line. Every one of these surprises makes sense once you see what JSX really is.
JSX is function calls
Vite compiles each JSX tag into a function call. This component:
function MovieCard() {
return <h2 className="title">Arrival</h2>;
}compiles to roughly this:
import { jsx } from 'react/jsx-runtime';
function MovieCard() {
return jsx('h2', { className: 'title', children: 'Arrival' });
}The call returns a plain JavaScript object, a React element: a lightweight description of what should be on screen, with a type and some props. You can model the idea with a tiny function of your own:
function h(type, props, ...children) {
return { type, props: { ...props, children } };
}
const card = h('article', { className: 'card' },
h('h2', null, 'Arrival'),
h('p', null, 2016),
);
console.log(card.type); // article
console.log(card.props.className); // card
console.log(card.props.children[0].props.children); // ["Arrival"]React’s real elements carry a little more information, but the shape is the same. React walks this tree of objects and creates or updates the DOM to match.
Notice that the compiled code imports jsx for you. That’s why modern React files don’t start with import React from 'react'. Older tutorials include that line because the previous compiler turned JSX into React.createElement(…) calls, which needed React in scope. Since React 17’s automatic JSX runtime it’s unnecessary, so leave it out.
Once you know JSX is function calls, the rules below stop feeling arbitrary.
Curly braces hold expressions
Inside JSX, { } switches back to JavaScript. Whatever goes inside becomes an argument to a function call, so it must be an expression: something that produces a value.
function MovieCard() {
const movie = { title: 'Arrival', year: 2016, rating: 8 };
const age = 2026 - movie.year;
return (
<article>
<h2>{movie.title.toUpperCase()}</h2>
<p>{movie.year} · {age} years old</p>
<p>{movie.rating >= 8 ? 'Must watch' : 'Maybe'}</p>
</article>
);
}Variables, property access, method calls, arithmetic and the ternary operator a ? b : c are all expressions. if, for and const are statements, so they can’t go inside braces, in the same way you can’t pass if (x) {} as a function argument. When the logic gets bigger, compute a variable above the return and use that.
Here’s the same card with a label that needs real branching. The if lives in plain JavaScript above the return, and JSX only receives the finished value:
function MovieCard({ movie }) {
let label;
if (movie.rating >= 9) {
label = 'Masterpiece';
} else if (movie.rating >= 7) {
label = 'Must watch';
} else {
label = 'Maybe';
}
return <p>{movie.title}: {label}</p>;
}This pattern scales. Your JSX stays a readable picture of the output, and the decisions sit in ordinary code you can read, test and debug like any other function. (The { movie } in the parameter list is a prop; you’ll learn props properly in the next section.)
Attributes take braces too: <img src={movie.poster} alt={movie.title} />. Don’t wrap braces in quotes; src="{poster}" is the literal string {poster}.
Attributes are JavaScript names
Because props become keys of a JavaScript object, they follow JavaScript naming:
class→className,for→htmlFor(both are reserved words in JS).- Multi-word attributes are camelCase:
onClick,tabIndex,autoComplete,maxLength. aria-*anddata-*attributes keep their dashes:aria-label="Remove",data-id="42".styletakes an object, with camelCase CSS properties:style={{ marginTop: 8 }}. A bare number means pixels for most properties. The double braces are an object literal inside a JSX expression.
Close every tag
JSX is stricter than HTML. Every element closes: <img />, <input />, <br />. A component returns one root, wrapped in a fragment <>…</> if you need siblings. Comments go inside braces: {/* like this */}.
What can be rendered
| You put in braces | What appears |
|---|---|
| A string or number | The text (0 included) |
| A React element | That element |
| An array of elements | Each element in order |
null, undefined, true, false |
Nothing |
| A plain object | Error: “Objects are not valid as a React child” |
The “nothing” row is what makes conditional rendering work, and the 0 in the first row is a classic trap you’ll meet in the Conditional Rendering lesson.
You can now read any JSX and predict what it does. In the next section you’ll stop hard-coding “Arrival” and learn to think in components, splitting a whole screen into pieces that receive their data through props.
Key takeaways
- JSX compiles to function calls that create plain JavaScript objects describing the UI; React turns those objects into DOM.
- Curly braces embed any JavaScript expression (a value), but never a statement such as
iforfor. - Attributes are camelCase JavaScript names, so you write
className,htmlFor,onClickand passstylean object. - Strings and numbers render as text, while
null,undefined,trueandfalserender nothing and plain objects throw.