user3657538
user3657538

Reputation: 215

React useState when updating state twice, first update gets deleted

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:

enter image description here

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

Answers (4)

ckedar
ckedar

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

Ian Vasco
Ian Vasco

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

Drew Reese
Drew Reese

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

Dupocas
Dupocas

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

Related Questions