Reactjs-setState previous state is the first argument, props as the second argument
Asked Answered
C

4

30

I have read in this official article these lines:

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

Can anyone please explain to me what the following code is trying to achieve by giving an example.

 this.setState((prevState, props) => ({
  couter: prevState.counter + props.increment
}));

I am referring to this official website of reactjs React.js

Chordophone answered 13/6, 2018 at 12:45 Comment(0)
F
53

They say you should do like that instead of the below example.

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});

They can't assure the state will have the correct value if you access like this because setState() will happen asynchronously, other updates could occur and change the value. If you are going to calculate the state based on the previous state, you have to make sure you have the last and most up to date value, so they made setState() accept a function that is called with prevState and props, so you can have the correct value to update your state, like the example below.

 // Correct
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));
Fustian answered 13/6, 2018 at 12:52 Comment(0)
A
9

To add to Bruno's answer, the correct function above is called a pure function. React is big on something called immutability which means that every declared value should never be changed from its original declaration if possible. The variables in that function aren't your actual props and state until you pass them in, which means on the javascript function stack (the thread that queues up sync and async calls) the values and references to properties will be stored differently, creating uncertainty of what the value will be in the "wrong" case.

Auberge answered 13/6, 2018 at 13:7 Comment(1)
thanx. Functions are stateless and classes are not in Reactjs?Chordophone
Y
6

Updating state based on the previous state

Suppose the age is 42. This handler calls setAge(age + 1) three times:

function handleClick() {
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
}

However, after one click, the age will only be 43 rather than 45! This is because calling the set function does not update the age state variable in the already running code. So each setAge(age + 1) call becomes setAge(43).

To solve this problem, you may pass an updater function to setAge instead of the next state:

function handleClick() {
  setAge(a => a + 1); // setAge(42 => 43)
  setAge(a => a + 1); // setAge(43 => 44)
  setAge(a => a + 1); // setAge(44 => 45)
}

Here, a => a + 1 is your updater function. It takes the pending state and calculates the next state from it.

React puts your updater functions in a queue. Then, during the next render, it will call them in the same order:

a => a + 1 will receive 42 as the pending state and return 43 as the next state.

a => a + 1 will receive 43 as the pending state and return 44 as the next state.

a => a + 1 will receive 44 as the pending state and return 45 as the next state. There are no other queued updates, so React will store 45 as the current state in the end.

By convention, it’s common to name the pending state argument for the first letter of the state variable name, like a for age. However, you may also call it like prevAge or something else that you find clearer.

React may call your updaters twice in development to verify that they are pure.

Yoakum answered 7/6, 2022 at 14:34 Comment(1)
Downvote for just copypasting the React docs without reference. Please properly cite your sources and paraphrase, this is not for copypasting third party content.Egomania
E
3

React may batch multiple setState() calls into a single update for performance.

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

And from https://reactjs.org/docs/react-component.html#setstate:

setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state.

Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.

Understanding with a example

This concept may be hard to understand and specially why it could cause issues, so I wrote an example that show an error happening:

/* Imagine props and states is the same as this.props and this.state */
var state = { counter: 0 } ; var props = { } ;

/* Our fake implementation of react setState */
var setStatesToRun = []
function setState(myFunction) { setStatesToRun.push(myFunction); }

/* Our fake implementation of react batch update */
function batchRunStateUpdates() {
  propsLocal = props
  stateLocal = state

  f1 = setStatesToRun.pop()
  newState = f1(stateLocal, propsLocal)  // Will run increment by 3
  console.log(newState) // newState: { counter: 3 }
  console.log(state) // state: { counter: 0 }

  f2 = setStatesToRun.pop()
  newState = f2(newState, propsLocal) // Will run increment by 2
  console.log(newState) // newState: { counter: 2 }
  console.log(state) // state: { counter: 0 }

  // ... get the next setState function loop

  console.log("Will update global state")
  state = newState  
  console.log(state) // state: { counter: 2 } // WRONG!
}

console.log(setStatesToRun) // []

// Right
setState((prevState, props) => { counter: prevState.counter + 3 });

// WRONG, using state (this.state)
setState((prevState, props) => { counter: state.counter + 2 });

console.log(setStatesToRun) // [func, func]

batchRunStateUpdates();

At the top we have some fake versions of React's methods. Those are overly simplified, but help explain what happens.

Then, we use setState the same way we do in React. One usage is right, the other is wrong.

Notice the final global state should be state: { counter: 5 }, but because of how we didn't respect React's recommendations we got state: { counter: 2 }

You can play with this code in https://jsfiddle.net/oniltonmaciel/g96op3sy/

Electrophilic answered 17/4, 2020 at 16:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.