If you want to modify an existing value in the state, you should never get the value directly from the state and update the state object, but rather use the updater function in setState
so you can guarantee the state values are the ones you need at the time of updating the state. This is just how React's state works and it's a very common React mistake.
From the official docs
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
setState() will always lead to a re-render unless
shouldComponentUpdate() returns false. If mutable objects are being
used and conditional rendering logic cannot be implemented in
shouldComponentUpdate(), calling setState() only when the new state
differs from the previous state will avoid unnecessary re-renders.
The first argument is an updater function with the signature:
(state, props) => stateChange
state is a reference to the component state at the time the change is
being applied. It should not be directly mutated. Instead, changes
should be represented by building a new object based on the input from
state and props.
Both state and props received by the updater function are guaranteed
to be up-to-date. The output of the updater is shallowly merged with
state.
So you must get the value exactly when you want to update the component inside the setState function using the first argument of the updater function.
lowerPlayerHealth = (index) => () => {
// use setState rather than mutating the state directly
this.setState((state, props) => {
// state here is the current state. Use it to update the current value found in state and ensure that it will be set correctly
return (state); // update state ensuring the correct values
});
}
Solution
To lower a value found in state:
class App extends React.Component {
constructor(props){
super(props);
this.state = {
playerState: [
{
name: 'Jack',
hp: 30
}, {
name: 'Alan',
hp: 28
}
],
};
}
lowerPlayerHealth = (index) => () => {
this.setState((state, props) => {
state.playerState[index].hp -=1; //update the current value found in state
return (state); // update state ensuring the correct values
});
}
render() {
return (
<div className="App">
<p>Player 1: {this.state.playerState[0].name}</p>
<p>Health: {this.state.playerState[0].hp}</p>
<button onClick={this.lowerPlayerHealth(0)}>Hit player 1</button>
<p>Player 2: {this.state.playerState[1].name}</p>
<p>Health: {this.state.playerState[1].hp}</p>
<button onClick={this.lowerPlayerHealth(1)}>Hit player 2</button>
</div>
);
}
}