In a React component, one effect sets state, a second effect depends on that state and sets more state, and a third reacts to that. What does this chain of effects cost, and how would you restructure it?
answer
- one render and commit per link
- intermediate states can reach the screen
- the order lives only in your head
- handler updates batch into one pass
- compute during render, act in the handler
basics
~20 sEach link costs its own render and commit, so users can briefly see half-updated screens and the logic becomes order-dependent and hard to trace. Compute what you can while rendering, and do the rest inside the single event that started the chain.
solid answer
~50 sThe cost is one full render-and-commit cycle per link, and effects run after paint, so intermediate states can actually reach the screen before the chain settles. It is also the hardest kind of React code to read: nothing states the sequence, each step is an independent effect that could also fire for other reasons, and adding a fourth step means reasoning about the whole graph again. The restructure has two moves. Anything that is just a value computed from existing state or props gets computed during render instead of being stored and synchronized. Anything that is a consequence of a specific interaction goes into that interaction's handler, where the whole sequence is written as ordinary top-to-bottom code and React batches the resulting updates into one render. A chain is only legitimate when a later step really is synchronization with something outside React.
code
javascript · 11 lines// One handler replaces a three-effect chain: runs once, batched into one render.
function handlePlaceCard(nextCard) {
setCard(nextCard);
if (!nextCard.gold) return;
if (goldCount < 3) {
setGoldCount(goldCount + 1);
} else {
setGoldCount(0);
setRound(round + 1);
}
}go deeper
Recognise the shape: an effect that sets state which another effect watches. Know that each step is a separate render, and that the fix is usually to do the work where the user action happened.
Explain the render, commit, paint, effect cycle and why intermediate states can be visible, then show the restructure — derive values during render, and write the remaining sequence as control flow in the handler.
Diagnose it in a real component: point at the extra commits in the profiler, identify the link that is genuine synchronization and must stay, and refactor the rest into a handler or a reducer action without changing behaviour.
Frame it as an architectural rule for the team: sequences belong in explicit control flow or a state machine, and reactive chains are reserved for real external synchronization, because chains are the pattern that stops composing as a component grows.
## What a chain of effects actually does at runtime Effects run after React has committed a render and the browser has painted. So an effect that calls a state setter starts a whole new cycle: render, commit, paint, then the next effect runs, sets more state, and the cycle repeats. Three chained effects mean the user's single action produces four passes over the component tree instead of one, with the browser free to paint whatever the intermediate states looked like. That is why chains show up as a visible flicker in real apps — a badge briefly showing the old count, a summary line that reads `0` for a frame, a list that renders unfiltered before the filter state catches up. The chain is not just slow; it is observable. Contrast this with updates made inside one event handler. React batches all state updates scheduled during a single event (and, since React 18, during timeouts, promises and native handlers too) into one re-render. Ten setters in a handler cost one pass; three setters spread across three effects cost three. ## Why chains are hard to reason about The sequence exists only in the developer's head. Nothing in the code says "first this, then that" — each effect is an independent reaction whose trigger is its dependency array, and any of those dependencies can change for reasons unrelated to the chain. That produces a specific class of bug: the chain fires from the middle. Some other code path sets the second state, the third effect runs, and the invariant the first effect was supposed to establish never happened. It also does not compose. Adding a step means finding where in the graph it belongs and re-checking every dependency array for accidental new triggers, rather than inserting a line into a function. ## The first replacement: compute during render Most chain links are not synchronizing anything — they are calculating. If a value can be derived from props and existing state, calculate it in the component body when you render. There is no state to store, no effect to keep in sync, and no window in which the stored copy disagrees with its inputs. Every link you delete this way removes an entire render pass. ## The second replacement: write the sequence in the handler The links that remain are usually consequences of one interaction: the user played a card, so the counter goes up, and if the counter crossed a threshold the round advances and the counter resets. That is a paragraph of ordinary logic. Written in the handler it reads top to bottom, runs once, is batched into a single render, and can be unit-tested as a plain function: ```js function handlePlaceCard(nextCard) { setCard(nextCard); if (!nextCard.gold) return; if (goldCount < 3) { setGoldCount(goldCount + 1); } else { setGoldCount(0); setRound(round + 1); } } ``` Notice what disappeared: the guards. Chained effects almost always start with `if (someFlag)` because an effect fires on every commit where its dependencies changed, and only some of those commits are the ones you meant. In a handler the condition *is* the control flow. When the same sequence is triggered from several handlers, extract it as a function they all call, or move the transitions into a reducer so one dispatched action performs the whole step. Both keep the sequence in one readable place. ## When a chain is genuinely correct Not every effect that follows another is a defect. A chain is legitimate when a later step is real synchronization with a system outside React and its trigger genuinely is "this state is now true", regardless of what made it true. Setting `roomId` from a click, a deep link, or a restored session should all connect the socket, and an effect keyed on `roomId` is the only thing that covers all three. The test is whether the step must happen for *any* cause of the state change, or only for the one interaction you had in mind. ## Spotting it in review Three signals, in order of reliability: state whose only reader is another effect's dependency array; effect bodies that open with a boolean guard; and setters called from effects at all. The last one is not automatically wrong — an effect that stores data arriving from an external subscription must set state — but every occurrence deserves the question: *who is this update for, and could the code that caused it have made this update directly?*
- Does React not batch the state updates the chained effects make?It batches within each effect, but not across them. Each effect runs in its own commit, so its updates schedule a fresh render that must complete before the next effect in the chain even runs. Batching removes redundant renders inside one step; it cannot collapse steps that are separated by a commit.
- What if two different handlers both need to run the same sequence?Extract it. Either put the sequence in a plain function both handlers call, or model the step as one reducer action both dispatch. Either way the sequence stays written in one place and still runs once per interaction — which is the property the effect chain was destroying.
- How would you convince a teammate that a chain is a problem when the app looks fine?Show the extra commits in the React DevTools Profiler for a single interaction, and point at the frame where an intermediate state paints. Then add a hypothetical fourth step and count how many dependency arrays have to be re-verified. The cost is render passes now and unreviewable coupling later.
saying these in an interview costs you the question
- Believes effects that set state are batched together into one render
- Thinks a chain is fine because each effect is short
- Adds a boolean guard to every effect instead of removing the chain
- Says the sequence is documented by the dependency arrays
- Assumes effects always fire in the order the developer intended