what's the difference between setCount(prev => prev + 1) and setCount(count + 1)?

Viewed 1962

I am reading an example of React hooks. I find they write

setCount(count + 1)

But I usually wrote like this

setCount(prev => prev + 1)

Is there any difference? Which one is better?

5 Answers

There is a difference, in the first, the count will be based on the current value at the time that that render occurred due to the closure in the function.

The second would always use the latest value for the increment.

Because closures are a complicated topic, here's some examples. The first shows the main difference between the two.

The second example shows several ways that will allow things to work properly with closures and effects/hooks

const { useState, useEffect } = React;
function Example(){
const [count1, setCount1] = useState(0);
useEffect(()=>{
  setCount1(count1 + 1);
  setCount1(count1 + 1);
  setCount1(count1 + 1);
},[])

const [count2, setCount2] = useState(0);
useEffect(()=>{
  setCount2(prev=> prev + 1);
  setCount2(prev=> prev + 1);
  setCount2(prev=> prev + 1);
},[])
return <div>
Both count1 and count2 have had 3 increments.
<br/>
count1 stays at 1 because the count1 variable in the useEffect isn't change due to the closure in the arrow function in the useEffect
<br/>
Current count1: {count1}
<br/>
Current count2: {count2}
</div>
}
ReactDOM.render(<Example/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>

const { useState, useEffect, useRef } = React;
function Example(){
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const [count3, setCount3] = useState(0);

const count3Ref = useRef(count3);
count3Ref.current = count3;

useEffect(()=>{
  const id = setInterval(()=>{
  setCount1(count1+1);
  setCount2(prev=>prev+1);
  setCount3(count3Ref.current+1);
  },300)
  return ()=>{clearInterval(id)}
},[])

const [count4, setCount4] = useState(0);
useEffect(()=>{
  const id = setTimeout(()=>{
  setCount4(count4+1);
  },300)
  return ()=>{clearTimeout(id)}
},[count4])
return <div>
All of the counts theoretically increment every 300ms
<br/>
<br/>
count1 stays at 1 because the count1 variable in the useEffect isn't change due to the closure in the arrow function in the useEffect
<br/>

Current count1: {count1}
<hr/>
count2 uses the functional version of setCount2 so it always uses the latest version and will update properly
<br/>
Current count2: {count2}
<hr/>
count3 increments because refs are mutable by nature and allow us to bypass the closure.
<br/>
Current count3: {count3}
<hr/>
Another possiblity: count4 increments because we properly use the dependency array and force the useEffect to re-run every time count4 changes.
<br/>
Current count4: {count4}

</div>
}
ReactDOM.render(<Example/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>

Well, It depends on the situation.

If you want to update the state whose values depend on the previous state then you should use below.

setCount(prev => prev + 1);

This will update the state to the new state.

If you use this like below, then it will update the count for one but not two times because setState is asynchronous in nature.

setCount(count + 1);
setCount(count + 1);

But if you do this

setCount(count => count + 1);
setCount(count => count + 1);

Then it will update the state two times as we are updating the state from the previous state.

const {useState} = React;

const Example = () => {

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

  const handleClick = () => {
    setCount(count => count + 1);
    setCount(count => count + 1);
  }

  return ( 
  <div>
    <p> {count} </p> 
    <button onClick = {handleClick}> Add by 2 </button>  
    </div>
  );
};

ReactDOM.render( 
  <Example />, document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>

I copied note of React Hook 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};
});

Another option is useReducer, which is more suited for managing state objects that contain multiple sub-values.

In most cases they are doing the same job.

But second one is better when your react component becomes super complex. Because count in first case is not reliable if you have many setState() queuing up. It may be some value you don't expect. setCount(prev => prev + 1) always add 1 to its previous value which is more reliable.

Actually you can pass (previousValue, props) as parameters to setState().

setState() is an asynchronous operation. React batches state changes to reduce the number of re-renders. (especially so in React 18 with automatic batching)

State may not change immediately after setState() is called.

Therefore the preferred method is to update state with prevState instead of relying on current state.

Related