You don't control state. React controls. After you asked React to change stored state you should not assume when it will be updated. Maybe next millisecond, maybe next year (I'm exaggerating just to make a point). Your component always renders current state, so when React will, eventually change state value, it will rerun your component. In a single run you place state value into variable that is not updated by React, since it is local. It is closured inside effect and even if React wanted, it can't assign new value to variable it does not know about.
Since your component is pure (not really, but let's stick to this terminology for simplicity), you don't own side effects. You can politely ask React to perform some side effects on some events - either DOM events (like onClick
where you put side effect in form of handler), or on each render with useEffect
hook. With useEffect you can limit when react will actually run your side effect. With dependencies array you can say "Hey, React, can you please run this side effect on initial render and when this values are changed".
Putting it together, to track that state value changed you need one more useEffect
hook, where you will be able to say with 100% certainty that state value changed. It will look like this:
function App() {
const [value, setValue] = useState("old");
useEffect(
function () {
console.log(value);
},
[value]
);
const run = async () => {
setValue("new");
const data = await wait(500);
console.log(value);
};
return <button onClick={run}>Run</button>;
}
But sometimes it is also just a part of solution. Sometimes you want to run side effect only when dependency value is "new" and not run it when it is "old". In this case you can add additional check inside your effect and make an early return if it is not value you are looking for:
function App() {
const [value, setValue] = useState("old");
useEffect(
function () {
if (value !== "new") return;
console.log(value);
},
[value]
);
const run = async () => {
setValue("new");
const data = await wait(500);
console.log(value);
};
return <button onClick={run}>Run</button>;
}
While it is almost sufficient, often it is better to put state and tied to said state effects in custom hook, or use some dedicated "state management" solution where side effects are first class citizens.