Reacting to the Async State

Nothing Can Be Easy... Ever

One of the many glorious features that allows developers to make and manipulate dynamic websites with the React js library is the idea of 'state'. Alongside 'components' and 'props', 'state' is one of the three core fundamental ideas behind React.

Unfortunately, 'state' is not a concept that is particularly easy to define. React's documentation doesn't even bother providing a definition, skipping straight to contrasting 'state' with 'props' and explaining how to utilize it. So, I might as well try my best to provide this concept a dictionary-ready description:

In React, State is the present set of mutable data stored within a component available to manipulate a webpage's DOM (Document Object Model). This data is stored in a JS object structure as a value to the key of 'state' on the component (this).

State's mutability is what makes it such a powerful tool in web development, but that dynamism could lead to some awfully slow rendering for large datasets, poorly coded recursive programs, and programs that make calls to many async processes. React, however, emphasizes performance. The result is that React updates state off of browser events asynchronously.

Why is this important? It means that updating our state values is not quite as easy as just making a new declaration in Javascript. The following code works as expected, setting the initial state in the constructor and then updating directly through the function.

    
      import React from 'react'

      export default class Applet extends React.Component {
        constructor() {
          super()
          this.state = { myState: false }
        }

        render() {
            console.log(this.state.myState) // logs false to the console
            this.state.myState = true
            console.log(this.state.myState) // logs true to the console
            return ( >div/> )
        }
      }
    
  

This does not really change for simple mutations, even when we chain multiple methods or pass the state as a prop to a child component.

    
import React from 'react'
import Header from './Header'

export default class Applet extends React.Component {
    constructor() {
        super()
        this.state = { myState: 'initial' }
    }

    updateState = () => { 
        this.state.myState = 'updated state'
        return this.state.myState
    }

    mutateState = () => { 
        this.state.myState += " has been mutated"
        return this.state.myState
    }

    render() {
        console.log(this.state.myState) // logs 'initial' to console
        console.log(this.mutateState()) // logs 'initial has been mutated' to console
        console.log(this.updateState()) // logs 'updated state' to console
        console.log(this.mutateState()) // logs 'updated state has been mutated' to console
        return >Header myState={this.mutateState()} />
    }
}

// Header.js

import React from 'react'

class Header extends React.Component {
	
	render() {
		console.log(this.props.myState) // logs 'updated state has been mutated has been mutated' to console
		return >div />
	}
}

export default Header
    
  

In fact, if we replace those declarative statements with a call to setState, we error out with a nasty recursion if we keep calls to setState within the render method.

    
import React from 'react'

export default class Applet extends React.Component {
  constructor() {
      super()
      this.state = { myState: 'initial' }
  }

  updateState = () => {
      this.setState({myState: 'updated state'})
  }

  render() {
      console.log(this.state.myState) // logs 'initial' to console
      this.updateState() // logs 'updated state' to console repeatedly
      return >Header myState={this.state} /> // component is correctly rendered until stack maximum is reached
  }
}
    
  

So, what is the point? We can happily change our state with declarations, and using React's methods of choice overflows our stack. Surely, there is a reason for this madness.

Why does React Update State Asynchronously?

Typically, when we consider asynchronous processes, we think of processes related to speaking with the server, not the DOM. It makes sense to make calls to our servers asynchronous, as requests and data are passed between machines. There is a required delay for promises to be resolved.

Changing state in React, on the other hand, does not necessarily require sending a request to a server, just the DOM. The advantage of calling setState asynchronously is that React can batch multiple updates. An application with many components will likely have event handlers on multiple components that will call setState. If a parent and child components both call setState from the same click event, React will batch the two calls together, which avoids re-rendering components twice. This can only be completed asynchronously.

Below is an example of a duel call to setState. Both the parent (Applet) and child (Header) div are listening for clicks and are programmed to change the same state attribute.

    
import React from 'react'
import Header from './Header'

export default class Applet extends React.Component {
  constructor() {
      super()
      this.state = {
          myState: 'bored' }
  }

  updateMyState = () => {
      this.setState({myState: 'hungry'})
  }

  updateMyStateFromChild= () => {
      this.setState({myState: 'intrigued'})
      }

  render() {
      console.log(this.state.myState) // logs 'bored' to console at initial render, then 'hungry' on subsequent clicks
      return (
          >div onClick={this.updateMyState} style={{backgroundColor: 'yellow'}}>
              >Header passedState={this.state} passedMethod={this.updateMyStateFromChild} />
          >/div>
  )}
}

// Header.js
import React from 'react'

class Header extends React.Component {
	
	render() {
		// console.log(this.props.myState)
		return >div onClick={this.props.passedMethod}>
			>div>My State is {this.props.passedState.myState}>/div> //displays state as bored on initial render, and then hungry after other clicks.
		>div>
	}
}

export default Header
    
  

While we cannot witness a state change to the value of 'intrigued' with this code, the applet does not break and React manages the handle the state change with ease.

But, what if we don't use setState() in this dual situation? What if we try to set things with our old friend, '=' ?

    
import React from 'react'
import Header from './Header'

