Most of the time our front-end applications interact with a wide range of services and APIs for populating and displaying the necessary data. We usually display loading screens for the same and we make the user wait a certain amount of time before we actually allow them to use the page. But sometimes most of the necessary content for the user is available but the user is made to wait for the unnecessary data on the page to load. This is very bad when it comes to user experience perspective.
Consider this scenario, You are opening a blog link. The text loads much faster but then the page doesn't allow you to navigate until the pictures and side links are loaded. Instead the page can allow you to navigate while the pictures and other things load simultaneously.
One of the ways of tackling this issue in react is to use an asynchronous wrapper for rendering the component. Let's take two components HeadingComponent
and ParagraphComponent
.
const HeadingComponent = props => <h1>{props.data}</h1>;
const ParagaphComponent = props => <p>{props.data}</p>;
We will now create the AsyncComponent
that acts a wrapper for the HeadingComponent
and ParagraphComponent
which displays data from two different APIs.
class AsyncComponent extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
resolvedError: false,
resolvedSuccess: false,
data: '',
error: '',
};
this.renderChildren = this.renderChildren.bind(this);
}
componentDidMount() {
this.props.promise()
.then(data => this.setState({ resolvedSuccess: true, data }))
.catch(error => this.setState({ resolvedError: true, error }));
}
renderChildren() {
return React.Children.map(this.props.children, child => (
React.cloneElement(child, {
data: this.state.data,
})
))
}
render() {
if (this.state.resolvedError) {
return <h1>Error Encountered</h1>;
} else if (this.state.resolvedSuccess) {
return <div>{ this.renderChildren() }</div>;
} else {
return <h1>Loading...</h1>;
}
}
}
The AsyncComponent
takes a prop called promise
that it calls from componentDidMount
. If it resolves successfully it stores the data in the state and error in case of a rejection. Then in the render the method we render
- Error component incase of error
- The child nodes if resolves successfully
- Loading component otherwise
Sometimes the child components needs the response data. React doesn't allow us to get the component directly from the child elements so we use React's inbuilt functions like React.Children.map
and React.cloneElement
. We traverse the children of the component and we clone each child element by adding a prop data
which has the actual response from the API so that the response is accessible to children as well.
Final piece of code that puts all of the above together
const HeadingAPI = () => new Promise((resolve, reject) => {
setTimeout(() => resolve('Heading'), 5000);
});
const ParagraphAPI = () => new Promise((resolve, reject) => {
setTimeout(() => resolve('Paragraph data'), 2000);
});
const App = () => (
<div>
<AsyncComponent promise={HeadingAPI}>
<HeadingComponent />
</AsyncComponent>
<AsyncComponent promise={ParagraphAPI}>
<ParagaphComponent />
</AsyncComponent>
</div>
);
Here's a Codepen running the scenario with both the promises resolving successfully.
Here's a Codepen running the scenario when one of the promises is rejected.
As you can see the failure of one API doesn't affect the rendering of the other component and the user can continue navigating the webpage regardless. This greatly improves the user experience and also reduces the amount of redundant code created by API calls across components.
You can still improve the wrapper by giving custom loader and error components to make it look more fancy.
Top comments (8)
This seems an interesting way to handle promises in the components. Thanks for sharing :)
We were handling the promise results in every component with utilizing an higher component that is dedicated to that component. This seems an amazing way to reduce code replication
Yes the very same problem I faced and I was thinking how to come up with the solution to improve User Experience while reducing the redundancy. I arrived at a wrapper for that. But this wrapper can still be improved in a lot of ways.
For lower level control you can even make AsyncComponent pass render props.
Yes absolutely we can it would be better solution than this actually.
Thanks, nice idea
Thank you. :D
Great article Adarsh, I will definitely follow this for my future reference. Thanks for Sharing :)
Bookmarked ;)
Thank you Koushik. :)