Article Image
read

Multiple Async Calls Updating State

In our app, we have multiple asynchronous calls firing off when it first loads up. Each call is updating the state with the information it retrieves, in turn components are receiving new props and triggering their setup process more than once. The first problem we have is the main container component rendering children before it is done gathering the data the children needs.

Unintended Loading User Experience

To fix this we need to manage when the children are rendered.

if ( this.state.setupComplete )
  return <div className='container'>
    { React.cloneElement(this.props.children}) }
  </div>
else
  return <div className='loader'>
    Loading...
  </div>

Don’t render the children until all of the async calls have finished. But how do we know when all of the calls are finished if they all finish at different times? We could try and manage this by contionously updating our state each time an async call finishes checking to see if it should also set state.setupComplete, but I want to avoid component life cycles and only update state when the setup process is completed, not when part of it is completed. For this scenario, I think Promises are a perfect use case, and the handy Promise.all can resolve all of our calls, and it makes handling failures a breeze too.

We start by creating a Promise for each Async call and passing the Promise’s resolve/reject into our async calls. We then resolve all of the promises at the same time using Promise.all. That returns an array of promises, which we will loop through and assign the data to our newState object. We can use setTimeout and have a 2sec delay to avoid our loading screen from flashing in and out.

// Create a Promise for Each Async Call
// For brevity I will include only 2 calls
const userDataPromise = new Promise( (resolve, reject) => { 
  $.get('/user')
    .done( data => { resolve(data) })
    .fail( error => { reject(error) })
})
const layoutDataPromise = new Promise( (resolve, reject) => { 
  $.get('/layout')
    .done( data => { resolve(data) })
    .fail( error => { reject(error) })
})

// Resolve all Promises after a 2s delay
setTimeout( () => {
  Promise.all([
    userDataPromise,
    layoutDataPromise
  ]).then( promises => {
      const newState = { setupComplete: true }

    // Assign promise data to our newState 
      for (let promise of promises) {
        Object.assign(newState, promise)
      }

      this.setState(newState)
    }).catch( failure => {
      // Easily Handle Failures
      const newState = { setupFailed: true }
      this.setState(newState)
    })
}, 2000)

Now let’s take a look at the result of Promise.all managing the Loading State.

YES! Success.

That is the user experience that we were going for! Okay so we have avoided the flashing screen, and have created an extensible setup process, but what kind of performance gains did we get with these changes?

React Performance Tools

To get started measuring Reactjs performance we will use the Performance Tools supplied to us by the Reactjs Team. To install them run npm install react-addons-perf -D and before your ReactDOM.render put the following

window.perf = require('react-addons-perf') //Mount the tools to the Window so we can run in console
window.perf.start()

Now when you run your page and your components have finished rendering, open up the web console and run the following

perf.stop()
perf.printInclusive() // Prints the overall time taken.
perf.printExclusive() // Time excluding mounting
perf.printWasted()    // Time spent on components that didn't render anything

Here are the results BEFORE we did any refactoring Whoa! Look at all of that wasted time!

Here are the results AFTER we implented Promise.all and Loading State Where did the wasted time go!

The biggest gains we can see is in the difference in the amount of instances and the amount of time spent on every component. One other thing you will notice that is missing in the second screenshot is the wasted measurements. That is because we are no longer wasting anytime in the components! Thanks to no longer rendering our components until the container is ready. We have created a highly performant and easily mangeable loading state using Promise.all and not rendering our children until they are ready.

Blog Logo

David Patrick


Published

Image

dponrails

Ruby on Rails, React, Angular, Javascript - The Exploration of One Developer

Back to Overview