React Reference
React is a library for building interfaces out of components. A component is a function that returns markup. When the data it draws from changes, React redraws it.
Starting a project — lesson 14
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev # then open the printed http://localhost:5173
Those four lines create a new project. A lesson's folder already holds one, so a lesson starts at
npm install, typed in a terminal opened from that folder; workflow.md says how. npm install is
needed once per project folder, and node_modules/ is never submitted.
| Path | What it holds |
|---|---|
index.html |
The one real HTML page. You rarely edit it |
src/main.jsx |
Mounts the app into that page. You rarely edit it |
src/App.jsx |
The top component. You edit this |
src/components/ |
One file per component. You edit these |
public/ |
Images and files served as-is |
A component — lesson 14
// src/components/Greeting.jsx
export default function Greeting() {
return (
<section className="greeting">
<h2>Hello</h2>
</section>
);
}
// src/App.jsx
import Greeting from "./components/Greeting.jsx";
export default function App() {
return (
<main>
<Greeting />
</main>
);
}
- A component's name starts with a capital letter. A lowercase tag is read as an HTML element, so it draws nothing, and only the browser's console warns about it.
export defaultmarks the one main thing a file exports. It is imported without braces, under any name; a named export, such asexport { total }, is imported with braces.- The round brackets after
returnlet the markup start on the next line. - It returns one top-level element. Wrap siblings in a
<>…</>fragment. - Every tag closes:
<img />,<br />. This rule and the one above stop the build when broken. classisclassName, andforishtmlFor, because both are JavaScript keywords.classstill works, but React reports it in the browser's console.- JSX belongs in a
.jsxfile.
JSX — putting values in markup
const name = "Ada";
const items = ["a", "b"];
<h1>{name}</h1> {/* a value */}
<p>{2 + 2}</p> {/* any expression */}
<img src={url} alt="" /> {/* an attribute */}
<div className={isOpen ? "open" : ""}> {/* a choice */}
{isOpen && <p>Only when open</p>} {/* show conditionally */}
{} holds an expression, not a statement. An if goes above the return.
Props — passing data down — lesson 14
Props go parent to child, and are read-only inside the child. A value in quotes is passed as a string, and any other value goes in braces.
// parent
<PersonCard name="Ada" born={1815} />
// child
export default function PersonCard({ name, born }) {
return <article><h3>{name}</h3><p>Born {born}</p></article>;
}
A default value, written { name, retired = false }, is used when the parent leaves that prop out.
A boolean is passed as retired={true} or as retired on its own; retired="true" passes a
string, and every non-empty string is truthy.
State — lesson 15
State is a value the component redraws itself from. Changing it with set… is what triggers the
redraw; assigning to a plain variable does nothing visible.
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0); // [value, setter] = useState(initial)
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
| Rule | Why |
|---|---|
Never assign to state directly (count = 5) |
count is a const, so the assignment stops with TypeError: Assignment to constant variable.; only the setter redraws |
| Call hooks at the top level of the component | Never inside an if or a loop |
| Replace arrays and objects, never mutate them | setList([...list, item]), not list.push(item) |
Events and controlled inputs — lesson 15
const [query, setQuery] = useState("");
function handleSubmit(event) {
event.preventDefault(); // stops the page reloading
setQuery("");
}
<form onSubmit={handleSubmit}>
<input value={query} onChange={(event) => setQuery(event.target.value)} />
<button type="submit">Search</button>
</form>
onClick={handleClick} and onSubmit={handleSubmit} pass the function, and React calls it when the
event happens. onClick={handleClick()} calls it while the component is drawn; if it sets
state, React stops with Too many re-renders. An input with value and no onChange does not
accept typing. A checkbox is controlled the same way, with checked={…} in place of value.
Rendering a list — lesson 15
const clubs = [
{ id: 31, name: "Chess club" },
{ id: 32, name: "Book club" }
];
<ul>
{clubs.map((club) => (
<li key={club.id}>{club.name}</li>
))}
</ul>
key goes on the outermost element the map returns, and must be unique and unchanging, so it is
the item's id. The array index works only if the list is never reordered or filtered. An item added
while the page runs can take Date.now() as its id.
Child to parent — lesson 15
A child cannot change a parent's state. The parent passes a function down; the child calls it.
// parent
const [booked, setBooked] = useState(0);
function bookSeat() {
setBooked(booked + 1);
}
<BookButton onBook={bookSeat} />
// child
export default function BookButton({ onBook }) {
return <button onClick={onBook}>Book a seat</button>;
}
A button drawn once per item passes that item's value through a new arrow function:
onClick={() => chooseRoom(room.name)}. When two components need the same value, move it up to
their nearest shared parent.
Effects and data — lesson 16
An effect runs code after the render: saving, fetching, starting a timer. The function given to
useEffect is not async; an async function is declared inside it and called.
import { useState, useEffect } from "react";
export default function OpeningHours({ branch }) {
const [hours, setHours] = useState("");
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
async function fetchHours() {
try {
const response = await fetch(`/hours-${branch}.json`);
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const opening = await response.json();
if (!cancelled) {
setHours(opening.text);
}
} catch (error) {
if (!cancelled) {
setHours("The hours could not be loaded.");
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchHours();
return () => {
cancelled = true; // the cleanup: a late answer for an old branch is ignored
};
}, [branch]); // runs again when branch changes
if (loading) {
return <p>Loading the hours…</p>;
}
return <p>{hours}</p>;
}
| Dependency array | When the effect runs |
|---|---|
[] |
Once, after the first render |
[branch] |
After the first render, and whenever branch changes |
| left out | After every render; an effect that sets state then runs without end |
fetch throws only when no answer comes back. A server's "not found" is an answer, so test
response.ok. A file in the project's public folder is fetched as "/" and its name.
Saving between visits
The calls to localStorage live in their own module, so a component never uses it directly and a
missing or broken saved value is handled in one place. localStorage stores only text.
// settings.js
export function saveBranch(branch) {
localStorage.setItem("branch", JSON.stringify(branch));
}
export function loadBranch() {
const text = localStorage.getItem("branch"); // null when nothing is saved
if (text === null) {
return null;
}
try {
return JSON.parse(text);
} catch (error) {
return null; // the saved text was not JSON
}
}
// App.jsx: declare the state first, then the effect that reads it
const [branch, setBranch] = useState(() => loadBranch() ?? "Riverside");
useEffect(() => {
saveBranch(branch);
}, [branch]);
useState(() => …) is a function initialiser, which runs only on the first render. a ?? b gives
b only when a is null or undefined. Array.isArray(value) is true only for an array, for
a saved value that must be a list.
Not taught in class — Level 4 material
| Feature | What it does |
|---|---|
useRef |
A value that survives renders without causing one |
useContext |
Share a value without passing props through every level |
useReducer |
State transitions as a single named function |
| Custom hooks | Your own useSomething() that reuses hook logic |
| React Router | Multiple pages in one app |
useMemo / useCallback |
Skip work that has not changed |
setCount((c) => c + 1) |
A setter given a function receives the current value, so several updates in one event do not overwrite each other |