Knowing when the UI is ready in React / Redux

Viewed 2626

I would like to know how long it takes my app to become "ready" for the user to interact with it. The timeline of the app loading includes the following:

  1. DOM load.
  2. Initial React render.
  3. HTTP calls triggered from various componentDidMount()s
  4. HTTP calls return. Redux state is updated with HTTP responses.
  5. React renders with new values from HTTP responses.
  6. All initial loading is complete. The user is ready to interact with the app.

At the end of this process, I'd like to fire a tracking event to record the value of window.performance.now().

I'm not sure what the best way to do this is. React's component system does a good job of decoupling various parts of the UI. In this case, it, unfortunately, means that I'm not going to have one easy place to check to know "has everything been rendered with the new data".

Things I've tried or considered:

  1. Look for a React lifecycle hook that tells me if any child is updating. Something like componentOrAnyChildDidUpdate(). I don't think this exists, and it may be counter to the React philosophy.
  2. Hack together an anyChildDidUpdate() lifecycle hook via context and making every component in my app either subclass a helper abstract base class or be wrapped in a higher-order component. This seems bad because context is an experimental API.
  3. Subscribe to the Redux state via store.subscribe(). Update the Redux state to have an explicit record of whether all the HTTP calls have returned. Once all the HTTP calls have returned, and React is finished re-rendering, then I know to fire the tracking callback. The problem is knowing when React is finished re-rendering. It would work if my store.subscribe() the callback was guaranteed to be executed synchronously after react-redux's callback. But that is not the case.

Is there a good way to do this in React?

6 Answers

I'm afraid there is not a universally good way to do this in React, this is "logic" that is related with the structure of your application.

I wanted to display a loader when navigating from one page to another in Single Page Application(SPA) written in react and I faced a similar problem not knowing when to stop displaying it and if all the components in the new page have completed their API calls/renders.

The way I resolved it was:

1) I removed all my API calls from inside my components.

2) Create a Page component to wrap all the components included in that page(invoked by your react router).

3) Perform all requests required for your components simultaneously on navigation(in my case while displaying the loader). For each request that completes, create a promise and inside it create your component dynamically using React.createElement. Pass to the component created the request response and the promise handler as props.

4) Resolve the promise in your component's componentDidMount.

5) Once all the promises are resolved you know the "page" is ready and you can record the value of window.performance.now().

It is hard to provide a minimal code example without a lot of out of context code since this code is spread across the application.

There's more than one way to do what you're asking - but off the top of my head I would do the following:

• Create a perf reducer and call performance.now() right before I make the redux store and add the value to the initial state.

{ perf: { start: '...', end: '...' } }

• Track loading status of initial HTTP requests in a loaded reducer.

{ loaded: { request1: false, request2: false, request3: false } }

• Connect top level component to loaded reducer and check if all requests are complete in componentDidUpdate. If true add end value to perf reducer.

import React from 'react';
import { connect } from 'react-redux';

class App extends React.Component {
  componentDidUpdate(prevProps) {
    if (!prevProps.loadingComplete && this.props.loadingComplete) {
      this.props.updatePerf(performance.now());
    }
  }
}

const mapStateToProps = state => ({
  loadingComplete: state.loaded.every(item => item),
});

const mapDispatchToProps = dispatch => ({
  updatePerf(time) {
    dispatch({ type: 'SET_ENDING_PERF', payload: time });
  },
});

export default connect(mapStateToProps, mapDispatchToProps)(App);

In our project, we use redux and a couple of actions - trigger, request, success, error.

"Trigger" calls request and throw loading: true into our "smart" component "Request" fetch data and throw them into Success/Error "Success", tells us that everything is fine and data loaded and throw { loading: false, data }

On success/error, we always know that is happening with our application

And of course you can use componentWillReceiveProps(nextProps) {} and check loading flag and data in your component

You can try to use react-addons-perf

That's how I benchmark my apps usually:

Basically you want to start measuring when your component starts updating and stop the measurement after the lifecycle method componentDidUpdate().

import Perf from 'react-addons-perf'

componentWillMount() {
  window.performance.mark('My app');
 }
componentDidMount() {
  console.log(window.performance.now('My app'));
  Perf.start()
  // ... do your HTTP requests
}
componentDidUpdate() {
 Perf.stop();
 Perf.printInclusive();
}

Like that you will track your component initial render + your component updates.

Hope it helps

Two steps to achieve this.

Track all HTTP-requests via actions

Simple counter in redux state would be enough, that increments on start of request and decrements on success or fail (all via appropriate actions). No window.dirty.stuff here in front of Dan Abramov.

Track all connected components

componentDidUpdate method indicates that all children finished rendering. This can be achieved without much boilerplate with recompose's lifecycle HOC. Right place to start tracking re-render might be sCU method or just cWRP if you use recomposes shouldUpdate or reselect already - since we won't track components that didn't update.

Note that all is assuming idiomatic react/redux app, no tricky side-effects or animations. For example, if some child component somewhere deep in the hierarchy fires its own AJAX request, say on cDM, not via redux action. This way you have to track them too, BUT no one can be sure if they imply any re-renders. Tracking such behaviors is still possible, just might require more effort.

from a design POV, in my opinion being ready or not is a user presentation property and so should be handled by react components alone.

for example, you may later decide to allow the user to interact with some part of your UI while some other parts are still loading, or you may decide that some components should allow the user to interact before the data model is ready, etc ... in other words, it's a (react) component responsability to tell if/when it's ready or not and nobody else.

That is, all 'waitable' components could expose an onLoad-like prop to be passed up the react tree; the root component would then coordinate the result by changing the tree down stream accordingly by propagating the appropriate props down to leaves.

This could be implemented by writing a mixin/high order component to incapsulate the "readyness update" logic.

Related