JSX is a function call
That is the whole of it. JSX is syntax for building an object that describes what to render — not a template, not a string, and not HTML.
<h1 className="title">Doors</h1>
// becomes
React.createElement("h1", { className: "title" }, "Doors")
Expressions in braces
<p>{door.name}</p>
<p>{door.days * 100}</p>
<p>{door.days > 7 ? "slow" : "quick"}</p>
<p>{items.map((i) => <li key={i.id}>{i.text}</li>)}</p>
<img src={door.image} alt={door.name} />
<div className={`card ${door.archived ? "is-archived" : ""}`} />
// NOT allowed: if, for, switch — they are statements
// do the work above the return instead
className="card" is the string "card". className={card} is the value of a variable called card. style="color: red" does not work at all — see below.
The differences from HTML
| HTML | JSX | Why |
|---|---|---|
class | className | class is a reserved word |
for | htmlFor | same reason |
onclick | onClick | camelCase for every event |
tabindex | tabIndex | camelCase for most attributes |
style="color:red" | style={{ color: "red" }} | an object, with camelCase keys |
<br> | <br /> | every tag must close |
| {"\u00a0"} | entities are not parsed |
data-id, aria-label | unchanged | these two keep their dashes |
One parent, or none
Use a fragment when the wrapper would break the layout — a table row, a flex or grid child, a list item.
// error: two roots
return (
<h1>Doors</h1>
<p>Two of them</p>
);
// a wrapper
return (
<div>
<h1>Doors</h1>
<p>Two of them</p>
</div>
);
// or a fragment — no element in the DOM at all
return (
<>
<h1>Doors</h1>
<p>Two of them</p>
</>
);
// the long form, when you need a key
<React.Fragment key={id}>…</React.Fragment>
Whitespace and text
// newlines and indentation between elements are removed
<p>
{door.name}
{door.days}
</p> // "Painted5" — no space
<p>
{door.name} {door.days}
</p> // "Painted 5" — a space on the same line
<p>{door.name}{" "}{door.days}</p> // explicit, and survives a formatter
// a comment
{/* this is a JSX comment */}
Conditional rendering
React renders 0 because it is a valid child. false, null and undefined render nothing. Always make the left side a boolean.
{isOpen && <Panel />} // and
{isOpen ? <Panel /> : <Placeholder />} // ternary
{isOpen ? <Panel /> : null}
// THE TRAP: && with a number
{items.length && <List items={items} />}
// when items is empty this renders "0" on the page
{items.length > 0 && <List items={items} />} // a real boolean
Escaping is automatic
Every value in braces is escaped. That single behaviour removes the commonest security bug on the web — which is also why the escape hatch is named so awkwardly.
<p>{userText}</p>
// "<img src=x onerror=…>" appears as TEXT, whatever it contains
// the only way to inject markup, and the name is a warning
<p dangerouslySetInnerHTML={{ __html: trustedHtml }} />
Children and composition
function Panel({ title, children }) {
return (
<section className="panel">
<h2>{title}</h2>
{children}
</section>
);
}
<Panel title="Specification">
<p>Aluminium frame.</p>
<p>1 mm gap.</p>
</Panel>
// several slots: pass elements as props
<Layout sidebar={<Nav />} main={<Article />} />