Implementing a waiting component with user experience in mind

Giving fast feedback to users has been improved by single page applications over the request response cycle. However, there is one serious downside with this approach. Elements are popping out of the wild on various sections everytime. Particular data loading indicated by a waiting animation is affected with this phenomenon. In this blog I’d like to present you our solution of a UI component that takes care about delaying the rendering of the animation.

Disclaimer: we’re using React in our frontend (without server side rendering). In case you don’t know React: React provides lifecycle hooks for UI components like

  • render
  • willUpdate
  • or .didUpdate

These hooks can be used to do internal stuff your component requires to be rendered correctly. React components can either be updated with changing propertiesor updating state. Properties are actually the public API of the component. Thestate, however, is the antagonist which can only be updated by the component itself. Changing properties or state triggers specific lifecycle hooks and finally a rerendering of the component. Don’t hesitate to read the react docs for more detail.

tl;dr source code is available on github.

loading or not loading

At first we have to satisfy the basic need. The user must get feedback whether we’re loading data currently or not. The most simple component takes a boolean property that reflects the current state.

class Waiting extends React.Component {
  render() {
    return this.props.loading ? <div>loading...</div> : null;
  }
}

This component can now be used in our App. The loading info is visible as long as theloading flag is set to true and hidden as soon as the flag is toggled.MyDataView is just another component that takes care about rendering the data.

class MyApp extends React.Component {
  // initialState
  // no data existent and we're loading currently
  state = {
    data: null,
    loading: true
  };

  renderData() {
    return this.state.data ?  : null;
  }

  render() {
    return (
      <div>
        
        {this.renderData()}
      </div>
    );
  }
}

One benefit of this solution is that we now have a reusable component. We don’t have to care about the visualisation stuff anymore at every place. It could render the div element with a static text or it could render some more advanced css animation. For instance we could change the loading animation to use this awesome codepen with refactoring theWaiting component implementation only. Consumers of the Waiting component wouldn’t have to be touched.

A second benefit is the really simple implementation of the Waiting component. Even without knowing React or JavaScript in detail you quickly see that a div or nothing is rendered.

pretend not loading when it’s fast

The next step is user experience improvement. We don’t want to render the loading text
when the loading flag is toggled back to false within 100ms.

0.1 second is about the limit for having the user feel that the system is reacting instantaneously, meaning that no special feedback is necessary except to display the result.

Jakob Nielsen

To keep changes small let us first map the loading property value to the internal component state. React takes care about calling render when either new properties are given or state is changed with setState. So in the constructor we’re mapping the original loading flag to render the initially intended state. Let’s say the yep, we’re currently loading state. Soon afterwards the property will eventually swap to nop, we’re finished loading. This can be intercepted by the componentWillReceiveProps lifecycle hook . Just like in the constructor we’re mapping the property to the internal state.

class Waiting extends React.Component {
+  constructor(props) {
+    super();
+    this.state = { loading: props.loading };
+  }
+
+  componentWillReceiveProps(nextProps) {
+    if (nextProps.loading !== this.props.loading) {
+      this.setState({ loading: nextProps.loading });
+    }
+  }
+
  render() {
-    return this.props.loading ? <div>loading...</div> : null;
+    return this.state.loading ? <div>loading...</div> : null;
  }
}

So far we’ve gained nothing but complexity /o\

Now to the interesting part. As soon as theWaiting component receives new properties we’re starting a timeout to update the internal state with a delay of 100ms. Remember react callsrenderon property changes as well as on state changes. Sorender is called two times now actually. The first time it renders the same as previously nop, we’re not loading. After 100mssetState is called which triggers the second rendercycle yep, we’re loading.

class Waiting extends React.Component {
  constructor() { ... }

  componentWillReceiveProps(nextProps) {
    if (nextProps.loading !== this.props.loading) {
+     window.clearTimeout(this._loadingTimeout);
+     this._loadingTimeout = window.setTimeout(() =&gt; {
        this.setState({ loading: nextProps.loading });
+     }, 100);
   }

   render() { ... }
 }
}

