Understanding Capture Value in React Hooks: Why State Updates Lag and How to Fix Them
This article explains the concept of Capture Value in React Hooks, demonstrates the issue with asynchronous state updates using a button example, and shows how to resolve it with useRef, while also providing a simplified custom hook implementation and deeper insight into hook internals.
1. Starting with an example
In React applications asynchronous requirements are common. The example requirement is a button that shows
falseby default, changes to
trueon click, and reverts to
falseafter two seconds.
<code>const Demo = (props) => {
const [flag, setFlag] = useState(false);
let timer;
function handleClick() {
setFlag(!flag);
timer = setTimeout(() => {
setFlag(!flag);
}, 2000);
}
useEffect(() => {
return () => {
clearTimeout(timer);
};
});
return (
<button onClick={handleClick}>{flag ? "true" : "false"}</button>
);
};
</code>2. Introducing Capture Value
Capture Value can be understood as a “solidified” value. The
useStatehook returns
[hook.memorizedState, dispatch]. When
setFlagis called,
hook.memorizedStatepoints to a new state, but a
setTimeoutcallback still references the old state, so it reads the stale value.
Each Render Has Its Own Props and State.
Viewing each render as an independent snapshot helps explain this behavior. In a counter example, each click triggers a new render where the
countvalue is captured and displayed, illustrating the Capture Value phenomenon.
<code>// useState Capture Value example
function Counter(props) {
const [count, setCount] = useState(0);
return (
<div>
<p>Current count: {count}</p>
<button onClick={() => setCount(count + 1)}>Click</button>
</div>
);
}
</code>Each render fixes the
countat 0, then 1, then 2, demonstrating Capture Value. The same principle applies to event handlers and
useEffect.
<code>function Counter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('useEffect count:', count);
});
return (
// ...
);
}
</code>3. How to bypass Capture Value
Using the initial button example, the bug occurs because the timeout callback reads the stale
flag. Storing the latest state in a
useRefallows the callback to access the current value.
<code>const Demo = (props) => {
const [flag, setFlag] = useState(false);
const flagRef = useRef(flag);
flagRef.current = flag;
function handleClick() {
setFlag(!flagRef.current);
setTimeout(() => {
setFlag(!flagRef.current);
}, 2000);
}
// ...
};
</code>This resolves the issue, though the demo focuses only on the Capture Value of
flag.
4. Underlying principle
Understanding Capture Value reveals that each re‑render re‑executes the function component, and previously executed components are not revisited. This insight helps write higher‑quality hook‑based code.
5. References
Hooks Exploration, “Complete Guide to useEffect”
Tencent IMWeb Frontend Team
IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.