简体   繁体   中英

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:

在此处输入图像描述

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 -_-

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

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
    }));
  }
};

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'}));

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM