Benjamin Seber
14.12.2017Implementing 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 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 callsrender
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 100mssetState
is called which triggers the second render
cycle 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(() => {
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(() => {
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 the
Waiting
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…)
GivenMyDataView
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 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 && !this.state.loading)
?
: null;
}
Furthermore… remember the actual challenge we tried to solve with the Waiting
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 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 Waiting
component 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()} />;
}
}
class FunctionAsChild extends React.Component {
render() {
return {() => this.renderData()};
}
}
The first step is to extend the Waiting
component with a render property. Instead of returning null
when 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();
}
}