I experienced different behaviors when calling multiple set functions of a useState hook in sync and async functions.
function Test() {
console.log('app rendering starts.');
const [a, setA] = useState(1);
const [b, setB] = useState(11);
const updateState = () => {
console.log('\tupdating a starts.');
setA(2);
console.log('\tupdating a ends.');
console.log('\tupdating b starts.');
setB(12);
console.log('\tupdating b ends.');
};
console.log('app rendering ends.');
return (
<div className="App">
<div>a is {a}</div>
<div>b is {b}</div>
<button onClick={() => {
console.log('--------------sync click--------------');
updateState();
}}>Update State a & b Sync</button>
<button onClick={() => {
console.log('--------------async click--------------');
setTimeout(updateState, 0)
}}>Update State a & b Async</button>
</div>
);
}
Both buttons execute the same code, but in a different way.
Sync button result:
app rendering starts.
app rendering ends.
--------------sync click--------------
updating a starts.
updating a ends.
updating b starts.
updating b ends.
app rendering starts.
app rendering ends.
Async button result:
app rendering starts.
app rendering ends.
--------------async click--------------
updating a starts.
app rendering starts.
app rendering ends.
updating a ends.
updating b starts.
app rendering starts.
app rendering ends.
updating b ends.
Is this a desired behavior?
How can I have a sync result in an async function?
I could not find any tips about this in official documents.
Any help would be appreciated.
Thanks!
This appears to be a known fact according to Github discussions.
One of the comments, which I thinks is pretty self explanatory:
React currently will batch state updates if they're triggered from within a React-based event, like a button click or input change. It will not batch updates if they're triggered outside of a React event handler, like a setTimeout().
React internally uses unstable_batchedUpdates(). You can pass in a callback function and state updates inside that will be batched. This does not happen automatically for timeouts, promises, and async functions. So they are called from outside the callback and state updates inside them are not batched.
import { unstable_batchedUpdates } from "react-dom";
const updateState = () => {
unstable_batchedUpdates(() => {
console.log("\tupdating a starts.");
setA(2);
console.log("\tupdating a ends.");
console.log("\tupdating b starts.");
setB(12);
console.log("\tupdating b ends.");
});
};
You may use single useState() or useReducer() hooks call to update multiple state values by storing them together instead of storing it in separate state slices.
const [values, setValues] = useState({a: 1, b: 11});
setValues({a: 2, b: 12});