React120 min total · 17 parts
React Fundamentals: Components, Hooks, and the Virtual DOM
Part 1 of 17 · ~3 min
Overview
Here's React's whole idea, in one line: you declare what a given piece of state ought to produce on the screen, and React works out how to make the actual page match your description. That half of the deal is genuinely easy, and it is not where anybody gets stuck.
Where people get stuck is when things run. Which function runs again, and how many times. Which values it can still see when it does. What React tears down versus quietly reuses. None of that is visible in the code you wrote, and all of it decides whether your screen is correct.
There is a reason that stays hard longer than it should. You learn useState from a counter, useEffect from a document.title, useMemo from a sorted array of numbers, and refs from an input that focuses itself. Each one makes sense in its nine-line demo. Then you open a real screen, where all of them are firing in the same component at the same time and interfering with each other, and the tidy separate demos stop being any help at all.
So we are not going to do it that way.
Instead, we are going to build one screen, together, from here to the end: a candidate review dashboard. A hiring manager opens it, sees everyone who has submitted a take-home, filters down to the ones worth looking at, clicks a row, reads the submission, leaves notes, and moves that person forward or out. It is an unglamorous piece of software and almost every application has one — a data table, a filter bar, a detail panel, a form. That is exactly why it works here: building it properly, rather than as a demo, forces you through every mechanism in this reference, in roughly the order you would hit them.
Here is where we start. It renders:
function Dashboard() {
const [candidates, setCandidates] = useState([]);
useEffect(() => {
fetch("/api/candidates")
.then((res) => res.json())
.then(setCandidates);
}, []);
return (
<table>
<tbody>
{candidates.map((c) => (
<tr>
<td>{c.name}</td>
<td>{c.score}</td>
<td>{c.stage}</td>
</tr>
))}
</tbody>
</table>
);
}
Twenty lines, and it works in the way a cardboard prototype works. It also contains at least four real bugs already, and by the last chapter you will be able to name all four without anyone pointing at them.
By then this screen will filter and sort eight hundred rows without the typing stuttering, keep a half-written note attached to the right person when the sort order changes underneath it, stop a slow response for one candidate from overwriting a fast response for another, survive a single malformed record instead of going blank, and load its heavy submission viewer only for the people who actually open one.
Each chapter takes one bite out of the distance between those two versions, and the React mechanism that closes the gap is that chapter's subject.
Read it front to back if you can — the dashboard genuinely accumulates, and more than one chapter fixes a bug an earlier chapter left sitting there deliberately. If you already know what you are looking for, the sidebar drops you straight in; every chapter names which version of the dashboard it is working on, so you can pick up the thread from anywhere.
One piece of vocabulary before we start, so the code stays readable. This is the shape of a candidate, exactly as the API hands it over:
{
id: "c_8814",
name: "Priya Raghunathan",
role: "Backend",
score: 82, // 0–100, or null if their submission timed out
stage: "screening", // "screening" | "shortlisted" | "rejected" | "hired"
submittedAt: "2026-03-02T09:14:00Z",
}
Every score, stage and candidate from here on is that object. The null score is not decoration — it blanks the entire dashboard in chapter 14.