But wait, what’s happening now when the loading property is swapped the other way around from yep to nop? Remember the implementation of MyApp from above?

class MyApp extends React.Component {
  // ...
  render() {
    return (
      <div>
        
        {this.renderData()}
      </div>
    );
  }
}

TheWaiting component receives the updated loading flag false and delays it’s internal rendering while this.renderData() renders the actual data. So the loading info is shortly visible amongst the data. Fortunately this can be fixed easily. We just have to update immediately when the loading property is set to false.

class Waiting extends React.Component {
  constructor() { ... }

  componentWillReceiveProps(nextProps) {
    if (nextProps.loading !== this.props.loading) {
      window.clearTimeout(this._loadingTimeout);
+     if (nextProps.loading) {
        this._loadingTimeout = window.setTimeout(() =&gt; {
          this.setState({ loading: nextProps.loading });
        }, 100);
+     } else {
+       this.setState({ loading: false });
+     }
    }
  }

  render() { ... }
}

Now we’ve gained a good user experience by not displaying the loading info if the loading property is toggled from yay back to nop within 100ms. There is no flickering anymore \o/ However, we’ve payed with some complexity in theWaiting component and even have async stuff happening there. So testing consumers of the Waiting component could be confusing. But in my opinion the better user experience is worth the complexity and tests should be fine as long as shallowRendering is used. Otherwise we have to use the timemachine feature of the testing library (e.g. jest provides jest.useFakeTimers() and jest.runTimersToTime(100))

improved handling of data rendering

Currently we have a waiting component that takes care about delaying the loading info. But the consumer is still responsible to check itself whether the data is available and should be rendered or not.

renderData() {
  return this.state.data
    ? 
    : null;
}

However, my collegues and my humble self could live with this redundancy actually. It is explicit and the waiting component wouldn’t be bloated with more features and complexity. But in our project we had the following issue (amongst some others…)

GivenMyDataViewrenders a list of items with a headline and other eye candy stuff. It takes care about rendering a no data info banner when the given list is empty. The default this.state.data value is an empty array instead of undefined or null to avoid the notorious Cannot read property XXX of undefined. Then the code snippet above results in always rendering MyDataView and therefore the no data info banner (empty array is a truthy expression).

The unwanted no data info banner could be avoided by adding the this.state.loading flag to the condition. But that’s not really satisfying since this adds more complexity which even will be copied and pasted into other components.

renderData() {
  return (this.state.data &amp;&amp; !this.state.loading)
    ? 
    : null;
}

Furthermore… remember the actual challenge we tried to solve with the Waitingcomponent which delays the rendering of the loading info? Exactly, we wanted to avoid flickering and displaying the loading info when the data is received within 100ms. Now we’ve added this again for MyDataView. The component will be unmounted and mounted within 42ms for instance. The new data is visible but all eye candy around the data list (like the headline) is gone and rerendered within one blink of an eye.

So let’s improve the Waitingcomponent to handle the rendering of it’s children. We have two react techniques to implement this:

  • render props
  • function as child

Both are the same actually. The render prop pattern uses a function passed as component property to render something. The function as child pattern is… well… the same. children is just an additional property of a React component. The difference between render props and function as child is the syntax. Personally I prefer render props since this is more explicit and doesn’t leave room of misconception for people not knowing React and JSX in detail.

class RenderProps extends React.Component {
  render() {
    return  this.renderData()} /&gt;;
  }
}

class FunctionAsChild extends React.Component {
  render() {
    return {() =&gt; this.renderData()};
  }
}

The first step is to extend the Waitingcomponent with a render property. Instead of returning nullwhen data is not loading we have to call this.props.render.

class Waiting extends React.Component {
  constructor() { ... }

  componentWillReceiveProps(nextProps) { ... }

+  renderContent() {
+    return this.state.loading ? <div>loading...</div> : this.props.render();
+  }
+
  render() {
-    return this.state.loading ? <div>loading...</div> : null;
+    return this.renderContent();
  }
}