JSX — JavaScript + HTML Rules
HTML and JavaScript in the Same File
JSX looks like HTML but compiles to JavaScript function calls — which is exactly why it has its own small set of rules that differ from writing plain HTML.
JSX Syntax Rules
.map(x => <div key={x.id}>) — React uses it to track which items changed, were added, or removed.function Profile({ name, age, isAdmin }) {
return (
<div className="profile">
{/* Conditional rendering */}
{isAdmin && <span>Admin</span>}
{age >= 18 ? <span>Adult</span> : <span>Minor</span>}
{/* Dynamic style */}
<h2 style={{ color: isAdmin ? "gold" : "white" }}>
{name}
</h2>
{/* Lists */}
{["Python","React"].map((skill, i) => (
<span key={i}>{skill}</span>
))}
</div>
);
}
JSX is not HTML — it is a function call wearing a costume
Every rule on this page follows from one fact: the browser never sees your JSX. A build step rewrites it into ordinary JavaScript first. This:
<h1 className="title">Hello</h1>
becomes this before it ever runs:
React.createElement("h1", { className: "title" }, "Hello");
Read that line again and the rules stop being arbitrary. A function can only return
one thing — hence one parent element. Its second argument is a plain
JavaScript object — so the keys follow JavaScript's naming, not HTML's, which is why
class becomes className and onclick becomes
onClick. And { } is simply where you stop writing markup and
start writing JavaScript again.
One parent element
Press Run JSX on the broken version. The error you get is the one you will meet most often in your first week of React — it is worth recognising on sight.
function App() {
return (
<h2>Title</h2>
<p>Body text</p>
);
}
<>...</> when you do not want an extra <div>
in the finished page.function App() {
return (
<>
<h2>Title</h2>
<p>Body text</p>
</>
);
}
Curly braces hold an expression, never a statement
This is the rule that catches people who have understood everything else. Inside
{ } React needs something that evaluates to a value. An
if statement does not evaluate to anything, so it cannot go there.
function App() {
const hour = new Date().getHours();
const items = ["React", "Props", "State"];
return (
<div>
<h3>{hour < 12 ? "Good morning" : "Good afternoon"}</h3>
{items.length > 0 && <p>{items.length} topics today</p>}
<ul>
{items.map(function (name) {
return <li key={name}>{name}</li>;
})}
</ul>
</div>
);
}
if, loops, variables — anywhere
before return. Work the answer out there, put the finished value in the braces.
Nested ternaries inside markup are where React code becomes unreadable.Why key matters, and why the index is a poor one
A key tells React which item in a list is which between renders. Use the array index and you are telling React "the item in position 0 is the same item it was before" — which stops being true the moment the list is reordered or something is removed from the front.
function App() {
const [rows, setRows] = React.useState([
{ id: "a", label: "Row A" },
{ id: "b", label: "Row B" },
{ id: "c", label: "Row C" }
]);
return (
<div>
<p>Type something into the first box, then remove the first row.</p>
<button onClick={function () { setRows(rows.slice(1)); }}>
Remove first row
</button>
<h4>Keyed by index — the text stays behind</h4>
{rows.map(function (r, i) {
return <div key={i}>{r.label} <input placeholder="type here" /></div>;
})}
<h4>Keyed by id — the text follows its row</h4>
{rows.map(function (r) {
return <div key={r.id}>{r.label} <input placeholder="type here" /></div>;
})}
</div>
);
}
Both lists hold the same data. Only the keys differ, and the behaviour is not the same — that is the whole argument for using a stable id.
Three things worth getting wrong on purpose
className back to class in any example above and run it.
React warns rather than crashing — find the warning and read it.{ if (x) { ... } } and watch the compile fail.
Then move the if above the return and make it work.key={r.id} from the list example. The page still renders —
which is exactly why this bug reaches production.