Overview
-
useState
doesn't update a value instantaneously, so that value will be as it was even if it's accessed soon afteruseState
like below.
cf. https://ja.react.dev/reference/react/useState#usage
- React handles the accumulated queues and updates state in bulk after all events executing in parallel completed being done.
- After that, components are re-rendered.
cf. https://ja.react.dev/learn/queueing-a-series-of-state-updates
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
console.log(count); // 0
}
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
useRef
- Use
useRef
as "escape hatch" to get the latest updated state whensetState
is called in an asynchronous event(function).
Gist
-
setState
is an asynchronous function. - The queue for updating the state is not captured by the lifecycle of the state in React when it is called in an asynchronous function. So the state is not updated then.
cf. https://crybabe.net/archives/12#toc1
cf. https://ja.react.dev/learn/referencing-values-with-refs#when-to-use-refs
function Example() {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
const countRef = useRef(0);
const handleClick = () => {
setTimeout(() => {
setCount(count + 1);
console.log(count); // 0
setCount2(count => count + 1);
console.log(count); // 1
countRef.current += 1;
console.log(countRef.current); // 1
}, 1000);
};
const onSubmit = () => {
console.log(count); // 0
console.log(count2); // 0
console.log(countRef.current); // 1
};
return (
<div>
<p>{countRef.current}</p>
<button onClick={handleClick}>Click me</button>
<button onClick={onSubmit}>Submit</button>
</div>
);
}
export default Example;
However, you need to be careful about the point useRef
is independent of the React rendering cycle and so doesn't update DOM.
In principle, it's important to design the overall logic in the component so that they can work even if useState
runs asynchronously.
Functional update
- Use a functional update way instead of the one to do directly if you wanna access an updated value soon after it's updated by
useState
in the same event(function).
const increaseDouble = () => {
setCount(count + 1); // not done
setCount(count + 1); // done
console.log(count) // 1
};
- First
setCount(count + 1)
is called. Butcount
is still 0 at this point due to the asynchronous update. - Instantaneously, second
setCount(count + 1)
is called. Butcount
is still 0 at this point too because 1stsetCount
is not done yet.
- So you can use a functional update to resolve that.
cf. https://ja.react.dev/reference/react/useState#updating-state-based-on-the-previous-state
const increaseDouble = () => {
setCount(count => count + 1); // done
setCount(count => count + 1); // done
console.log(count) // 2
};
※ However seems there's no big difference between a functional update and a direct update for the most part except for the case like above.
Top comments (0)