Reputation: 159
When clicking the button, the text "second message" and "third message" should be added to the state through these functions:
class Button extends React.Component {
constructor(props) {
super(props);
this.addMessage = this.addMessage.bind(this);
}
addMessage() {
this.props.addMessage('second message'); // This is ignored
this.props.addMessage('third message'); // Only this message is added
}
render() {
return(
<button onClick={this.addMessage}>Add text</button>
)
}
}
But only one of them is firing.
I created a simple snippet that shows the problem.
class Button extends React.Component {
constructor(props) {
super(props);
this.addMessage = this.addMessage.bind(this);
}
addMessage() {
this.props.addMessage('second message'); // This is ignored
this.props.addMessage('third message'); // Only this message is added
}
render() {
return(
<button onClick={this.addMessage}>Add text</button>
)
}
}
class Messages extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<p>{this.props.message}</p>
)
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
messages: [{
text: 'first message'
}]
}
this.addMessage = this.addMessage.bind(this);
}
addMessage(message) {
let messages = this.state.messages.slice();
messages.push({text:message});
this.setState({messages: messages});
}
render() {
let messages = [];
this.state.messages.forEach((message) => {
messages.push(<Messages message={message.text}></Messages>);
});
return(
<div>
{messages}
<Button addMessage={this.addMessage} />
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>
<div id="root"></div>
Thanks in advance!
Upvotes: 3
Views: 3344
Reputation: 16354
Quoted from the reactjs docs about state updates:
React may batch multiple
setState()
calls into a single update for performance.Because
this.props
andthis.state
may be updated asynchronously, you should not rely on their values for calculating the next state.
Your second call to addMessage()
uses this.state
to set the new state. But because setState()
is asynchronous the state my not have been updated yet which leads to your second call overriding your first one:
addMessage(message) {
// here you access this.state which hasn't been updated yet in your second call
let messages = this.state.messages.slice();
messages.push({text: message});
this.setState({messages: messages});
}
To fix it, use a second form of
setState()
that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument:
Use an arrow function that gets passed the previous state including changes of preceding calls to setState()
to calculate the new state. That will fix your issue.
Using es6 spread syntax:
this.setState((prevState, props) => ({
messages: [...prevState.messages, {text: message}]
}));
Using slice()
:
this.setState((prevState, props) => {
let messages = prevState.messages.slice();
messages.push({text:message});
return {messages};
});
Upvotes: 10