Meldon Meldon - 4 months ago 18
React JSX Question

React: How to use this.state in subsequent setState() calls? How to force an update to this.state?

I'm working on a small application with which you can keep track of some game scores.

The game is played between two players. A player can score points but can also incur a penalty. Instead of substracting the penalty from the player's score, it gets added to his opponent's score. The opponent should also get the next turn.

I setup a demo that implements the above description: https://codepen.io/anon/pen/ybLQvR

As you can see in the demo it doesn't quite work: when a player incurs a penalty he gets awarded the penalty, instead of his opponent.

This is due to

setState
within
setTurn
not being processed immediately.
setScore
still uses the 'old' state in which turn has not been updated.

I'm aware this can be handled by passing a callback to
setState
instead of an object (see the commented code), which takes in the previously set state. If I do that things work as expected.

However, I need the updated value of state (in this case
this.state.turn
) in the
addScore
outside of the
setState
call as well. How would I achieve this? I've tried
this.forceUpdate()
to no avail.

Should I wrap the entire function body of
addScore
in the
setState
callback? How would I then return any calculations based on
this.state.turn
from the
addScore
method should I need it?

Side question: why doesn't
this.forceUpdate()
work? Does it just trigger re-rendering the component without processing the state queue?

Thanks for helping me out!

Answer Source

I see what you're saying now. To my undestanding what you are trying to do is make sure that the setState method has been executed before another setState method.

Now the docs have this to say on the matter:

The second parameter is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.

So by their recommendations you should use componentDidUpdate(). So how is the question now.

componentDidUpdate(prevProps, prevState)

That is the method signature. So we can see here that the previous state gets passed in, and we now have access to the current state via this.state.

See codepen for the answer.

Basically - I turned the addScore and changeTurn into functions that were just snippets of the setState. This means we can combine them together with or state changes to make one atomic operation. And they can be reused without adding complicated default parameters for different scenarios.

Secondly, I added in functions which applied these methods in order to set the state. These are what the buttons use!

Finally in the componentDidUpdate function there is now a chance to apply actions based on the current action that is being executed. Do not add a setState for the default action, otherwise you'll get an infinite loop of updating......