📖 Notes LESSON 3 OF 18

JSX — JavaScript + HTML Rules

⏱️ 20 min
⚛️ React

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

1️⃣
One Parent Element
Return one parent element, or use a Fragment <>...</>.
2️⃣
className
Use className instead of class ("class" is reserved in JS).
3️⃣
Self-Closing Tags
Empty tags must self-close: <img />, <br />.
4️⃣
Curly Braces
JavaScript expressions go inside { }.
5️⃣
camelCase Events
onClick, onChange — not onclick, onchange.
6️⃣
Double-Brace Style
style={{ color: "red" }} — an object inside an expression.
⚠️
Rule 7 — the key prop
Arrays of elements need a unique "key" prop: .map(x => <div key={x.id}>) — React uses it to track which items changed, were added, or removed.
All Rules in One Component
JSX
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:

What you write
JSX
<h1 className="title">Hello</h1>

becomes this before it ever runs:

What actually runs
JS
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.

Broken — two elements returned
JSX
function App() {
  return (
    <h2>Title</h2>
    <p>Body text</p>
  );
}
⚠️
Adjacent JSX elements must be wrapped in an enclosing tag
You asked one function to return two values. It cannot. Wrap them in a parent, or in a Fragment <>...</> when you do not want an extra <div> in the finished page.
Fixed — a Fragment adds no extra markup
JSX
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.

Works — a ternary is an expression
JSX
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>
  );
}
💡
When the logic gets long, move it above the return
You are allowed ordinary JavaScript — 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.

Type in a box, then press Remove first
JSX
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

1️⃣
Predict the error
Change className back to class in any example above and run it. React warns rather than crashing — find the warning and read it.
2️⃣
Break the braces
Replace a ternary with { if (x) { ... } } and watch the compile fail. Then move the if above the return and make it work.
3️⃣
Drop a key
Delete key={r.id} from the list example. The page still renders — which is exactly why this bug reaches production.
🗒 Cheat Sheet 📝 Worksheet