Reputation: 215
I noticed a behavior I cant understand how to solve, I never had it while I was writing react class based using this.setState but I get it while using useState hook.
for example:
const Main = () => {
const [values, setValues] = useState({val1: '', val2: '', val3: ''})
const func1 = async () => {
await setValues({
...values,
val1: '111111111'
});
await func2();
}
const func2 = async () => {
result = (fetch some data);
await setValues({
...values,
val2: result
});
}
};
now if you run func1, val1 will be changed but as soon as func2 finishes and we setValues the second time val1 will get overwritten and val2 will contain value.
what am I doing wrong and how can I fix this ?
Thanks in Advance!
Edit:
when using Hooks I cant see what is the acctual anme of the value entered in the React Chrome Dev tool. is there a way to fix this ? when I was having one useState containing an object I could see the titles of each object key... now its hidden -_-
Upvotes: 2
Views: 4384
Reputation: 1909
The behaviour of the code becomes clear once we note that values
variable in func2 is clousre on values
at outer scope, which is really a copy of state taken at the time useState
was called.
So what your spreading in func2
is is stale copy of your state.
One way of correcting this would be to use functional update
setValues((values) => ({...values, val2: result}));
This will make sure that you are using updated value of the state.
Upvotes: 0
Reputation: 1340
I haven't tested it yet but maybe it could work as an alternative.
Sometimes, when we need to get the value in the state, we usually do the spread operator within the state but it doesn't guarantees that the value is the correct one. For those cases, we better call setState
with a callback which takes the previous value from state. So, you can use something like this:
setValues(prev => ({...prev, val1:'11111'}));
Upvotes: 0
Reputation: 202907
React useState
also takes a functional updater to update your component state similarly to how class-based component's setState
works.
Note
Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
So in your code you could update as follows:
const Main = () => {
const [values, setValues] = useState({val1: '', val2: '', val3: ''});
const func1 = async () => {
await setValues(prevState => ({
...prevState,
val1: '111111111'
}));
await func2();
}
const func2 = async () => {
result = (fetch some data);
await setValues(prevState => ({
...prevState,
val2: result
}));
}
};
Upvotes: 3
Reputation: 21317
You're spreading the state before updating the correspondent property, try to chunk
your state
const Main = () => {
const [value1, setValue1] = useState(null)
const [value2, setValue2] = useState(null)
const func1 = async () => {
setValue1('foo')
await func2();
}
const func2 = async () => {
result = (fetch some data);
await setValue2('foo')
}
};
Here is what is happening
setValues
is called changing val1
(state isn't updated yet)
setValues
is called again changing val2
and spreading
the rest
By the time setValues
spreads values
val1
still holds it's initial value, overwriting the first setValues
call. Remember, changes in state are reflected asynchronously
Upvotes: 3