const [counter, setCounter] = useState(0)
Let's say I have two async functions func1 and func2. Both functions are asynchronous and update the counter state. func1 runs before func2.
async function func1() {
....extra code
setCounter(counter 1)
}
async function func2() {
....extra code
setCounter(counter 1)
}
func1()
func2()
Questions:
- Is it guaranteed that when func2's useState runs, the Counter's state is updated by func1? Please keep in mind both functions are asynchronous.
- If not, what should I do to make sure that func2's use State runs only after func1's Usestate has updated the Counter's value.
Here both func1 and func2's use state do the same job of incrementing Counter by 1. No matter which function runs first, the output is going to be the same. But while answering please answer as if both states do the different tasks and in order to achieve the desired result, func1 should run before func2.
CodePudding user response:
Is it guaranteed that when
func2
'suseState
runs, the Counter's state is updated byfunc1
?
No, not at all.
If not, what should I do to make sure that
func2
's use State runs only afterfunc1
'sUsestate
has updated the Counter's value.
You could make func2
wait until the promise returned by func1
settles (async
functions always return a promise), but that wouldn't necessarily mean that func2
didn't start running before the setCounter
state update had occurred; state updates are asynchronous, too.
To be sure of that, you need to both wait for func1
's promise to settle and use the callback form of setCounter
: setCounter(c => c 1)
:
async function func1() {
// ....extra code
setCounter(counter 1);
}
async function func2() {
// ....extra code
setCounter(c => c 1); // Note the callback form of state setter
}
func1().then(() => func2);
There are a dozen ways to spin that. For instance, you could pass func1
's promise into func2
as an argument and have it only await
it at the very end:
The basic idea is to make func2
wait until func1
is done.
async function func1() {
// ....extra code
setCounter(counter 1);
}
async function func2(promise) {
// ....extra code
await promise; // Wait for `func1` to finish
setCounter(c => c 1); // Note the callback form of state setter
}
func2(func1());
In the vast majority of cases, you probably don't care whether func1
is done or not; just use the callback form of setCounter
in both places so that neither of them is using outdated state (the counter
they close over):
async function func1() {
// ....extra code
setCounter(c => c 1);
}
async function func2() {
// ....extra code
setCounter(c => c 1);
}
func1();
func2();
In fact, I'd argue that any time you're using existing state (counter
's previous value) to update state (incrementing it by one), you should use the callback form of the setter. It's really easy to close over a stale copy of the state variable; using the callback form ensures you're using up-to-date state.
CodePudding user response:
No, it's definitely not guaranteed that when func2 runs func1 has already updated the state. What you can do to achieve that with a functional component is using the useEffect hook to track the changes on the state counter:
useEffect(() => {
func2();
}, [counter])
By doing this you are running func2 every time the counter state has changed. Also, if you want to make sure that func2 gets run only once after func1 updated the state, you can use a flag like this:
const isStateUpdatedByFunc1 = useRef(false);
async function func1() {
....extra code
isStateUpdatedByFunc1.current = true;
setCounter(counter 1);
}
useEffect(() => {
if(isStateUpdatedByFunc1.current){
isStateUpdatedByFunc1.current = false;
func2();
}
}, [counter])