B1 · Part 1 · Beginner
Project setup & JSX
JSX is not HTML and it is not a template language — it is a function call wearing a costume.
09On this page
Starting a project
Two commands cover almost everything you will do. Vite for a plain React app that runs entirely in the browser, and Next.js when you want routing and a server as well.
npm create vite@latest my-app -- --template react-ts
npm create next-app@latest my-app
The old create-react-app is no longer maintained. If you find a
tutorial that starts with it, the rest of the tutorial is old too.
Where the app actually starts
// main.tsx — the file every tutorial skips
import { createRoot } from "react-dom/client";
import App from "./App";
createRoot(document.getElementById("root")).render(
<StrictMode>
<App />
</StrictMode>
);
createRoot takes a real DOM node and hands React control of
everything inside it. That is the only place React and the document meet
— from here down, React owns the tree.
For a server-rendered page the call is hydrateRoot instead: the
HTML already exists, so React attaches to it rather than building it. Using
createRoot on server HTML throws it away and re-renders from
scratch, which quietly discards the whole point of rendering on the server.
<StrictMode> is development-only and adds nothing to the
production build. It deliberately double-invokes components and effects to
surface impure renders and missing cleanups — which is why your effect
runs twice locally and once in production.
What JSX actually is
This is the single most useful thing to understand on day one, because everything confusing about React later makes sense once you have it.
const el = <h1 className="title">Hello</h1>;
Before your code runs, the build step turns that into a function call:
const el = jsx("h1", { className: "title", children: "Hello" });
Which returns a plain object — roughly
{ type: "h1", props: { className: "title", children: "Hello" } }.
That object is called a React element, and it is just a description of
what you want on screen. Nothing has touched the DOM at this point.
Three things this explains
- JSX is an expression. You can put it in a variable, return it from an
if, or hold it in an array — because it evaluates to an object. - You cannot have two sibling tags at the top level, because a function returns one value.
- React elements are cheap. Creating them is making small objects, not touching the page.
The rules that trip everyone up
One root element
Wrong, and it will not compile:
return (
<h1>Title</h1>
<p>Body</p>
);
Wrap them. If you do not want a real <div> in the output, use a
fragment — the empty tag:
return (
<>
<h1>Title</h1>
<p>Body</p>
</>
);
Braces mean "back to JavaScript"
Inside JSX, { } is an escape hatch to a JavaScript
expression — something that produces a value.
const name = "Ana";
const age = 30;
<p>{name} is {age} next year: {age + 1}</p>
<p>{name.toUpperCase()}</p> // calls are fine
<p>{age > 18 ? "adult" : "minor"}</p> // ternary is fine
A statement is not. {if (x) ...} and {for (...) ...}
are syntax errors, because if and for do not produce
a value. That constraint is why React code leans on &&,
ternaries and .map() — not style, arithmetic.
Attributes are props, so they are camelCase
| HTML | JSX | Why |
|---|---|---|
class | className | class is a reserved word in JavaScript |
for | htmlFor | same reason |
onclick | onClick | they are object keys, and JavaScript is camelCase |
tabindex | tabIndex | same |
style="color: red" | style={{ color: "red" }} | an object, not a string |
The double braces in style are not special syntax. The outer pair
is "back to JavaScript", the inner pair is an object literal.
Self-closing tags are required
<img> and <br> are legal HTML. In JSX
they must be <img /> and <br />, because
the compiler needs to know where the element ends.
What React renders and what it skips
<p>{"text"}</p> // text
<p>{42}</p> // 42
<p>{null}</p> // nothing
<p>{undefined}</p> // nothing
<p>{false}</p> // nothing
<p>{[1, 2, 3]}</p> // 123
<p>{0}</p> // 0 ← not nothing
null, undefined, true and
false render nothing, which is what makes
{condition && <Thing />} work. Zero is the
exception, and it is the most common rendering bug in React —
{items.length && <List />} puts a literal
0 on the page when the list is empty.
Conditional rendering covers the fix.
Two transforms, and why old code imports React
Before React 17, JSX compiled to React.createElement(...), so
React had to be in scope — hence
import React from "react" at the top of every file, even ones
that never mentioned it. That was the classic runtime.
The automatic runtime compiles to jsx() imported from
react/jsx-runtime by the compiler itself. You no longer import
React to write JSX. You still import it for hooks, but
import { useState } from "react" is the modern form.
// classic — what you will see in older tutorials
import React from "react";
React.createElement("h1", { className: "title" }, "Hello");
// automatic — what your build actually emits today
import { jsx as _jsx } from "react/jsx-runtime";
_jsx("h1", { className: "title", children: "Hello" });
If a tutorial tells you a missing React import is why your JSX broke, it is written for a version you are not using.
Comments and whitespace
<div>
{/* a JSX comment — braces, then a block comment */}
<span>a</span> <span>b</span> // space between them: kept
<span>a</span>
<span>b</span> // newline between them: removed
<span>a</span>{" "}
<span>b</span> // {" "} puts it back
</div>
JSX strips whitespace that includes a newline at the start or end of a line.
That is why two elements on separate lines run together, and why
{" "} exists — it is an explicit space that survives
formatting. A missing space after a link is nearly always this.
The compile errors you will actually see
| Message | What it means |
|---|---|
| Adjacent JSX elements must be wrapped | Two siblings at the top level. Add a fragment. |
| Objects are not valid as a React child | You rendered an object — often a whole record instead of a field, or a Date. Render a string. |
| Unexpected token, expected "," | Usually a stray class= or an unclosed tag a few lines above. |
| Each child in a list should have a unique "key" | A .map() without keys — see lists and keys. |
| Functions are not valid as a React child | {handleClick} where you meant {handleClick()}, or a component used without angle brackets. |
Escaping is automatic
Anything you interpolate is escaped before it reaches the DOM, so
{"<img onerror=alert(1)>"} renders as visible text rather
than as a tag. That is React's main defence against XSS, and it is on by
default — the only way past it is
dangerouslySetInnerHTML, which is named that way deliberately.
Interview answer, one sentence
"JSX is syntax sugar for function calls that return plain objects describing the UI. React reads those objects and works out what to change in the DOM, which is why JSX is an expression and why the rules feel like JavaScript rules rather than HTML rules."
Opens in the editor: write it, run it, and check it against real tests.
All 46 for this level →Finished reading?
0 of 57 chapters read in this series