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 properties
or updating
. Properties are actually the public API of the component. Thestate
, however, is the antagonist which can only be updated by the component itself. Changing state
or properties
triggers specific lifecycle hooks and finally a rerendering of the component. Don’t hesitate to read the react docs for more detail.state
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.
This component can now be used in our App. The loading info is visible as long as the
flag is set to loading
and hidden as soon as the flag is toggled.true
is just another component that takes care about rendering the data.MyDataView
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 the
component implementation only. Consumers of the Waiting
component wouldn’t have to be touched.Waiting
A second benefit is the really simple implementation of the
component. Even without knowing React or JavaScript in detail you quickly see that a div or nothing is rendered.Waiting
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
flag is toggled back to false within 100ms.loading
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.
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
. 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 setState
lifecycle hook . Just like in the constructor we’re mapping the property to the internal state.componentWillReceiveProps
So far we’ve gained nothing but complexity /o\
Now to the interesting part. As soon as the
component receives new properties we’re starting a timeout to update the internal state with a delay of 100ms. Remember react callsWaiting
on 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 100msrender
is called which triggers the second setState
cycle yep, we’re loading.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
from above?MyApp
The
component receives the updated loading flag false and delays it’s internal rendering while Waiting
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 this.renderData()
property is set to false.loading
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 the
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 Waiting
and jest.useFakeTimers()
)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.
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…)
Given
renders 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 MyDataView
value is an empty array instead of undefined or null to avoid the notorious this.state.data
. Then the code snippet above results in always rendering Cannot read property XXX of undefined
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
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.this.state.loading
Furthermore… remember the actual challenge we tried to solve with the
component 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 Waiting
. 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.MyDataView
So let’s improve the
component to handle the rendering of it’s children. We have two react techniques to implement this:Waiting
- 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.
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.children
The first step is to extend the
component with a render property. Instead of returning Waiting
when data is not loading we have to call null
this.props.render
.