DEV Community 👩‍💻👨‍💻

Adarsh
Adarsh

Posted on

React - Asynchronous Component Rendering Wrapper

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.

Top comments (8)

Collapse
musyilmaz profile image
Mustafa YILMAZ

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

Collapse
sadarshannaiynar profile image
Adarsh Author • Edited on

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.

Collapse
zeerorg profile image
Rishabh Gupta

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

Collapse
sadarshannaiynar profile image
Adarsh Author

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

Collapse
glebirovich profile image
Gleb Irovich

Thanks, nice idea

Collapse
sadarshannaiynar profile image
Adarsh Author

Thank you. :D

Collapse
kousikpaul4u profile image
Koushik Pal

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

Collapse
sadarshannaiynar profile image
Adarsh Author

Thank you Koushik. :)

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.