# React from Zero - The UI Library, Finally Explained

> What React actually is, why it re-renders, and how components, state, and effects fit together - taught from the mental model up, not from boilerplate down.


---

# React from Zero - The UI Library, Finally Explained

You've seen React on every job posting. Maybe you've copied a component from a tutorial, changed a
line, and watched the whole thing break with an error about hooks or keys that meant nothing to you.
That's not a you problem. Most React teaching starts with boilerplate and vocabulary instead of the
one idea the entire library is built on. This guide starts with that idea, and everything else - state,
props, effects, keys - falls out of it in order.

By the end you'll be able to read a React codebase and know *why* it's shaped the way it is, build
components that don't fight you, and recognize the five errors every React developer meets in their
first month before they cost you an afternoon.

## How to read this

- **In a panic right now?** Jump to [Phase 8: When React Breaks](08-when-it-breaks.md) and use the
  cheat-card at the top.
- **Want it to finally make sense?** Read in order - each phase builds on the last, and the first one
  is the foundation everything else stands on.

## The phases

1. **[What React Actually Is](01-what-react-actually-is.md)** - the one idea under everything:
   your UI is a function of your data.
2. **[Components and Props](02-components-and-props.md)** - functions that return descriptions of
   UI, and how data flows between them.
3. **[State and Re-renders](03-state-and-re-renders.md)** - `useState`, why changing state redraws
   the screen, and why React insists you never mutate.
4. **[Lists, Keys, and Conditional Rendering](04-lists-keys-and-conditional-rendering.md)** - showing
   many things, showing things sometimes, and what that `key` warning is really about.
5. **[Events and Forms](05-events-and-forms.md)** - handling clicks and keystrokes, and the
   controlled-input pattern that confuses everyone once.
6. **[Effects](06-effects.md)** - `useEffect` is for talking to the world outside React, and almost
   nothing else.
7. **[Sharing State](07-sharing-state.md)** - lifting state up, context, and when prop drilling is
   actually fine.
8. **[When React Breaks](08-when-it-breaks.md)** - the classic errors ("too many re-renders", stale
   state, missing keys), what each one means, and the calm fix.
9. **[Where to Go Next](09-where-to-go-next.md)** - the ecosystem without the hype: what to learn
   next and what to ignore for now.

> Deliberately deferred to follow-up guides: server-side rendering and Next.js, performance tuning
> (`memo`, `useMemo`, profiling), advanced patterns (reducers, portals, suspense), and testing. This
> guide makes the core model solid; those build on it.


---

# What React Actually Is

Before React, building a web interface meant writing two kinds of code: code that draws the page the
first time, and code that *updates* it afterward. The second kind is where projects went to die. Every
new feature meant finding every element that might need to change, in every situation, and updating
each one by hand. Miss one and the page silently shows stale data. If you've ever debugged a page
where the counter says 3 but the list shows 4 items, you've met this bug.

React exists to delete that entire category of bug. Here's the one idea the whole library is built on:

💡 **Key point:** In React, you never update the screen. You update your **data**, and React redraws
the screen *from* the data. The UI is a function of the state: give it the same data, you get the same
screen, every time.

Hold onto that sentence. Components, hooks, props, keys - every React concept in this guide is a
consequence of it.

## The problem, concretely

Say you're building a shopping cart badge with plain JavaScript:

```js
let cartCount = 0;

function addToCart(item) {
  cartCount = cartCount + 1;
  // Now update every place the count appears... and don't miss one.
  document.querySelector('#cart-badge').textContent = cartCount;
  document.querySelector('#checkout-button').textContent = `Checkout (${cartCount})`;
  if (cartCount === 1) {
    document.querySelector('#empty-cart-message').style.display = 'none';
  }
}
```

**Why this falls apart.** The data (`cartCount`) and the screen are two separate things you must keep
in sync *manually*. Three places today, seven places after next sprint. The empty-cart message needs
to come *back* when the cart empties - did you remember to write that code too? Every possible
transition between UI states is your job to handle, and the number of transitions grows much faster
than the number of states.

## The React version of the same thing

```jsx
function CartHeader({ count }) {
  return (
    <header>
      <span id="cart-badge">{count}</span>
      <button>Checkout ({count})</button>
      {count === 0 && <p>Your cart is empty.</p>}
    </header>
  );
}
```

There is no update code. None. This function describes what the header looks like *for any value of
`count`* - zero, one, or a thousand. When the count changes, React calls the function again with the
new value and makes the real page match the new description. The empty-cart message appears and
disappears correctly forever, because it's derived from the data instead of toggled by hand.

**Why this saves you later.** The stale-UI bug class is gone. There is no screen state to forget to
update, because the screen is never updated directly - it's recomputed. Your job shrinks to: keep the
data right.

## What JSX actually is

That HTML-looking syntax inside JavaScript is called **JSX**, and it trips people up until they learn
one fact: it's not HTML, and it's not a template language. It compiles to plain function calls.

```jsx
// What you write:
const badge = <span className="badge">{count}</span>;

// What it becomes after the build tool compiles it:
const badge = createElement('span', { className: 'badge' }, count);
```

📝 **Terminology:** the thing `createElement` returns is a **React element** - a small plain
JavaScript object like `{ type: 'span', props: { className: 'badge', children: 3 } }`. It's a
*description* of a piece of UI, not the UI itself. Cheap to create, cheap to throw away.

That explains the JSX rules that otherwise feel arbitrary:

- `className` instead of `class` - because you're writing JavaScript, and `class` is a reserved word.
- `{count}` in curly braces - that's a real JavaScript expression being passed as an argument, not a
  template placeholder.
- One root element per return - a function returns one value, so a component returns one element
  (which can have unlimited children).

## How React makes the browser match

So a component returns a description. What does React *do* with it?

```mermaid
flowchart LR
  D[your data] --> C[component function]
  C --> N[new description]
  N --> DIFF{compare with previous}
  DIFF --> P[minimal DOM patches]
  P --> B[browser screen]
```

Each time your data changes, React calls your component again and gets a fresh description of the
whole UI. Then it **compares** that description with the previous one and applies only the
differences to the real page. If only the badge number changed, only the badge's text node is
touched - the rest of the page is left alone.

📝 **Terminology:** you'll hear this comparison called the **virtual DOM** or **reconciliation**.
Strip the mystique: React keeps the previous description in memory, diffs it against the new one, and
patches the real DOM minimally. That's the entire trick.

⚠️ **Gotcha:** "re-render" does *not* mean the browser repaints everything. It means React re-runs
your component functions to get fresh descriptions. Re-running a function that returns a small object
is cheap - it happens constantly in normal React apps and is almost never your performance problem.
Don't fear re-renders; understand them.

## Seeing it for real

You don't need a build setup to prove any of this, but for real work you'll use one. The standard
starter is Vite:

```console
$ npm create vite@latest my-app -- --template react
$ cd my-app
$ npm install
$ npm run dev

  VITE ready in 512 ms

  ➜  Local:   http://localhost:5173/
```

*What just happened:* Vite scaffolded a tiny project (an `index.html`, a `main.jsx`, an `App.jsx`),
installed React, and started a dev server that recompiles your JSX on every save. Open the local URL
and you're looking at your `App` component, rendered.

The whole app boots from one call in `main.jsx`:

```jsx
import { createRoot } from 'react-dom/client';
import App from './App.jsx';

createRoot(document.getElementById('root')).render(<App />);
```

*What just happened:* React took control of one DOM node (`#root`) and rendered your top-level
component into it. From here on, everything inside that node is React's to manage - you'll never
call `document.querySelector` to change it again.

## Recap

1. The core idea: UI is a function of state. You change data; React redraws from the data.
2. JSX is not HTML - it compiles to function calls that produce cheap description objects.
3. On every data change React re-runs your components, diffs the new description against the old
   one, and patches the real DOM minimally.
