Stale Closures in Effects

An effect callback closes over the props and state from the render that created it, so incorrect dependencies can cause it to observe outdated values. The usual fix is to declare every reactive value the effect reads as a dependency or restructure the code to avoid the closure problem.

In React, each render creates its own set of values, and functions created during that render close over those values. An effect callback therefore sees the values belonging to the render in which that effect was created.

React uses the dependency array to determine when an effect should be synchronized again. If an effect reads a reactive value such as state or a prop but omits it from the dependency array, later renders can change that value without causing the effect to receive a new closure.

For example, an effect with `[]` runs only for the initial mount in the common React model. If its callback reads `count`, it will continue to reference the initial `count` rather than the current state. Adding `count` to the dependencies causes React to recreate and resynchronize the effect when `count` changes.

A dependency fix is not always the best design. If the effect is being used to derive data for rendering, that work may belong directly in render logic or `useMemo`. If an event handler needs the latest value without resubscribing to an external system, a different structure may be more appropriate. The key is to make the synchronization boundary match the values the effect actually uses.

Key points

Interview questions

Why can a React effect see an old state value?

Each render creates its own lexical environment, and the effect callback closes over the values from that render. If the effect is not recreated when the state changes, its callback still refers to the older value. A common cause is leaving a reactive value out of the dependency array. Declaring the value as a dependency lets React resynchronize the effect with a closure from a later render.

What is the correct way to fix a stale value in an effect?

First identify every reactive value that the effect reads. Those values should normally appear in the dependency array so the effect can synchronize again when they change. If adding a dependency produces unwanted behavior, that is often evidence that the effect's design should be reconsidered rather than that the dependency should be omitted. Depending on the goal, the logic may belong in rendering, an event handler, or a different synchronization pattern.

Why doesn't putting an effect dependency in the array merely mean React passes the latest value into the old callback?

The dependency array is not a mechanism for mutating an existing closure. Instead, when dependencies change, React performs the effect's cleanup and then runs the effect created for the newer render. That new callback closes over the newer values. Thinking in terms of replacing the synchronization setup makes the behavior easier to reason about.

How would you diagnose an effect that logs an unexpectedly old value?

I would first identify which render created the effect and list the reactive values referenced by its callback. Then I would compare those values with the dependency array and inspect whether the effect is intentionally synchronized only once. I would also check whether the effect is doing work that belongs elsewhere, because unnecessary effects can make stale-value behavior harder to reason about. Finally, I would verify cleanup and re-subscription behavior when dependencies change.