export default class Applet extends React.Component {
    constructor() {
        super()
        this.state = {
            myState: 'bored' }
    }

    updateMyState = () => { 
        this.state.myState = 'hungry'
        console.log(this.state.myState)
    }

    updateMyStateFromChild= () => { 
        this.state.myState = 'intrigued'
        console.log(this.state.myState)    
    }

    render() {
        console.log(this.state.myState) // logs 'bored' to console at initial render, then both 'hungry' and 'intrigued' on subsequent clicks
        return (
            >div onClick={this.updateMyState} style={{backgroundColor: 'yellow'}}>
                >Header passedState={this.state} passedMethod={this.updateMyStateFromChild} />
            >/div>
    )}
}


// Header.js
import React from 'react'

class Header extends React.Component {
	
	render() {
		return >div onClick={this.props.passedMethod}>
			>div>My State is {this.props.passedState.myState}>/div> //displays state as bored on initial render, and does not change with the clicks.
		>div>
	}
}
    
  

While setState() will automatically fire off processes to rerender our affected components, simply reassigning our state, does not cause anything to change on the DOM.

Interestingly enough, if we were to call our parent class's render method from within our state changing method. And when we do, the console.log() in the method will log our updated state value, but the child component will still not rerender on the DOM with the updated state value. (It is being returned to our update method, not the React parent component.)

By compiling multiple requests of setState before changing the state, React is also able to edit multiple components in one go, which improves performance.

SetState in the Component Lifecycle

The setState() method is only available at certain parts of the component lifecycle. While it is not technically available in the constructor() method, this.state can be set equal to a JavaScript object to provide the component's state with initial values.

As we discovered earlier, setState() is not really available during the render() due to stack overflow from infinite recursion. SetState() is available during the ComponentDidMount() phase.

While calling setState() in ComponentDidMount() will trigger additional rendering, it occurs before React updates the screen, so the user experience is largely unaffected. React's documentation suggests avoiding setState() here except for cases where the state may be dependent on screen measurement (for example, what space is available for modals), since almost everything else could be set in the constructor.

ComponentDidUpdate() also allows us to call setState(), which is highly sensible. However, the setState() call must be wrapped in a conditional statement to avoid an infinite loop.

An alternative to calling setState() within ComponentDidUpdate() is to call it within... setState(). While our above examples only passed one argument (the update object) to the method, setState will also accept a second argument. The second argument is a callback function that will be fired once setState merges the first argument object with the existing state and rerenders the component. This method of updating, while not preferred by React's developers, more neatly mimics the call-and-response pattern used in API fetch requests.

    
import React from 'react'
import Header from './Header'

export default class Applet extends React.Component {
    constructor() {
        super()
        this.state = {
            myState: 'bored',
            myAppetite: 'hungry',
            sleeping: true
         }
    }

    updateMyState = () => { 
        this.setState({
            myState: 'intrigued',
            sleeping: false
        }, this.updateMyAppetite)
    }

    updateMyAppetite = () => {
        const {sleeping, myAppetite, myState} = this.state
        console.log("Between processes, I am", myState, "and", myAppetite)

        if (!sleeping) {
            this.setState({myAppetite: 'satiated'})
        }
    }

    render() {
        return (
            >div onClick={this.updateMyState} style={{backgroundColor: 'yellow'}}>
                >Header passedState={this.state} />
            >div>
    )}
}

// Header.js

import React from 'react'

class Header extends React.Component {
	render() {
		console.log('Upon child render, I am', this.props.passedState.myState, 'and' this.props.passedState.myAppetite)
		return >div>
			>div>My State is {this.props.passedState.myState} >/div>
		>/div>
	}
}

export default Header
    
  

The code above is bit much, but it will print to the console four times.

  1. On the initial rendering of the Header child based on the initial values of state, it will log "Upon child render, I am bored and hungry"
  2. Once the div element is clicked, three sentence are logged to the console in rapid succession. On the click, the updateMyState function is called, which in turn calls our first call to setState(), which asynchronously stages an update to two of our state keys, switching myState from 'bored' to 'intrigued' and sleeping from 'true' to 'false'. This then re-renders the Header child, which causes the console to log the message "Upon child render, I am intrigued and hungry". This also changes the message in the header's div.
  3. Once the Header has been rerendered (and all previously staged asynchronous processes have cleared), our setState method moves onto its callback function, this.updateMyAppetite. This function logs 'Between processes, I am intrigued and hungry' to the console.
  4. After checking the sleeping Boolean, updateMyAppetite calls another setState method that does in fact finally update the state's appetite value. Another rerender of the header causes another message to log to the console: 'Upon child render, I am intrigued and satiated'

Conclusion

With so many components being constructed, rendered, mounted, and unmounted in a React website, there really is no choice but to set processes up asynchronously. This allows React websites to handle a multitude of event listeners, check for available screenspace, adjust for different browsers, and update the screen dynamically all while keeping the number of component rerenders to a minimum. While updating State values directly is not advised, React provides a number of times during the component lifecycle that state can be updated, in addition to user interaction via event listeners.