React setState asynchronous behaviour

1 month 2 weeks ago

I recently fixed a bug in one of my react.js application whose root cause was that I was trying to set a state value using its child component and when I was trying to retrieve the state from its parent component then I was getting incorrect data. Every time when the component rerender and update the state but it was always missing one last character from the state.

Using setState is tricky for beginners, even experience react programmers can also introduce bugs when using React’s own state mechanism, like this.

Things to remember about setState

During this bug fixing I learned the following 3 major things about setState.

  • setState is asynchronous
  • setState causes unnecessary renders
  • setState is not sufficient to capture all component state

Many frontend developer using react.js do not realize this initially, but setState is asynchronous. If you set some state in a child component and want to use its state in its parent component then there is possibility that you will see always old state data and the component will retrieve previous value of state. This is because setState has not done its job yet when it was invoked again.  This is the trickiest part of setState. Let me show you with an example.

export default class MainModule extends Component {
    constructor(props) {
        super(props)
        this.state = {
          message: ‘Write in the input box’,
        }
    }
    appHandleSubmit = (txt) => {
        this.setState({message: txt})
    }
    render() {
        return (
            <div className='myApp'>
            <MyForm onChange={this.appHandleSubmit}/>
            <Message message={this.state.message}/>
            </div>
        )
    }
}

export class MyForm extends Component {
   handleSubmit = () => {
        this.props.onChange(this.state)
    }
    handleChange = (e) => {
        this.setState({message: e.target.value})
        this.handleSubmit()
    }
    render() {
        return (
            <form className="reactForm" onChange={this.handleChange}>
            <input type='text' />
            </form>
        )
    }
}

export class Message extends Component {
    render() {
        return (
            <div className="message">
                <p>{this.props.message}</p>
            </div>
        )
    }
}

Here we set a message state in our react.js component onChange handler for input field.

handleChange = (e) => {
    this.setState({message: e.target.value})
    this.handleSubmit()
}

We also call another handleSubmit handler which is actually passing the current state to its parent component. Now the problem is that a call to setState isn't synchronous by nature. It always creates a "pending state transition."  You can find excellent react docs here which cover everything that could go wrong when using setState.

Solution

First solution is that we simply pass the handleSubmit as a callback to setState method, this way after setState complete only the handleSubmit will get executed.

 
handleChange = (e) => {
    this.setState({message: e.target.value})
    this.handleSubmit()
}

But in this way the parent component will get the latest state but inside the child component we still get prvious value of the state. So how to fix this? Our second solution is the way to fix this problem. In second Solution I need to explicitly pass the new input value as part of the event being raised. See how I fixed my problem by passing explicitly the new input value from child component.

 

handleSubmit = (txt) => {
    this.props.onChange(txt)
}
handleChange = (e) => {
    this.setState({message: e.target.value})
    this.handleSubmit(e.target.value)
 }

Now I am passing my new input value which is now available on my parent component. Sounds cool!

Things to remember when you use States

The state contains data specific to this component that may change over time. Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable. 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.

Shahid Hussain

Shahid Hussain is a frontend developer and UX Consultant living and working in Sweden. Shahid is specializes in JavaScript development and developed anything from WordPress websites to complex e-commerce JavaScript applications. Shahid can also sketch, from websites to apps and icons, even print material. He works on content-centric, and mobile products, as well as cross-portal user experiences. Photography, music and travelling can trigger his attention.