React Hooks Function Dependency
Solution 1:
TL;DR; Your solution is likely kind user
's answer
Below I'll describe what I've thought and learned so far throughout researches, and come up with 5 suggestions/solutions from people, via blogs,...
You've said:
My issue is that the function setFoo is properly executed, so foo state is a new array, but setBar that depends on the state of foo, receives an empty array. Basically setBar is executed before setFoo finished so the getBar function receives an empty array.
You're true. Basically because in React (both Hooks and class component), setState
is asynchronous. What does it mean? It means that setSomething just tells React to re-render the component later. It doesn't magically replace the const something
variable in the current running function — that's not possible.
const [foo, setFoo] = useState(0)
functionhandleClick() {
setFoo(42)
// we declared foo with const, you "obviously" shouldn't expect this // to "somehow" immediately change `foo` to 42console.log(foo);
// it's 0 in this render, BUT on next render, `foo` will be 42
}
Solution 1.
The easiest technique to you is to store the newly calculated value of foo
in a variable, then use that newly calculated value to both setFoo and setBar - a quite popular technique tho.
React.useEffect(() => {
const newFoo = getFoo(props.fooList, props.fooId);
setFoo(newFoo);
setBar(getBar(newFoo.listToFilter));
}, [props.fooId]);
// or: shouldn't use this, only to demonstrate the callback syntax in // the new setState Hook (different than the old callback syntax setState):React.useEffect(() => {
setFoo(() => {
const newFoo = getFoo(props.fooList, props.fooId);
setBar(getBar(newFoo.listToFilter));
return newFoo;
})
}, [props.fooId]);
Solution 2.
Another technique can be found here: https://stackoverflow.com/a/54120692/9787887 is using useEffect
to setBar
with the dependency list whose foo
.
React.useEffect(() => {
setFoo(getFoo(props.fooList, props.fooId));
}, [props.fooId]);
React.useEffect(() => {
setBar(getBar(foo.listToFilter));
}, [foo]);
Despite the answer get 27 upvotes, I think it's just overcomplicated the situation, and also (as I know) make the component unnecessarily rerender 2 times instead of 1, should be avoided.
Solution 3.
Another solution that might work is to use async/await
to make the state changes triggered asynchronously, to lead the changes not be batched (regarding this answer https://stackoverflow.com/a/53048903/9787887)
React.useEffect(async () => {
awaitsetFoo(getFoo(props.fooList, props.fooId));
awaitsetBar(getBar(foo.listToFilter));
}, [props.fooId]);
// no, actually this will not work!! it'll throw you an (annoyed) error// the actual working code is:React.useEffect(() =>const setFooAndBar = async () => {
awaitsetFoo(getFoo(props.fooList, props.fooId));
awaitsetBar(getBar(foo.listToFilter));
}
setFooAndBar();
}, [props.fooId]);
You see, the working code is again another overcomplicated (and bad) solution, (but should be introduced anyway??).
Solution 4.
Another solution that gaearon mentioned is to use useReducer
- With Hooks you could also useReducer to centralize state update logic and avoid this pitfall.
Another his insight:
- the recommended solution is to either use one variable instead of two (since one can be calculated from the other one, it seems), or to calculate the next value first and update them both using it together. Or, if you're ready to make the jump, useReducer helps avoid these pitfalls.
But it again seems to be another overcomplex suggestion to this case, doesn't it?
Solution 5.
The last suggestion is a comment of gaearon, tell you to rethink about your state dependence, is the state dependence really needed?
the best solution is simply to not have state that is calculated from another state. If
this.state.y
is always calculated fromthis.state.x
, removethis.state.y
completely, and only trackthis.state.x
. And calculate what you need when rendering instead
Thank you for being patient enough to read to here :)).
Solution 2:
Setting a state is an asynchronus process. So setBar(getBar(foo.listToFilter));
calling this foo is the empty array. You can use another useEffect for this
React.useEffect(() => {
setFoo(getFoo(props.fooList, props.fooId));
}, [props.fooId]);
React.useEffect(() => {
setBar(getBar(foo.listToFilter));
}, [foo]);
Solution 3:
setState
is an asynchronous function, that's why you are receiving an empty array in setBar
function. Basically you can't be sure that the state will be updated before the second setState
evaluates.
Why not to simply refer to the props in both cases?
React.useEffect(() => {
const newFoo = getFoo(props.fooList, props.fooId);
setFoo(newFoo);
setBar(getBar(newFoo.listToFilter));
}, [props.fooId]);
Post a Comment for "React Hooks Function Dependency"