Handling Rendering with Loading, Empty, Error States for React Components

Better component state indication = Happier user.

Kenneth Cheung
< />

--

The Big Picture

In react development we often face a problem of dealing with different states for some data-driven components. In most cases, those states include:

  • Ideal State. The happy path for the component, everything is fine.
  • Loading State. Component shows something to indicate it is loading.
  • Error State. Component shows something went wrong.
  • Empty State. Component shows something to indicate it is empty.

For those components, you would like to show a proper hint to users base on state of the component. The code may look something like the following:

container.js

import MyComponent from 'path/to/my/component';
import ErrorHint from 'path/to/error/hint';
import LoadingSpinner from 'path/to/loading/apinner';
import EmptyHint from 'path/to/empty/hint';
class Container extends React.Component {
render() {
return (
// ...
{
isComponentError
? <ErrorHint />
: isLoading
? <LoadingSpinner />
: data.length > 0
? <MyComponent data={ data } />
: <EmptyHint />
}
// ...
)
}
}

The Problems

The code above is not ideal, because

  1. Nested Ternary operator. If there are several components all implement this kind of logic, it is not easy to understand at a galance.
  2. Spreading logics. This kind of similar logic can be generalized and be handled in a single place instead of spreading all over the code base.
  3. Verbose importing. If <ErrorHint />, <LoadingSpinner />, <EmptyHint /> are the same across the whole project, you still have to import all of them to wherever they are used. It makes the code more verbose.
  4. Lower cohesion. If <ErrorHint />, <LoadingSpinner />, <EmptyHint /> are specific for the component, then they should be located in the component.js instead of in the container.js for higher cohesion.

To address these problems, I think Provider Pattern would be a good solution. Provider provides global Loading, Empty, Error Components and uses Higher-Order-Component to wrap the component you would like to implement render logic. Like the following,

index.js

<RenderCtrlProvider
ErrorComponent={ () => <div>default error hint</div> }
EmptyComponent={ () => <div>default empty hint</div> }
LoadingComponent={ () => <div>default loading hint</div> }
>
<YourApp />
</RenderCtrlProvider>

YourComponent.js

class YourComponent extends Component {
//...
}
export default withRenderCtrl(YourComponent, {
// your customized loading for this component
LoadingComponent: () => <div>I am loading</div>
});

container.js

class Container extends Component {
// ...
render() {
return (
// ...
<YourComponent
isError={ isComponentError }
isLoading={ isLoading }
isDataReady={ data.length > 0 } // or other logics indicate data is ready
// other props of "YourComponent"...
/>
// ...
);
}
}

A npm package

react-render-ctrl alleviates the problems we mention above. It implements the pattern based on the rendering logic below,

Squares with gray background are components

The implementation details can be found in the src folder of the project.

If you have to handling different states for components across the project, you can give it a try:)

Conclusion

Provider Pattern and Higher-Order-Component provider an interface of what a component-render-logic should look like to simplify how we work with different UI states. If you have different idea for making the rendering flow logic more flexible or general, please share with me, thanks!

--

--