Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I rely on useState value to calculate the new state?

Considering simple state hook:

const [count, setCount] = React.useState(0);

I want to increase or decrease the count. Basically do the same thing shown in the hooks docs.

But as a known fact for the old this.setState function:

Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.

and the right way to update the state based on the old state is:

this.setState((state) => ({
  counter: state.counter + 1
}));

Does the same thing applies to setCount? Or I can be sure that count is always up-to-date?

like image 464
streletss Avatar asked Jan 20 '20 17:01

streletss


2 Answers

useState hooks work differently than this.setState. Calling your setter, setCount in the below example, does work asynchronously, but since count is not changed during the rendering of a functional component the evaluation is deterministic.

The following example, copied verbatim from Hooks at a Glance in the React docs, is 100% safe (you won't have any errors about calling methods on unmounted components) and will behave as expected:

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

The only time you would need to use the functional update where you pass a function to setCount is if you need to call setCount multiple times when referencing state. Note that this isn't because setState is asynchronous—it's because count is not updated until the next render.

For example, this would be a problem:

<button
  onClick={() => {
    setCount(count + 1)
    setCount(count + 1) // since `count` is not dynamically updated mid-render, this evaluates to the same value as above
  }}
/>

In practice, this pattern is relatively rare. You wouldn't call the setter multiple times in a single function in this way. Instead, if you are passing the setter as a prop on children, and those children reference the state controlled by the setter, then you would use the functional update pattern. Even in this scenario, though, React will re-render the component with each successive value.

As for concerns about whether this is a reliable and/or recommended approach, the React documentation has this to say about the matter (source):

You might hear a recommendation to always write code like setCount(c => c + 1) if the state you’re setting is calculated from the previous state. There is no harm in it, but it is also not always necessary.

In most cases, there is no difference between these two approaches. React always makes sure that for intentional user actions, like clicks, the count state variable would be updated before the next click. This means there is no risk of a click handler seeing a “stale” count at the beginning of the event handler.

However, if you do multiple updates within the same event, updaters can be helpful. They’re also helpful if accessing the state variable itself is inconvenient (you might run into this when optimizing re-renders).

If you prefer consistency over slightly more verbose syntax, it’s reasonable to always write an updater if the state you’re setting is calculated from the previous state. If it’s calculated from the previous state of some other state variable, you might want to combine them into one object and use a reducer.

like image 95
coreyward Avatar answered Nov 08 '22 16:11

coreyward


I am not sure the exact logic, and the use case of the count state, but generally, you will need to call

setCount(count + 1); 

Or, functional updates using callbacks

setCount(prev => prev + 1); 

If you wish to update your state. However, do take note that the updating of state can be asynchronous, which is similar to the way class component's setState() works. You may refer to the useState documentation for more details.

like image 27
wentjun Avatar answered Nov 08 '22 14:11

wentjun