4. "Re-render" means "re-run the function", not "repaint the page" - it's cheap and normal.

A quick check before the next phase - these two ideas carry the entire guide:

```quiz
[
  {
    "q": "In React, how does the screen get updated when something changes?",
    "choices": [
      "You call DOM methods to change the elements that need updating",
      "You change your data, and React redraws the UI from the new data",
      "React watches the DOM for changes and syncs your data to match",
      "The browser re-runs your whole script from the top"
    ],
    "answer": 1,
    "why": [
      "That's the plain-JavaScript approach React exists to replace - manual DOM updates are exactly the bug factory this phase opened with.",
      null,
      "It's the reverse: data is the source of truth and the DOM follows it, never the other way around.",
      "Nothing re-runs your whole script; React re-runs your component functions and patches only what changed."
    ],
    "explain": "You update state, React re-runs your components and makes the DOM match the new description."
  },
  {
    "q": "What does JSX like <span>{count}</span> compile to?",
    "choices": [
      "An HTML string that gets inserted with innerHTML",
      "A template that the browser natively understands",
      "A function call returning a plain object describing the element",
      "Direct DOM manipulation code"
    ],
    "answer": 2,
    "why": [
      "React never builds HTML strings from your JSX - that would throw away the ability to diff and patch minimally (and would be an XSS hazard).",
      "Browsers can't parse JSX at all - that's why a build step exists.",
      null,
      "The compiled code creates descriptions; React separately decides what DOM operations those descriptions require."
    ],
    "explain": "JSX compiles to createElement calls that return cheap description objects - React elements."
  }
]
```


---

# Components and Props

Every React app, from a to-do list to Facebook, is built from one kind of piece: the component.
There's no second mechanism to learn later. Once you can read a component and see how data enters and
leaves it, you can read any React codebase, because that's all a React codebase is - components
handing data to other components.

## What a component actually is

**What it actually is.** A JavaScript function whose name starts with a capital letter and which
returns JSX. That's the whole definition.

```jsx
function Greeting() {
  return <p>Welcome back.</p>;
}
```

**What it does in real life.** Once defined, it becomes a tag you can use inside other components'
JSX: `<Greeting />`. When React renders that tag, it calls your function and slots the returned
description into the tree at that spot.

