3

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!

0

1 Answer 1

10

State Updates May Be Asynchronous

Quoted from the reactjs docs about state updates:

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.

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};
});
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.