React setState async mistake

I was making a vary simple contact form in React the other day, and I got stuck for a while on a mistake I made when I was updating the component state.

Here is a simplified and buggy version code I was working on:

Not ringing any bells

The problem was that this.state.validationMsgName was not updating to "Name is required” but however the this.state.validationMsgEmail as updating.

The function setState takes in a object that should specify how the new state should update. I knew that the expression { …this.state, validationMsgName: “Name is required" } would result in a object that is like that state but only the validationMsgName property would update, so it did not ring any bells that I was doing something wrong.

Thinking synchronously but using asynchronous methods will cause problems

The fundamental problem of my approach was that I was thinking synchronously when writing the code. That each line would execute after the other and finish executing before next line would be executed. But setState is asynchronous so the code will carry on to the next line imitatively after setState is called. When name is empty it will call setState to update and set the validationMsgName property, but then it would carry on and check if email is empty and call setState. This means that we have two requests to update the state what would for example look like this:

// 1
setState({
name: "",
email: "bar@foo.com",
validationMsgEmail: "",
validationMsgName: "Name is required"
});
// 2
setState({
name: "",
email: "bar@foo.com",
validationMsgEmail: "",
validationMsgName: ""
});

Notice that the second call to will also update validationMsgName property to the empty string, that is because of setState is called with …this.state. Therefore the end result is that validationMsgName will always be empty.

Solution

The solution is pretty straight forward. It is to not spread this.state object when calling setState (e.g setState({ validationMsgName }) but only call it with the properties you want to update. Now the request will look something like this:

// 1
setState({ validationMsgName: "Name is required" });
// 2
setState({ validationMsgEmail: "" });

While I knew that setState is asynchronous and I know that the difference is between synchronously and asynchronous, I still managed to get myself stuck on this problem. But I think writing this short story will help me prevent myself making this kind of mistake again.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.