⚠️ **Gotcha:** the capital letter is not a style preference - it's how JSX tells your components
apart from HTML tags. `<greeting />` compiles to a request for a built-in element named "greeting"
(which doesn't exist, so you get nothing). `<Greeting />` compiles to a call to your function. A
lowercase component name is one of the quietest bugs in React: no error, just a component that never
appears.

## Props: the component's arguments

A component that always renders the same thing isn't worth much. **Props** make components
reusable - they're the function's arguments, passed in JSX the way attributes are written in HTML.

```jsx
function Greeting({ name, unreadCount }) {
  return (
    <p>
      Welcome back, {name}. You have {unreadCount} unread messages.
    </p>
  );
}

function App() {
  return (
    <main>
      <Greeting name="Ada" unreadCount={3} />
      <Greeting name="Grace" unreadCount={0} />
    </main>
  );
}
```

*What just happened:* React called `Greeting` twice, once with `{ name: 'Ada', unreadCount: 3 }` and
once with `{ name: 'Grace', unreadCount: 0 }`. All the props arrive as a single object (the first
argument), which is why you'll almost always see it destructured right in the parameter list.

Two syntax details that bite newcomers:

- **Strings** can be passed with plain quotes: `name="Ada"`. **Everything else** - numbers, booleans,
  arrays, objects, functions - needs curly braces: `unreadCount={3}`, `onSave={handleSave}`.
  `unreadCount="3"` passes the *string* `"3"`, and one day `"3" + 1` gives you `"31"` on screen.
- **`children`** is a prop with special syntax. Whatever you nest between a component's opening and
  closing tags arrives as `props.children`:

```jsx
function Card({ title, children }) {
  return (
    <section className="card">
      <h2>{title}</h2>
      {children}
    </section>
  );
}

// Used like:
<Card title="Danger zone">
  <p>Deleting your account is permanent.</p>
  <button>Delete</button>
</Card>
```

This is React's composition mechanism: `Card` owns the frame, callers own the contents. It's how you
build one Card, one Modal, one Layout and reuse them everywhere without them needing to know what
they'll contain.

## Props flow one way: down

Here is the rule that makes React apps debuggable at scale:

💡 **Key point:** data flows **down** the tree, from parent to child, through props. A child cannot
reach up and change its parent's data. When a child needs to *tell* the parent something, the parent
hands it a function as a prop, and the child calls it.

```jsx
function App() {
  function handleBuy(productId) {
    // the parent decides what buying means
  }
  return <ProductCard id="p-101" name="Mechanical keyboard" onBuy={handleBuy} />;
}

function ProductCard({ id, name, onBuy }) {
  return (
    <article>
      <h3>{name}</h3>
      <button onClick={() => onBuy(id)}>Buy</button>
    </article>
  );
}
```

*What just happened:* the data (`id`, `name`) went down as props; the event ("the user wants to buy")
went up as a function call. `ProductCard` doesn't know or care what buying does - it just reports.

**Why this saves you later.** When a value on screen is wrong, there's exactly one direction to
look: up the tree, following the prop until you find where the value was born. In two-way systems,
a wrong value could have been written by anyone from anywhere, and you get to interrogate every
suspect. One-way flow turns "who changed this?" from an investigation into a walk.

```mermaid
flowchart TD
  App -->|"props: name, onBuy"| ProductCard
  ProductCard -->|"calls onBuy(id)"| App
```

## Props are read-only

Inside a component, props are not yours to change:

```jsx
function Greeting({ name }) {
  name = name.toUpperCase(); // legal JavaScript, wrong React
  return <p>Hello {name}</p>;
}
```

Why the fuss? Remember phase 1: a component should return the same UI for the same inputs, every
time - that's what lets React re-run it freely. A component that edits its inputs is a component
whose output depends on *how many times it ran*. If you need a transformed value, compute a new one:

```jsx
function Greeting({ name }) {
  const displayName = name.toUpperCase();
  return <p>Hello {displayName}</p>;
}
```

Same result, but `name` still means what the parent sent, all the way through. This habit - derive,
don't overwrite - is the same immutability discipline that state will demand in the next phase, so
it's worth building now while the stakes are low.

## Recap

1. A component is a capitalized function returning JSX; `<Greeting />` means "call this function
   here."
2. Props are the arguments, passed as one object; non-string values need `{curly braces}`.
3. `children` is the composition prop: the frame owns the layout, the caller owns the contents.
4. Data flows down as props; events flow up as callback props. One-way flow is why debugging scales.
5. Props are read-only - derive new values, never overwrite what the parent sent.

```quiz
[
  {
    "q": "A component renders <profile /> (lowercase) and nothing appears, with no error. Why?",
    "choices": [
      "The component file wasn't imported correctly",
      "Lowercase tags are treated as HTML elements, so React never calls the Profile function",
      "Props are missing, so React skips rendering",
      "JSX requires self-closing tags to be capitalized only in strict mode"
    ],
    "answer": 1,
    "why": [
      "A missing import is a loud error (an undefined reference), not silence - the quiet failure is the lowercase tag.",
      null,
      "Missing props render just fine (they arrive as undefined); they don't make React skip a component.",
      "The capitalization rule is core JSX compilation, not a strict-mode behavior."
    ],
    "explain": "Capitalization is how JSX distinguishes your components from built-in tags - lowercase compiles to a DOM element lookup, not a function call."
  },
  {
    "q": "A child component needs to notify its parent that the user clicked Save. What's the React way?",
    "choices": [
      "The child modifies a shared global variable the parent checks",
      "The child changes its props to signal the parent",
      "The parent passes a function down as a prop and the child calls it",
      "The child re-renders the parent directly"
    ],
    "answer": 2,
    "why": [
      "Globals reintroduce exactly the who-changed-this debugging problem one-way flow exists to prevent.",
      "Props are read-only and flow downward - a child writing its props breaks the model and React doesn't propagate it anywhere.",
      null,
      "No component can render another component - each one only returns its own description."
    ],
    "explain": "Data down, events up: the parent decides what saving means and hands the child an onSave function to call."
  }
]
```


---

# State and Re-renders

Props come from outside. But some data is *born* inside a component: is the dropdown open, what has
the user typed, which tab is selected. That's **state**, and it's where React goes from "template
library" to "the thing running your app." It's also where the first real confusion lives, so this
phase moves slowly and names every trap.

## Why a normal variable can't work

The obvious attempt:

```jsx
function Counter() {
  let count = 0;
  function handleClick() {
    count = count + 1;
    console.log(count); // logs 1, 2, 3... but the screen says 0 forever
  }
  return <button onClick={handleClick}>Clicked {count} times</button>;
}
```

The variable really does increment - the console proves it. The screen never changes, for two
separate reasons, and understanding them *is* understanding React state:

1. **Nothing tells React to redraw.** Assigning to a local variable is invisible to React. No
   re-render happens, so the DOM keeps showing the description from the last render.
2. **The variable wouldn't survive anyway.** A re-render means *calling `Counter` again*. Every call
   creates a fresh `let count = 0`. Local variables have the lifespan of one function call; your UI
   needs memory that outlives the call.

State solves exactly these two problems: it's memory that lives *outside* your function between
calls, plus a way of changing it that *notifies React*.

## useState: memory plus a doorbell

```jsx
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}
```

*What just happened:* `useState(0)` registers a slot of memory with React (initial value `0`) and
returns a pair: the current value and a setter function. Click the button and `setCount(1)` does two
things - stores `1` in React's slot, and schedules a re-render. React calls `Counter` again;
this time `useState` returns the stored `1`; the new description says "Clicked 1 times"; React
patches the text. The loop from phase 1, now with memory:

```mermaid
flowchart LR
  E[user event] --> S[setCount stores new value]
  S --> R[React re-runs Counter]
  R --> D[new description]
  D --> P[DOM patched]
```

📝 **Terminology:** `useState` is a **hook** - a function that hooks your component into a React
feature (here: persistent memory). Hooks have two hard rules: only call them **inside components**
(or other hooks), and only at the **top level** - never inside an `if`, a loop, or a callback. The
reason is mundane: React identifies each state slot *by the order the hooks are called in*. A hook
inside an `if` makes the order change between renders, and every slot after it silently gets the
wrong value. Phase 8 shows the error message this produces.

## State is a snapshot

Here's the trap that catches everyone in week one:

```jsx
function handleClick() {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
}
```

You'd expect +3. You get **+1**. Why: `count` isn't a live wire into React's memory - it's a plain
number that was copied into this render. If `count` was `0` when the render ran, all three lines say
`setCount(0 + 1)`. Three votes for the same value.

When the next value depends on the current one, pass the setter a *function*, and React will feed it
the latest value:

```jsx
function handleClick() {
  setCount(c => c + 1);
  setCount(c => c + 1);
  setCount(c => c + 1); // now it's +3
}
```

💡 **Key point:** each render sees a frozen snapshot of state. `setX(newValue)` when the new value is
independent; `setX(old => new)` whenever the new value is computed *from* the old one. Adopting the
function form as a reflex will also quietly save you from a stale-closure bug in phase 6.

## Why you must not mutate

The second trap, and the single most common "React is broken" moment:

```jsx
const [todos, setTodos] = useState([{ id: 1, text: 'Learn state' }]);

function addTodo(text) {
  todos.push({ id: 2, text }); // mutates the existing array
  setTodos(todos);             // "nothing happens"
}
```

The screen doesn't update, and here's the exact mechanism: when you call `setTodos`, React
compares the value you passed with the value it already has - using `Object.is`, which for arrays and
objects means *"is this the same object in memory?"* You pushed into the same array and handed the
same array back. Same object, "nothing changed," no re-render. Your data is right and your screen is
wrong - the exact bug class React was supposed to abolish, reintroduced by mutation.

The fix is always the same move: **make a new object/array that shares the unchanged parts**.

```jsx
function addTodo(text) {
  setTodos([...todos, { id: crypto.randomUUID(), text }]);
}

function removeTodo(id) {
  setTodos(todos.filter(t => t.id !== id));
}

function renameTodo(id, text) {
  setTodos(todos.map(t => (t.id === id ? { ...t, text } : t)));
}
```

*What just happened:* spread (`...`), `filter`, and `map` all return **new** arrays, so the identity
check sees a different object and re-renders. Note the pattern in `renameTodo`: new array, and a new
object for the one changed item, while untouched items are reused as-is. That's not waste - copying
references is cheap, and it's precisely what lets React's diff skip everything that didn't change.

⚠️ **Gotcha:** the mutating array methods - `push`, `pop`, `splice`, `sort`, `reverse` - all modify
in place. `sort` is the sneakiest: `setTodos(todos.sort(...))` returns the *same* array, mutated.
Use `setTodos([...todos].sort(...))` (or `toSorted(...)` in modern runtimes).

## One more shape: object state

```jsx
const [form, setForm] = useState({ name: '', email: '' });

function updateEmail(email) {
  setForm({ ...form, email }); // copy the object, overwrite one field
}
```

Same rule, object edition. `form.email = email` mutates; `{ ...form, email }` replaces. If you find
yourself spreading three levels deep on every update, that's a signal the state is too nested -
flatten it, or wait for the reducer pattern in a follow-up guide. For this guide's purposes: keep
state flat and small, and the spreads stay one level deep.

## Recap

1. Local variables reset on every render and can't trigger one - state is React-managed memory plus
   a re-render trigger.
2. `useState` returns `[value, setter]`; calling the setter is what schedules the redraw.
3. Hooks: top level only, components only - React tracks slots by call order.
4. State is a snapshot per render: use `setX(old => new)` when new depends on old.
5. Never mutate. React detects change by object identity, so mutation looks like "no change." New
   array, new object, every time.

```quiz
[
  {
    "q": "You call setItems(items) after items.push(newItem), and the screen doesn't update. Why?",
    "choices": [
      "push is asynchronous, so the item isn't in the array yet",
      "You passed the same array object, so React's identity check sees no change",
      "setItems only works with primitive values like numbers and strings",
      "The component is missing a key prop"
    ],
    "answer": 1,
    "why": [
      "push is synchronous - the item is in the array immediately; the data is right and the screen is wrong, which is the signature of a mutation bug.",
      null,
      "Setters accept any value, including arrays and objects - the constraint is identity, not type.",
      "Keys are about list items, and their absence produces a console warning, not a frozen UI."
    ],
    "explain": "React compares old and new state by identity (Object.is). Mutating in place and passing the same reference reads as 'nothing changed'."
  },
  {
    "q": "Inside one click handler you call setCount(count + 1) twice. count was 5. What does the UI show after the re-render?",
    "choices": ["7", "6", "5", "It depends on how fast React batches"],
    "answer": 1,
    "why": [
      "Both calls read the same snapshot (5), so both say 'set it to 6' - to get 7 you'd use the function form setCount(c => c + 1) twice.",
      null,
      "The setter does work - one increment lands; it's the second one that collapses into the first.",
      "Batching timing never changes the arithmetic here; the snapshot value does."
    ],
    "explain": "State is a per-render snapshot: both calls computed 5 + 1. The function form setCount(c => c + 1) reads the latest value instead."
  },
  {
    "q": "Why do the rules of hooks forbid calling useState inside an if statement?",
    "choices": [
      "Conditional state is bad program design",
      "React matches state slots to hook calls by their order, and a conditional call changes the order between renders",
      "if statements can't contain function calls in JSX",
      "It would create a new state slot on every render, leaking memory"
    ],
    "answer": 1,
    "why": [
      "It's not a style rule - it's a mechanical constraint of how React stores your state.",
      null,
      "This is regular JavaScript, where function calls are legal anywhere - the constraint comes from React, not the language.",
      "Slot creation only happens on the first render either way; the danger is misalignment, not leakage."
    ],
    "explain": "Hook calls are matched to their stored slots positionally. If call #2 sometimes doesn't happen, every hook after it reads the wrong slot."
  }
]
```


---

# Lists, Keys, and Conditional Rendering

Real UIs are mostly two moves: *show one of these for each item in this array* and *show this only
when that's true*. React has no special syntax for either - both are plain JavaScript expressions
inside JSX. That's good news (nothing new to memorize) with one exception: the `key` prop, which
looks like bureaucracy until you've seen the bug it prevents. This phase shows you the bug.

## Rendering a list: map, nothing more

```jsx
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}
```

*What just happened:* `map` turns an array of data into an array of elements, and JSX renders an
array of elements as siblings. There's no `for` directive, no repeater component - the JavaScript
you already know is the templating language.

Which brings us to the attribute you were told to add without being told why.

## What keys are actually for

Recall phase 1: on every render, React diffs the new description against the old one. For a list,
that raises a question the diff can't answer on its own: *is the first `<li>` in the new list the
same item as the first `<li>` in the old list, or a different one?*

The `key` is your answer. It's an identity tag: "this element represents item `id: 7`, wherever it
appears." With stable keys, React can tell moved-vs-changed apart:

- Same key, new position → the item **moved**; React moves the existing DOM node (and everything
  attached to it) instead of rebuilding it.
- New key → a genuinely new item; build DOM for it.
- Key gone → item removed; delete its DOM.

## The index-as-key bug, live

The tempting shortcut is `key={index}` - it silences the console warning, and it *seems* fine. Here's
the situation where it corrupts your UI. Suppose each row has an uncontrolled input (some state that
lives in the DOM, like what the user typed), and the user deletes the *first* row:

```text
Before delete (key = index):          After deleting "Buy milk":
  key=0  Buy milk    [typed: "2L"]      key=0  Walk dog    [typed: "2L"]  ← wrong!
  key=1  Walk dog    [typed: ""  ]      key=1  Call mom    [typed: ""  ]
  key=2  Call mom    [typed: ""  ]
```

React's view with index keys: "key 0 still exists, its text changed from 'Buy milk' to 'Walk dog'" -
so it *keeps the first DOM row* (typed text and all) and just swaps the label. The "2L" the user
typed for milk now sits next to "Walk dog". No error, no warning, just data attached to the wrong
row. With `key={todo.id}`, key 0's row is correctly seen as *deleted*, and every other row keeps its
own DOM.

💡 **Key point:** a key must be **stable** (same item → same key on every render) and **unique among
siblings**. An `id` from your data is right. The array index is only acceptable for lists that never
reorder, never insert, and never delete - which is a promise most lists eventually break.

⚠️ **Gotcha:** `key={Math.random()}` or `key={crypto.randomUUID()}` *generated during render* is the
opposite error: every render invents new keys, so React sees every item as brand new and rebuilds the
entire list's DOM each time - state wiped, focus lost, performance gone. Generate ids when the *data*
is created, not when it's rendered.

## Conditional rendering

Again, plain JavaScript expressions - three idioms cover nearly everything:

```jsx
function Inbox({ messages, error, isLoading }) {
  if (error) return <ErrorBanner error={error} />;   // 1. early return for whole-component branches

  return (
    <section>
      {isLoading && <Spinner />}                     {/* 2. && for "show or nothing" */}
      {messages.length > 0
        ? <MessageList messages={messages} />
        : <p>Inbox zero. Enjoy it.</p>}              {/* 3. ternary for either/or */}
    </section>
  );
}
```

⚠️ **Gotcha:** the `&&` idiom has one famous edge: numbers. `{messages.length && <List />}` renders
the *number* `0` on screen when the list is empty, because `0 && anything` evaluates to `0`, and JSX
renders numbers (it only skips `false`, `null`, and `undefined`). Write the comparison explicitly:
`{messages.length > 0 && <List />}`. If a stray `0` ever appears in your UI, this is where it came
from.

## Hiding vs unmounting - it matters

When a condition flips from true to false, React doesn't hide the component - it **unmounts** it:
DOM removed, state destroyed. Flip it back and you get a brand-new component with fresh initial
state. A collapsed panel rendered with `{open && <Panel />}` forgets everything typed inside it when
it closes. If the contents must survive, either lift the state up to the parent (phase 7) or keep the
component mounted and hide it with CSS. Neither is "the right way" - one destroys state, one
preserves it; choose the one the UX needs.

## Recap

1. Lists are `array.map(item => <El key={item.id} />)` - plain JavaScript, no special syntax.
2. Keys give list items identity across renders so React can move DOM instead of rebuilding it.
3. Index keys corrupt row-attached state the moment a list reorders or deletes; render-time random
   keys rebuild everything every time. Use stable data ids.
4. Conditionals: early return, `&&` (watch the `0`!), ternary.
5. A false condition unmounts - state inside is destroyed, not hidden.

```quiz
[
  {
    "q": "A list uses key={index}. The user deletes the first row, and the text they'd typed into that row's input now appears in a different row. What happened?",
    "choices": [
      "The browser cached the input value and restored it in the wrong place",
      "React matched rows by index, so it kept the first DOM node and only changed its label",
      "The delete handler mutated the array instead of copying it",
      "Two rows accidentally had the same id in the data"
    ],
    "answer": 1,
    "why": [
      "The browser restores values on page reload, not on list re-renders - this is React's reconciliation at work.",
      null,
      "A mutation bug would freeze the UI entirely (no re-render), not shift typed text between rows.",
      "Duplicate data ids cause a console warning about duplicate keys - but this list isn't using data ids at all."
    ],
    "explain": "With index keys, 'the item with key 0' still exists after the delete, so React reuses that DOM node - along with the user's typed text - for what is actually a different item."
  },
  {
    "q": "Your UI mysteriously shows a stray 0 above an empty list. Which line produced it?",
    "choices": [
      "{items.length > 0 && <List items={items} />}",
      "{items.length && <List items={items} />}",
      "{items.map(i => <Row key={i.id} />)}",
      "<List items={items ?? []} />"
    ],
    "answer": 1,
    "why": [
      "The explicit comparison yields false for an empty list, and JSX renders false as nothing - this is the fixed version.",
      null,
      "Mapping an empty array renders an empty array - nothing appears, stray or otherwise.",
      "?? substitutes an empty array for null/undefined; it never produces a visible number."
    ],
    "explain": "0 && X evaluates to 0, and JSX renders numbers. Only false, null, and undefined render as nothing - always write the comparison."
  }
]
```


---

# Events and Forms

Events are where your app stops being a picture and starts being software. React's event handling is
deliberately close to the DOM's - `onClick`, `onChange`, `onSubmit` - so there's little new to learn.
But two spots reliably burn newcomers: passing a function *call* instead of a function, and the
controlled-input pattern. Both get named and defused here.

## Handlers: pass the function, don't call it

```jsx
function DeleteButton({ onDelete, itemId }) {
  return (
    <>
      <button onClick={onDelete}>Delete</button>                {/* ✓ pass the function */}
      <button onClick={() => onDelete(itemId)}>Delete</button>  {/* ✓ pass a function that calls it */}
      <button onClick={onDelete(itemId)}>Delete</button>        {/* ✗ calls it DURING render */}
    </>
  );
}
```

The third line is the classic. `onDelete(itemId)` isn't "wired up to run on click" - it's plain
JavaScript, evaluated *while the component renders*. The item deletes itself the moment it appears,
and `onClick` receives the function's return value (probably `undefined`). When the handler needs an
argument, wrap it in an arrow function: the arrow is what gets called on click.

⚠️ **Gotcha:** the same mistake with a state setter is worse: `onClick={setCount(count + 1)}` calls
the setter during render, which triggers a re-render, which calls it again, forever. That's the
"Too many re-renders" error - phase 8 covers the message, but the cause lives here.

React hands your handler an event object, and one method earns daily use: `preventDefault`. The
browser's default for a form submit is a full page reload - the one thing a React app never wants:

```jsx
function handleSubmit(e) {
  e.preventDefault(); // forget this and every submit reloads the page
  // ...do the actual work
}
```

## The controlled input

Here's the pattern that makes React forms click. An `<input>` is a strange beast: it has its *own*
built-in state - the browser tracks what's typed with no help from you. So when you build a form in
React, there are suddenly two places the truth could live: the DOM's memory, or your state. The
controlled pattern picks one:

💡 **Key point:** a **controlled input** takes its `value` from state and reports every keystroke
into state via `onChange`. React state becomes the single source of truth; the input displays it.

```jsx
function SignupForm() {
  const [email, setEmail] = useState('');

  function handleSubmit(e) {
    e.preventDefault();
    console.log('signing up:', email); // the value is right here in state - no DOM digging
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={e => setEmail(e.target.value)}
      />
      <button disabled={!email.includes('@')}>Sign up</button>
    </form>
  );
}
```

*What just happened:* each keystroke fires `onChange`; `setEmail` stores the new text; the re-render
feeds it back as `value`. The payoff is that last line: the submit button's disabled state is
*derived* from the same state the input writes to. Live validation, character counters, dependent
fields - they all become one-liners because the data is already where your logic is, on every
keystroke, not just at submit time.

⚠️ **Gotcha:** set `value` without `onChange` and the input **freezes** - state never changes, so
every render shows the same text, and typing appears to do nothing (React also warns in the console).
The half-fix `onChange={setEmail}` freezes it more quietly: `onChange` receives the *event object*,
not the text, so you've stored an event in state. It's `e => setEmail(e.target.value)`.

📝 **Terminology:** the alternative - leaving the input's state in the DOM and reading it only when
you need it - is an **uncontrolled** input. It's legitimate for fire-and-forget forms where nothing
reacts to the value until submit. But every "the button should enable when...", "show the error
as they type..." requirement pushes you controlled, which is why controlled is the default habit
worth building.

Checkboxes are the one syntax variation: the boolean lives on `checked`, not `value` -
`<input type="checkbox" checked={agreed} onChange={e => setAgreed(e.target.checked)} />`.

## A real form, whole

The pattern scaled to a form with two fields and a select, sharing one state object (the
copy-don't-mutate move from phase 3):

```jsx
function ProfileForm({ onSave }) {
  const [form, setForm] = useState({ name: '', role: 'dev' });

  function update(field) {
    return e => setForm({ ...form, [field]: e.target.value });
  }

  return (
    <form onSubmit={e => { e.preventDefault(); onSave(form); }}>
      <input value={form.name} onChange={update('name')} placeholder="Name" />
      <select value={form.role} onChange={update('role')}>
        <option value="dev">Developer</option>
        <option value="designer">Designer</option>
      </select>
      <button disabled={!form.name.trim()}>Save</button>
    </form>
  );
}
```

*What just happened:* `update('name')` returns a handler for that field; each handler copies the
form object and overwrites its own key. One state object, one submit handler that already has
everything, and the events-up rule from phase 2: the form doesn't know what saving means - it calls
`onSave` and lets the parent decide.

## Recap

1. `onClick={fn}` or `onClick={() => fn(arg)}` - never `onClick={fn(arg)}`, which runs during render.
2. `e.preventDefault()` in submit handlers, or the browser reloads the page.
3. Controlled input: `value` from state, `onChange` into state - one source of truth, and validation
   becomes derived data.
4. `value` without a working `onChange` = frozen input. Checkboxes use `checked`.
5. Forms report upward: collect in state, hand the result to a callback prop.

```quiz
[
  {
    "q": "A button is written as onClick={deleteItem(item.id)} and the item vanishes as soon as the page loads. Why?",
    "choices": [
      "The click event fired automatically when the button mounted",
      "deleteItem(item.id) is evaluated during render, so the delete runs immediately",
      "item.id was undefined, which deletes everything",
      "React batched the click with the initial render"
    ],
    "answer": 1,
    "why": [
      "No click happened - mounting never fires click events; the call happened in plain JavaScript before any event existed.",
      null,
      "An undefined id would make the delete miss, not fire early - the timing is the clue, not the argument.",
      "Batching groups state updates; it cannot invent a click."
    ],
    "explain": "JSX braces hold ordinary expressions. deleteItem(item.id) calls the function right there during render; onClick={() => deleteItem(item.id)} passes a function to call later."
  },
  {
    "q": "You set value={text} on an input but typing does nothing. What's missing?",
    "choices": [
      "A name attribute so the browser can track the field",
      "An onChange handler that writes e.target.value into state",
      "A key prop to preserve the input between renders",
      "defaultValue instead of value"
    ],
    "answer": 1,
    "why": [
      "name matters for browser autofill and form submission payloads - it has no effect on typing into a controlled input.",
      null,
      "Keys identify list siblings; a single input doesn't need one and it wouldn't unfreeze typing.",
      "defaultValue would technically make typing work - by making the input uncontrolled, which abandons the pattern rather than completing it."
    ],
    "explain": "A controlled input displays state. If keystrokes never reach state via onChange, every render shows the same value and the input appears frozen."
  }
]
```


---

# Effects

`useEffect` has a reputation as React's hardest part, and it's a deserved one - but not because the
hook is complicated. It's because it gets taught as "the place to put code that runs after render,"
which is vague enough to invite every bug it's famous for. Here's the accurate sentence:

💡 **Key point:** an effect **synchronizes your component with something outside React** - a server,
a timer, a browser API, a websocket. If no outside system is involved, you almost certainly don't
need an effect.

Rendering must stay pure: same props and state, same JSX, no side effects - that's the contract from
phase 1 that lets React re-run your components freely. But real apps must fetch data, start timers,
update `document.title`. Effects are the escape hatch: *after* React has rendered and patched the
DOM, it runs your effect, letting you touch the outside world without polluting the render itself.

## The shape

```jsx
useEffect(() => {
  // runs AFTER the render is committed to the DOM
  return () => {
    // cleanup: undo whatever the effect set up
  };
}, [deps]); // when to re-run
```

The dependency array is the part to get precise about, because its three forms mean three different
things:

| Form | Meaning |
|---|---|
| `[]` | run after the first render only (no value it reads can change) |
| `[userId]` | run after the first render, and again whenever `userId` changes |
| *(omitted)* | run after **every** render - almost always a mistake |

The real rule for what goes in the array: **every value from component scope that the effect
reads** - props, state, and anything derived from them. The array isn't a scheduling knob you tune;
it's a declaration of what the effect depends on, and lying about it causes the stale-data bugs
below.

## A real one: fetching

```jsx
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    let cancelled = false;
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        if (!cancelled) setUser(data);
      });
    return () => { cancelled = true; };
  }, [userId]);

  if (!user) return <Spinner />;
  return <h1>{user.name}</h1>;
}
```

*What just happened:* after the first render, the effect fetches user data and stores it in state
(triggering a re-render that shows it). If the parent switches `userId` from 1 to 2, the dependency
changed, so React first runs the *cleanup* from the previous effect, then the effect again for the
new id.

That `cancelled` flag is not decoration - it's the fix for a real race. The user clicks profile 1,
then quickly profile 2. Two fetches are in flight, and nothing guarantees they return in order: if
1's response arrives *after* 2's, a naive effect would overwrite the correct profile with the stale
one. The cleanup flips 1's flag before 2's effect starts, so the late response gets ignored.

## Cleanup: the other half

Whatever an effect starts, its cleanup stops. React runs cleanup before re-running the effect, and
when the component unmounts.

```jsx
useEffect(() => {
  const id = setInterval(() => setSeconds(s => s + 1), 1000);
  return () => clearInterval(id);
}, []);
```

Skip the cleanup and every mount leaks an interval that keeps firing after the component is gone -
calling a setter on an unmounted component, stacking up if the component mounts repeatedly. The same
applies to event listeners (`removeEventListener`) and subscriptions (`unsubscribe`). An effect
without cleanup should make you ask: does this start anything that outlives the render?

Notice `setSeconds(s => s + 1)` - the function form from phase 3. `setSeconds(seconds + 1)` inside
this effect would read the `seconds` snapshot from the render the effect ran in: **0**, forever.
The interval would dutifully set `1` once a second. This is the **stale closure** - the effect's
functions see the values from the render that created them, and the function-form setter is the
clean way out.

📝 **Terminology:** in development, `<StrictMode>` (which Vite's template turns on) mounts every
component, unmounts it, and mounts it again - deliberately. Your effect runs twice on mount, *in dev
only*. It's not a bug; it's a smoke test: any effect whose double-run causes trouble is an effect
whose cleanup is missing or wrong. Write the cleanup instead of deleting StrictMode.

## The infinite loop, dissected

The most famous effect bug:

```jsx
const [data, setData] = useState([]);

useEffect(() => {
  fetch('/api/items').then(r => r.json()).then(setData);
}); // ← no dependency array
```

No array means "run after every render." The effect sets state → state change renders → the effect
runs again → sets state → ... The network tab fills with identical requests. Adding `[]` fixes this
one. The subtler variant survives the array:

```jsx
useEffect(() => { ... }, [{ id: userId }]); // object literal: new identity every render
```

Dependencies are compared by `Object.is` - the same identity check from phase 3, now working against
you. An object or array *created during render* is a brand-new identity each time, so the dependency
"always changed." Depend on the primitives inside (`[userId]`), not on containers built in render.

## When you don't need an effect

The most common `useEffect` in beginner code shouldn't exist:

```jsx
// ✗ derived state via effect: an extra render, an extra place to desync
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
useEffect(() => { setTotal(items.reduce((s, i) => s + i.price, 0)); }, [items]);

// ✓ derived data is just... computed during render
const total = items.reduce((s, i) => s + i.price, 0);
```

If a value can be computed from props and state, compute it in render - no hook, no lag, no second
copy to keep in sync. The same goes for reacting to a click: put the code in the event handler, not
in an effect watching a `clicked` flag. Reach for `useEffect` only when an outside system is
genuinely involved.

## Recap

1. Effects synchronize with the world outside React; pure derivations and event responses don't
   belong in them.
2. The dependency array declares everything the effect reads. `[]` = once, `[x]` = when x changes,
   missing = every render.
3. Cleanup undoes the effect: cancel the fetch flag, clear the timer, unsubscribe. StrictMode's
   dev double-mount exists to expose missing cleanup.
4. Stale closures read old snapshots - the function-form setter sidesteps the commonest case.
5. Objects/arrays created in render make dependencies "always changed" - depend on primitives.

```quiz
[
  {
    "q": "An effect fetches data and sets state, and the network tab shows the same request firing forever. The most likely cause?",
    "choices": [
      "The API is slow, so React retries automatically",
      "The dependency array is missing, so the effect re-runs after the re-render its own setState caused",
      "The fetch was not awaited",
      "StrictMode is enabled"
    ],
    "answer": 1,
    "why": [
      "React never retries fetches - it has no idea your effect even makes one.",
      null,
      "Not awaiting changes nothing here - the .then chain handles the response either way.",
      "StrictMode doubles the mount-time run in dev (two requests), it cannot produce an endless stream."
    ],
    "explain": "No array = run after every render. Effect sets state, state renders, effect runs again: a loop through the render cycle."
  },
  {
    "q": "setInterval(() => setCount(count + 1), 1000) inside a mount-only effect makes the counter go 0 → 1 and then stop. Why?",
    "choices": [
      "The interval only fires once without a cleanup function",
      "The callback closed over count from the first render (0), so every tick sets 1",
      "setCount can't be called from inside setInterval",
      "The effect needs count in its dependency array to keep the interval running"
    ],
    "answer": 1,
    "why": [
      "The interval fires every second - the console would prove it; each firing just sets the same value.",
      null,
      "Setters work fine from any callback - the problem is which value the callback can see.",
      "Adding count to the deps 'works' by tearing down and recreating the interval every second - treating the symptom; the function-form setter fixes the cause."
    ],
    "explain": "A stale closure: the interval callback sees the snapshot from the render that created it. setCount(c => c + 1) asks React for the latest value instead."
  },
  {
    "q": "You have items in state and need the total price on screen. The React-appropriate way?",
    "choices": [
      "A second useState for total, updated by a useEffect watching items",
      "Compute const total = items.reduce(...) during render",
      "Store total inside the items array's last element",
      "A ref that accumulates the total as items are added"
    ],
    "answer": 1,
    "why": [
      "It works, but it renders twice per change and creates a second copy of the truth that can desync - the pattern this phase specifically warns against.",
      null,
      "Smuggling derived data into your source data corrupts the model both directions.",
      "Refs don't trigger renders, so the screen would never update when the total changes."
    ],
    "explain": "Derived data is computed, not stored. If it follows from existing state, calculate it in render - no effect, no extra state, nothing to desync."
  }
]
```


---

# Sharing State

Sooner or later two components need the same piece of data: a search box in the header and a results
list in the main column. Or every component in the app needs the current theme. React's answer isn't
a new kind of state - it's a question about **where** the state you already know should live. Get the
location right and the sharing follows from the prop rules you learned in phase 2.

## Lifting state up

The rule of thumb, and it covers most cases:

💡 **Key point:** state lives in the **closest common parent** of every component that needs it.
The parent passes the value down as a prop to readers, and a setter callback down to writers.

```jsx
function SearchPage() {
  const [query, setQuery] = useState('');       // lives here: the common parent

  return (
    <>
      <SearchBox query={query} onQueryChange={setQuery} />
      <ResultsList query={query} />
    </>
  );
}

function SearchBox({ query, onQueryChange }) {
  return <input value={query} onChange={e => onQueryChange(e.target.value)} />;
}

function ResultsList({ query }) {
  const matches = ALL_ITEMS.filter(i => i.name.includes(query));
  return <ul>{matches.map(m => <li key={m.id}>{m.name}</li>)}</ul>;
}
```

*What just happened:* `SearchBox` doesn't own the query - it's a controlled component all the way up:
value from a prop, changes reported through a callback. `ResultsList` just reads. There is exactly
one `query` in the whole app, so the two can never disagree.

The refactor is mechanical when you see the smell: two components with their *own* copies of state
that are supposed to stay equal. Copies drift. Move the state up, pass it down, delete the copies.

⚠️ **Gotcha:** lift to the *closest* common parent, not to the top. Every state's change re-renders
the component holding it and its subtree. Hoisting everything into `App` means every keystroke
re-renders the world - correct behavior, needless work, and eventually sluggish typing. State wants
to live as *low* as it can while still reaching everyone who needs it.

## Prop drilling - and why it's usually fine

When the common parent is far above the readers, the value has to ride through layers that don't use
it:

```jsx
<App>               {/* owns currentUser */}
  <Layout user={user}>          {/* doesn't use it, passes it */}
    <Sidebar user={user}>       {/* doesn't use it, passes it */}
      <UserBadge user={user} /> {/* finally uses it */}
```

This is **prop drilling**, and it gets more hatred than it earns. Passing a prop through two or
three layers is explicit, greppable, and refactor-safe - you can see exactly where every value goes.
Drilling is a real problem only when it's *wide and deep*: the same value threading through many
layers on many separate branches, where adding one consumer means touching six files.

## Context: broadcast for the true cross-cutters

For those genuinely app-wide values - theme, current user, language - React provides **context**:
a way for a parent to make a value available to *any* component below it, without the intermediate
layers passing anything.

```jsx
import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext('light');          // 1. create (module scope, exported)

function App() {
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider value={theme}>              {/* 2. provide */}
      <Layout />                                       {/* nothing threads theme props */}
    </ThemeContext.Provider>
  );
}

function ThemedButton() {                              // anywhere under Layout, any depth
  const theme = useContext(ThemeContext);              // 3. consume
  return <button className={`btn-${theme}`}>Save</button>;
}
```

*What just happened:* `ThemedButton` asked for the nearest `ThemeContext.Provider` above it and got
its current value. When `theme` state changes, the provider's `value` changes, and every component
that consumes the context re-renders with the new value - no matter how deep it sits.

```mermaid
flowchart TD
  App[App - provides theme] --> Layout
  Layout --> Sidebar
  Layout --> Content
  Sidebar --> B1[ThemedButton - consumes]
  Content --> B2[ThemedButton - consumes]
```

⚠️ **Gotcha:** every consumer re-renders whenever the provided value changes - context has no
partial subscription. Put your fast-changing form state in a context and the whole consuming tree
re-renders per keystroke. Context earns its keep for values that are *widely read and rarely
changed*. That's also why context isn't a state-management upgrade or a Redux replacement - it's a
transport mechanism. The state still lives in a plain `useState` at the provider; context just
delivers it.

⚠️ **Gotcha:** consuming a context with no provider above you silently returns the default value
from `createContext('light')` - no warning. If your theme toggle "does nothing," check that the
provider actually wraps the part of the tree you're standing in.

## Choosing, in one table

| Situation | Reach for |
|---|---|
| One component needs it | `useState` right there - don't share what isn't shared |
| Siblings need the same value | Lift to the common parent, pass props |
| 2-3 quiet layers between owner and reader | Prop drilling - it's fine, really |
| Widely read, rarely changed (theme, user, locale) | Context |
| Complex updates, many actions, shared everywhere | A follow-up guide (reducers, external stores) |

## Recap

1. Shared state lives in the closest common parent - one copy, readers get props, writers get
   callbacks.
2. As low as possible, as high as necessary: hoisting everything to the top trades correctness for
   nothing and performance for pain.
3. Prop drilling through a few layers is explicit and fine; it's a smell only at width and depth.
4. Context broadcasts widely-read, rarely-changed values; every consumer re-renders on change.
5. Context transports state; it doesn't manage it.

```quiz
[
  {
    "q": "A search input component and a results component each keep their own query state, and they keep showing different things. What's the fix?",
    "choices": [
      "Sync the two states with a useEffect in each component",
      "Move the query state to their common parent and pass it to both",
      "Wrap the app in a context that holds both copies",
      "Give both components the same key so React links them"
    ],
    "answer": 1,
    "why": [
      "Effect-syncing two copies is a perpetual chase - there's always a render where they disagree, which is the bug you started with.",
      null,
      "Context would deliver the value, but the actual fix is having one value instead of two - and for siblings, lifting is the direct tool.",
      "Keys identify list items to the reconciler; they create no data link between components."
    ],
    "explain": "Two copies of one truth always drift. Lift the state to the common parent so there is exactly one query, passed down to both."
  },
  {
    "q": "When does context clearly beat lifting state and passing props?",
    "choices": [
      "Whenever more than one component needs a value",
      "When a rarely-changing value is read by many components across distant branches",
      "When the state changes on every keystroke",
      "Whenever you would otherwise pass a prop through even one intermediate layer"
    ],
    "answer": 1,
    "why": [
      "Two siblings sharing a value is the textbook lifting case - context adds indirection for nothing there.",
      null,
      "Fast-changing values are context's worst case: every consumer re-renders on every change.",
      "One or two pass-through layers is ordinary, healthy prop drilling - explicit and greppable."
    ],
    "explain": "Context is a broadcast for widely-read, rarely-changed values like theme or current user. For siblings and short distances, lift state and use props."
  }
]
```


---

# When React Breaks

Every error on this page is one you will meet. That's not pessimism - it's the good news: React's
classic failures are a short, fixed list, each one a direct consequence of a rule from earlier
phases. Meet them here first and each becomes a thirty-second fix instead of a lost afternoon.

## The cheat-card

| Symptom / message | Almost always means | Fix |
|---|---|---|
| **"Too many re-renders"** | A setter is being called *during* render | Find `onClick={fn(...)}` or a bare `setX(...)` in the body; wrap in a function |
| **"Rendered more hooks than during the previous render"** | A hook inside `if`/loop/early return | Move all hooks above the first `return`; branch *after* them |
| **"Each child in a list should have a unique 'key'"** | Mapped elements without `key` | `key={item.id}` on the top element inside `map` |
| **"Objects are not valid as a React child"** | Rendering `{obj}` instead of `{obj.field}` | Render the field; `JSON.stringify(obj)` to inspect |
| **"Cannot read properties of undefined/null"** | Rendering before async data exists | Guard: `if (!data) return <Spinner />` |
| UI stuck, but the data is right in the console | State was mutated, identity unchanged | New array/object: spread, `map`, `filter` (phase 3) |
| Input won't accept typing | `value` set with no working `onChange` | `onChange={e => setX(e.target.value)}` (phase 5) |
| Handler sees old state values | Stale closure snapshot | Function-form setter, complete effect deps (phase 6) |
| Effect fires twice on mount (dev) | StrictMode's deliberate double-mount | Not a bug - write the cleanup, don't remove StrictMode |

The rest of this phase walks the five that deserve more than a table row.

## "Too many re-renders. React limits the number of renders..."

```jsx
function Tabs() {
  const [active, setActive] = useState(0);
  return <button onClick={setActive(1)}>Details</button>; // ✗
}
```

Read `onClick={setActive(1)}` as JavaScript: call `setActive(1)` *now*, during render, and pass its
return value to `onClick`. Setting state schedules a render; the render runs this line again; loop.
React counts the laps and pulls the plug with this error.

The fix is the phase 5 rule: `onClick={() => setActive(1)}`. When the error points at a component
with no obvious handler bug, look for any bare `setX(...)` call sitting in the function body - the
same crime without the costume.

## "Rendered more hooks than during the previous render"

```jsx
function Profile({ user }) {
  if (!user) return <Spinner />;        // ✗ early return above a hook
  const [tab, setTab] = useState('posts');
  ...
}
```

Phase 3 told you *why* this rule exists: React matches state to hooks by call order. First render
(no user): zero hooks ran. Second render (user loaded): one hook. The bookkeeping no longer lines
up, and React refuses to guess. The mechanical fix: hooks first, branches after.

```jsx
function Profile({ user }) {
  const [tab, setTab] = useState('posts'); // ✓ every hook, every render
  if (!user) return <Spinner />;
  ...
}
```

## "Objects are not valid as a React child"

```jsx
<p>Ordered by {order.customer}</p>  // customer is { name: 'Ada', id: 7 }
```

JSX happily renders strings and numbers, skips booleans and null, and *throws* on plain objects -
because there's no sane default for "draw this object." The error names the object's keys
(`found: object with keys {name, id}`), which is your map to the fix: render `{order.customer.name}`.
A surprise variant: `{new Date()}` throws too - a `Date` is an object; format it first.

## The one with no error message at all

The worst React bug is silent: you click, the handler runs, the console shows the data changing,
and the screen just... sits there. You've already learned everything needed to solve it, so here it
is as a drill. The suspects, in order of likelihood:

1. **Mutation** - `push`/`sort`/property assignment on state, then setting the same reference.
   Verify: is the setter receiving a *new* object? (phase 3)
2. **Wrong state copy** - two components own separate copies of "the same" data and you updated the
   other one. Lift it. (phase 7)
3. **Snapshot arithmetic** - `setX(x + 1)` where `x` is stale. Function form. (phase 3)

🪖 **War story:** a teammate lost half a day to a table that wouldn't re-sort. The sort *worked* -
`console.log` showed the array perfectly ordered. The code was `setRows(rows.sort(byDate))`: `sort`
mutates in place and returns the same array, so the data was right, the reference identical, and
React saw nothing to do. The fix was eleven characters: `setRows([...rows].sort(byDate))`. The
lesson: when the console is right and the screen is wrong, stop debugging your logic - hunt the
mutation.

## Reading a React error like a local

Two habits turn React's scary red walls into directions:

- **Read the component stack, bottom-up.** Under the message, React prints which component was
  rendering, inside which parent. The top frames are React internals - your bug is in the first
  frame that names *your* component.
- **In dev, errors surface twice** (StrictMode double-invokes renders to flush out impure ones).
  Fix the first occurrence; the echo is the same bug.

## Recap

1. Setter called during render → infinite loop → "Too many re-renders." Wrap it in a function.
2. Hook count must match every render - hooks above all returns, branches below.
3. Missing `key` is a warning today and a corrupted-row bug the day the list reorders.
4. Objects can't be rendered - render their fields; guard against not-yet-loaded data.
5. Right data + frozen screen = mutation, almost every time. New references.

```quiz
[
  {
    "q": "\"Rendered more hooks than during the previous render\" appears after data loads. What's the likely shape of the bug?",
    "choices": [
      "Two components share one useState",
      "An early return (like a loading guard) sits above a hook, so hook count changed between renders",
      "The dependency array of an effect is missing",
      "useState was called with a different initial value on the second render"
    ],
    "answer": 1,
    "why": [
      "Hooks can't be shared across components - each call belongs to the component that made it.",
      null,
      "A missing deps array causes extra effect runs, not a hook-count mismatch.",
      "The initial value is only read on the first render; changing it later is ignored, not an error."
    ],
    "explain": "The count went from N to N+1 because a conditional path skipped a hook last render. Hooks first, returns after - always."
  },
  {
    "q": "Clicking sort visibly reorders the array in the console, but the table on screen never changes. First thing to check?",
    "choices": [
      "Whether the API returned the rows in the wrong order",
      "Whether sort mutated the state array in place, so the setter received the same reference",
      "Whether the table is missing an onChange handler",
      "Whether StrictMode is double-rendering the table"
    ],
    "answer": 1,
    "why": [
      "The console already shows the data correctly ordered - the data layer is fine; the update isn't reaching the screen.",
      null,
      "onChange belongs to inputs; a display table doesn't have or need one.",
      "StrictMode double-renders in dev but never suppresses an update."
    ],
    "explain": "Array.prototype.sort mutates and returns the same array - identical reference, no re-render. Copy first: setRows([...rows].sort(fn))."
  }
]
```


---

# Where to Go Next

The React ecosystem is famous for making newcomers feel behind before they start: Redux, Zustand,
TanStack Query, Next, Remix, RSC, signals discourse... Take a breath. Here's the load-bearing
truth: **everything in that pile is an optimization of something you now already understand** -
state, props, effects, and rendering. None of it is a second React to learn. This phase is a map of
when each piece becomes worth picking up, and a permission slip to ignore it until then.

## What you can already build

With phases 1-8 you can build real applications: multi-view UIs, forms with live validation, lists
with filtering and sorting, data fetched from APIs and kept in sync. Do that first. Two or three
small real projects will teach you more React than any amount of ecosystem reading, and they'll
generate the *specific pains* the ecosystem tools exist to solve - which is the only reliable way to
evaluate those tools.

## The map: learn it when you feel this pain

| When you feel this | Reach for | What it is |
|---|---|---|
| "My fetching effects are repetitive: loading flags, error flags, caching, refetching" | **TanStack Query** (or SWR) | Server-data management - the fetch effect from phase 6, industrialized: cache, retries, background refresh |
| "I need pages, URLs, and back-button behavior" | **React Router** | Client-side routing - URL state mapped to component trees |
| "I need SEO, fast first paint, or a server anyway" | **Next.js** | A framework around React: server rendering, file-based routing, data loading conventions |
| "Passing state around is getting genuinely painful at scale" | **Zustand**, then maybe **Redux Toolkit** | External stores - phase 7's table, one row further |
| "One state object, many complex update rules" | **useReducer** | Built into React: setters consolidated into a single dispatch/reducer |
| "The app is visibly slow" | **React DevTools Profiler**, then `memo`/`useMemo` | Measure first; memoization is a targeted tool, not a seasoning |
| "I keep writing the same form logic" | **react-hook-form** | Forms at scale, uncontrolled under the hood for performance |

Notice what's *not* on the list: nothing is labeled "you must learn this next." Every row is gated
on a pain you'll recognize when it arrives.

## Three specific pieces of advice

**On state libraries:** the industry default for new apps is much less "Redux everywhere" than the
older tutorials suggest. `useState` + lifting covers small apps entirely; add TanStack Query and
most of what people used to put in Redux (server data) has a better home; what's left - genuinely
client-side, genuinely global state - is often small enough for context or a tiny Zustand store.
Learn Redux Toolkit when a codebase you work in uses it, not preemptively.

**On Next.js:** learn it when you need what a framework adds (server rendering, routing, SEO, API
routes) - and you'll be glad plain React came first, because Next's hardest concepts (what runs on
the server vs the client) are only understandable on top of the rendering model you now have.

**On TypeScript:** if you already know it, use it with React immediately - props types alone pay
for the setup. If you don't, it's a bigger lever on your career than any item in the table above.

## Additional resources

- [react.dev](https://react.dev) - the official docs, rewritten around hooks and genuinely
  excellent; "Thinking in React" and "You Might Not Need an Effect" extend phases 2 and 6 directly.
- [TanStack Query docs](https://tanstack.com/query/latest) - even the introduction sharpens your
  sense of what server state *is*; read it before building your third fetch effect.
- [Next.js Learn](https://nextjs.org/learn) - the official interactive course, for when the
  framework pain arrives; assumes exactly the React you now know.

## Recap

1. You already know the model; the ecosystem is optimizations of it, adopted pain-by-pain.
2. Build two or three real things before adding any tool - the pains are the curriculum.
3. Server data → TanStack Query; routing → React Router; framework needs → Next.js; the rest can
   wait until it hurts.

```quiz
[
  {
    "q": "You've finished this guide and your fetch effects are getting repetitive - loading flags, error handling, caching. What's the reasonable next step?",
    "choices": [
      "Learn Redux, since state management is the standard next topic",
      "Adopt a server-data library like TanStack Query that industrializes exactly that pattern",
      "Rewrite the app in Next.js",
      "Move all fetching into one giant effect in App"
    ],
    "answer": 1,
    "why": [
      "Redux addresses shared client state - repetitive fetching is server-data pain, a different problem with a dedicated tool.",
      "",
      "A framework migration is a big hammer for a fetching-ergonomics problem - Next earns its place when you need server rendering, routing, or SEO.",
      "Centralizing every fetch recreates the library badly: one component re-rendering the world, no caching, no retries."
    ],
    "explain": "Match the tool to the felt pain: repetitive fetch/loading/cache logic is exactly what TanStack Query (or SWR) exists to absorb."
  }
]
```
