React - Asynchronous Component Rendering Wrapper

Adarsh - May 9 '18 - - Dev Community

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>;
Enter fullscreen mode Exit fullscreen mode

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>;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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

  1. Error component incase of error
  2. The child nodes if resolves successfully
  3. 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>
);
Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . .