How to read React you did not write
Every earlier module in this course taught you to write React. This module teaches the other half: reading React someone — or something — else wrote, and deciding whether it is correct. An AI assistant hands you a component that compiles, that tsc --noEmit accepts, and that looks exactly like the hooks examples you have been studying. None of that proves it is correct. React’s own failure modes are unusually quiet: an effect that reconnects on every keystroke instead of once, a list that silently swaps two rows’ state, a memo wrapper that never once skips a render. The six steps below are the order that actually surfaces these failures, and every later lesson in this module drills one part of it.
flowchart TD A["1. Hooks and their deps<br/>dependency arrays, rules of hooks"] --> B["2. Server/client boundary<br/>'use client' / 'use server' directives"] B --> C["3. Keys and identity<br/>list identity vs the reset switch"] C --> D["4. State ownership<br/>derived vs synced, lifted vs local"] D --> E["5. Effects vs events<br/>what belongs in which"] E --> F["6. Memo pairing and compiler assumptions<br/>what actually skips a render"]
1. Hooks and their dependencies
Section titled “1. Hooks and their dependencies”Before you trust what a hook does, read what it depends on. A dependency array is a claim: “this effect only needs to re-run when these values change.” An AI-generated component frequently gets the claim wrong in either direction — a value silently omitted (so the effect goes stale) or a value silently added because the linter complained (so the effect reconnects far more than it should).
- What to look for: every
useEffect/useLayoutEffect/useMemo/useCallbackdependency array, checked against everything the callback actually reads; every hook call, checked against whether it sits at the top level, unconditionally, in the same order every render; whether a value that changes on every render (a promise built inline, a new object literal) is handed to a hook that needs a stable reference (use(),useSyncExternalStore). - The question to ask: if I actually enabled the ESLint rule for this, would it pass — and if a value was left out of the array on purpose, is there a real reason (
useEffectEvent), or did someone just silence the warning? - Which lesson explains it: useEffect & Effects for dependency arrays, cleanup, and
useEffectEvent; Rules & Custom Hooks for the rules of hooks themselves; External Stores for the selector-must-be-cached trap.
2. The server/client boundary and directives
Section titled “2. The server/client boundary and directives”React code written for a framework (Next.js, or any other RSC-capable setup) carries directives that change where a file’s code runs. 'use client' marks the boundary into interactive, browser-side code; 'use server' marks a function as a callable network endpoint. Getting the boundary wrong doesn’t fail in this course’s own Vite probe — a plain tsc --noEmit treats both directives as harmless string literals — but it fails hard the moment a real RSC framework builds the same file.
- What to look for: does every file that calls
useState,useEffect, or attaches an event handler actually have'use client'(on itself, or a parent) in a real RSC project? Does a prop crossing from a Server Component into a Client Component stay serializable — no bare functions, no class instances? - The question to ask: if this file were dropped into a real Next.js App Router project today, would the framework’s own build reject it — and does this course’s probe (which can’t run RSC) actually prove that, or only prove the TypeScript shapes are valid?
- Which lesson explains it: Server Components for the boundary itself and what can cross it.
3. Keys and identity
Section titled “3. Keys and identity”key does two separate jobs, and AI-generated code frequently only knows about one of them. As list identity, a wrong key doesn’t throw — it silently attaches the wrong internal state to the wrong row the next time the list changes shape. As a reset switch on a single component, a missing deliberate key is invisible until you notice a component that should have reset is still showing yesterday’s data.
- What to look for: every
.map()that renders a list — is the key a stable id from the data, or the array index, or missing entirely (falling back to index behavior with a console warning)? Every<>...</>shorthand fragment inside a.map()— does it need a key it structurally cannot have? Every editor/detail component whose “current item” prop changes — does it usekey={currentItemId}to force a clean reset, or does it accumulate stale internal state instead? - The question to ask: if this list reorders, inserts, or deletes an item, does React attach the right internal state to the right row — and if this component’s subject changes, does its internal state actually reset, or silently carry over?
- Which lesson explains it: Rendering & Reconciliation for both jobs of
key, proven with real mount/unmount tests.
4. Who owns each piece of state
Section titled “4. Who owns each piece of state”Every useState call is a claim about ownership: this component is the single source of truth for this value. AI-generated code frequently duplicates ownership — mirroring a prop into state with an effect, mutating a value in place instead of replacing it, or broadcasting fast-changing state through a mechanism built for slow-changing, broadly-shared values.
- What to look for: state that could be computed during render (
const fullName = first + ' ' + last) but is instead synced viauseEffect; an object or array mutated in place (.push, direct field assignment) before being handed to a setter;setStatecalled directly in a render body, not an event handler or an effect; a Context whose value changes on every keystroke or animation frame, feeding many scattered consumers that would be better served by an external store’s selector. - The question to ask: does this component actually need to own this value, or is it just re-deriving something already available from props/other state — and if it owns it, is every update producing a genuinely new reference?
- Which lesson explains it: useEffect & Effects for “you probably don’t need an effect”; State Snapshot & Purity for the
Object.isbail-out and why mutation silently fails to re-render; Context for why a wide, fast-changing context re-renders everything underneath it,memoincluded.
5. Effects vs events
Section titled “5. Effects vs events”An effect and an event handler answer two different questions — “what should stay synchronized with something outside React” versus “what should happen because the user did something” — and AI-generated code regularly answers the wrong one with the wrong tool. The same confusion shows up in the async-Transition pitfall: code that looks like it’s inside startTransition the whole time, when only the part before the first await actually is.
- What to look for: a data fetch inside an effect with no
AbortController, when the identifier it depends on can change quickly; asetcall issued after anawaitinsidestartTransition(async () => {...})that was never re-wrapped in its ownstartTransition; auseLayoutEffectthat reads a value a siblinguseEffectis responsible for writing, on the same commit. - The question to ask: if this identifier changes quickly (fast typing, rapid navigation), can an older, slower response overwrite a newer one — and does every
setcall after anawaitstill carry the priority the surrounding code implies it has? - Which lesson explains it: useEffect & Effects for abort-safe fetching; Transitions and Actions & useActionState for the async-Transition
awaitpitfall; Commit Phase & Effects for exactly whyuseLayoutEffectanduseEffectfire in that order, every commit.
6. Memo pairing and compiler assumptions
Section titled “6. Memo pairing and compiler assumptions”React.memo, useMemo, and useCallback only work if the rest of the component plays along — a single inline object or function literal passed as a prop defeats a memo wrapper completely, silently, with no warning. The React Compiler changes this calculus by inserting the same discipline automatically, but only for code that actually follows the Rules of React; it assumes purity, and a component that violates it just doesn’t get compiled, with nothing in the build output telling you so.
- What to look for: an object, array, or function literal built inline and passed as a prop to a component wrapped in
memo; a component’s return type annotatedJSX.Element/ReactElementin a place that also needs to returnnull; whether the project’s build config actually wires upbabel-plugin-react-compilerat all, versus code that reads as if it assumes the compiler is running. - The question to ask: does this exact prop keep the same reference across renders when nothing meaningful changed — and if the answer is no, is that actually memoized anywhere, by hand or by the compiler?
- Which lesson explains it: Memoization & the Compiler and The React Compiler for what each tool actually catches, with real compiler output; TypeScript with React for
ReactNodevsReactElement/JSX.Element.
Mental Model
Section titled “Mental Model”Reading order for a React component you did not write:
1. Hooks & deps → does every dependency array tell the truth?2. Boundary/directives → does 'use client'/'use server' actually sit where it needs to?3. Keys & identity → does the right internal state follow the right row/subject?4. State ownership → who actually owns this value, and is every update a new reference?5. Effects vs events → is this synchronizing with something external, or reacting to input?6. Memo/compiler → does anything actually skip a render, and would the compiler agree?
Skipping straight to "does it compile" answers none of these — all six patterns in thismodule's catalog pass tsc --noEmit clean.Common Misconceptions
Section titled “Common Misconceptions”- “If
tsc --noEmitpasses, the code is correct.” Every entry in this module’s catalog type-checks cleanly. TypeScript proves the shapes are valid, never that the runtime behavior is what the prose around the code claims. - “A missing dependency is always a bug to silence with
useEffectEventor a wider array.” Sometimes the real fix is neither — it’s splitting the effect into two effects doing two genuinely different things. - “AI-generated code needs more scrutiny than human-written code.” Every pattern in this module’s catalog also appears in human-written React — AI just produces it with more confidence, and at higher volume.
- “If a component renders the right thing once, its state management is correct.” Several of this module’s bugs (index keys, context store, mutation) only show up on the second interaction, reorder, or unrelated re-render — a single render proves almost nothing about ownership.