Skip to content
Chapter 25Lesson 9

Quiz - Effects, context, and concurrent hooks

Quiz progress

0 / 0

Your effect runs twice in dev. A teammate “fixes” it with a useRef(false) guard that skips the second run, and the doubling stops. Why is this the wrong move?

The guard hides the symptom but leaves the missing cleanup in place — and React 19 genuinely mounts, unmounts, and remounts components in production, where the leak it was masking still bites.

useRef(false) re-initializes to false on every render, so the guard never actually holds and the second run still fires.

Strict Mode double-invokes the ref guard too, so the two extra runs cancel out and leave the effect running an odd number of times.

In a chat component, roomId changes from "general" to "random". In what order does React run the effect’s cleanup and setup, and why?

Cleanup first (disconnect general), then setup (connect random) — cleanup runs before every re-sync, not only on unmount.

Setup first (connect random), then cleanup (disconnect general), because the new connection must exist before the old one is dropped.

Only setup runs; cleanup is reserved for when the component unmounts entirely.

Two engineers want a chat socket to reconnect only when roomId changes, while still calling the freshest onMessage and currentUser on each message. One wraps the handler in useCallback([onMessage, currentUser]); the other moves it into a useEffectEvent. Why does only the Effect Event actually work?

useCallback returns a stable function over a fixed snapshot — a real change to currentUser still mints a new identity and reconnects the socket. useEffectEvent reads the latest values yet stays out of the deps entirely, so [roomId] is the only thing that re-syncs.

useCallback can’t be given a dependency array inside a component, so it captures a stale currentUser and never updates.

Both work identically; useEffectEvent is just newer syntax for the same useCallback behavior.

You run the audit’s two-question code review on a useEffect: “What external system does it synchronize with?” and “What does its cleanup tear down?” Both answers come back blank. What does that tell you?

It’s almost certainly one of the catalog anti-patterns in disguise — a derived value, misplaced handler logic, or a fetch — and should be reshaped, not kept.

The effect is correctly minimal; an empty cleanup is the sign of a well-scoped effect with no side effects to undo.

The effect just needs an AbortController added to its cleanup to become a legitimate synchronization.

A reducer-backed NotificationsContext exposes { state, dispatch } as one value. A “Clear all” button reads only dispatch, yet it re-renders on every toast that comes and goes. What is the canonical fix?

Split into two contexts — a state context and a dispatch context — so dispatch-only consumers subscribe to dispatch, whose reference is stable for the component’s life and never changes.

Wrap the { state, dispatch } object in useMemo so its reference stays stable across notification changes.

Read dispatch with use(NotificationsContext) instead of useContext, which subscribes to only the dispatch field.

“Marking the slow list update as a transition will make filtering 5,000 rows faster.” Is this right?

No — transitions are priority markers, not speed boosts. The filter render costs exactly what it did; it just no longer blocks the keystroke, rendering in an interruptible background lane instead.

Yes — startTransition batches the work and skips intermediate renders, so the final filter runs faster.

Yes — wrapping the update in a transition debounces it, so the expensive filter runs fewer times overall.

A Client Component reads its data with use(fetch('/api/activity').then(r => r.json())) written directly in the render body. It spins forever and the Network tab fires the same request on a loop. Why?

The render body creates a brand-new promise on every render. use() tracks promises by reference, sees a new resource each time, suspends, resolves, re-renders, and creates another promise — an endless loop.

use() may only read a promise that originates in a Server Component; calling it in a Client Component always re-suspends.

The promise needs to be wrapped in a <Suspense> boundary, and without one use() retries the fetch indefinitely.

A junior reads that use() can be called inside an if and concludes “hooks can be conditional now,” so they wrap a useState in a condition. Why does this break, when the conditional use() did not?

useState claims a numbered slot by call order, so skipping it on some renders drifts the slot count and misaligns every hook below it. use() claims no slot — it’s tracked by reference or tree position — so moving it lines up against nothing.

useState is fine inside an if too; the lint flags it only as a style warning, not a real bug.

useState must be conditional-free because it triggers re-renders, whereas use() never does.

Quiz complete

Score by topic