React - Asynchronous Component Rendering Wrapper
Adarsh

Adarsh @sadarshannaiynar

About: A Software Engineer with the mind of a curious kid who likes to explore and deep dive into frameworks, libraries and computers in general.

Location:
Los Angeles, CA, USA
Joined:
Jan 11, 2018

React - Asynchronous Component Rendering Wrapper

Publish Date: May 9 '18
77 8

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.

Comments 8 total

  • Rishabh Gupta
    Rishabh GuptaMay 10, 2018

    For lower level control you can even make AsyncComponent pass render props.

    • Adarsh
      AdarshMay 10, 2018

      Yes absolutely we can it would be better solution than this actually.

  • Mustafa YILMAZ
    Mustafa YILMAZMay 10, 2018

    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

    • Adarsh
      AdarshMay 10, 2018

      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.

  • Koushik Pal
    Koushik PalMay 11, 2018

    Great article Adarsh, I will definitely follow this for my future reference. Thanks for Sharing :)
    Bookmarked ;)

    • Adarsh
      AdarshMay 11, 2018

      Thank you Koushik. :)

  • Gleb Irovich
    Gleb IrovichNov 13, 2018

    Thanks, nice idea

    • Adarsh
      AdarshNov 13, 2018

      Thank you. :D

Add comment