Loading…widgets in react-redux application

I got an interesting requirement for my React app to display multiple widgets with the following conditions:

  1. Number of widgets to displayed – UNKNOWN
  2. Content to be displayed – UNKNOWN
  3. API endpoints – UNKNOWN
  4. Only known thing was the placeholder in the widgets where data needs to be displayed
  5. Need to display loader for each API call

Fortunately, an API was available to get details such as the number of widgets and their endpoints.

As per the requirement to display loader for each API call, the first screen was:

main-widget
Initial Loader screen

When the first API returns the response containing details regarding widgets such as the number of widgets and their endpoints, the above widget should turn into the following screen if there are 4 widgets data returned by initial API call:

widget-loaders
screen 2

And each of the widgets will call different APIs to get the data it needs to display. Most important,
some of the widgets were supposed to call more than one APIs to get the data. And after all these

My destination screen should look like:

Screen 3

How to start??

Let’s start with some coding:

To display initial loader screen, as it involves an API call, I had to make it a container component.
Along with it’s seperate reducer, actions and action creators.

Note: In this blog, Whenever I say created a container component, it means container along with its reducer, actions, and action creators.

In JSX, it should be like:

 componentDidMount() {

   { /** API call to get initial widget screen */ }
   this.props.getWidgets() 
}

render() {
   if ( this.props.isLoading === true ) {

     { /** A component rendering loader image */ }
     return <Loader />; 
   } else {
     return (
       {
          this.props.widgets.map((widget) => {
            return <Widget data={ widget } />;
          })
       }
    )
  }
}

Above code indicates, display loader till we get API response, that is till, isLoading set to False. And once API response is returned by initial API call, display each of the widgets.

The action dispatched will be:

 
dispatch({ 
  type: "FETCH_WIDGETS", 
   payload: { 
     isLoading: true 
   } 
}) 

The dispatch() function is the only way to trigger a state change in the redux store.

Now I want to render the individual widgets, with it’s data from various API calls.

The first Challenge I had was, how to organize each widget component, which was supposed to talk to different APIs.

I thought of three ways in which I could do this:

  1. Render all the widgets in one container component, and call all the APIs simultaneously
    • Pros:
      • Able to achieve the goal
    • Cons
      • Difficult to handle responses returned by multiple APIs
      • If one of the API failed, none of the widgets will load
  2. Keep a common data-store for all the widgets
    • Cons
      • End up loading last API response data in all the widgets
  3. Render each widget as a container component, in the main container
    • Pros
      • Each of the widgets will handle their own API call, so if one of the fails, none will be affected
      • Easier to handle API response individually
      • Achieve the goal

So, I decided to go with the last approach, to create Container component for each of the widgets, which will display component till, it gets a response from API call, same as initial loader screen,

renderWidget( widget, index) {

  switch `${widget.name}` {
    case 'widget 1':
      return <Widget1Container />
    case  'widget 2':
      return <Widget2Container />
    case  'widget 3':
      return <Widget3Container />
    default:
      return <Widget4Container />
  }
}

 

  if ( isLoading === true ) {
    return <Loader /> 
  }
  else {
    return ({
      this.props.widgets.map((widget, index) => {
        return this.renderWiget( widget, index)
      })
    })
  }

And each individual widget container component will have the same logic as the main container to display the loader. that is,

componentDidMount() {
  /** API call to get widget1 data */
  this.props.getWidget1()
}

render() {
  const { isLoading, widget1 } = this.props;

  if ( isLoading === true ) {
    { /** A component rendering loader image */ }
    return <Loader /> 
  } else {
    return <Widget data={ widget1 } />
  }
}

And we got the screen 2 as well, from the above three images.

The last challenge I had was, how to handle multiple API calls per widget?

The JavaScript Promise.all() came to the rescue, due to which I could be able to manage those API calls as follows:

In each of the widgets action creator, after initiating loaders, we need to call those APIs:

Promise.all([
  fetch(‘/api1’),
  fetch(‘/api2’),
  fetch(‘/api3’)
]).then((responses) => {
  console.log(response[0], response[1], response[2]) 
  /** responses in the same sequence as API call*/
}).catch((errorResponse) => {
  /**Error handling if one of the APIs failed*/
})

Now, the last task remaining was to convert accumulated reponse from various API calls to a format best suited for the UI component.

For this, I divided the individual widget into small parts, structured the redux store as UI. And updated the store with calculated values from API response.

And rendering a common presentational component inside each widget container component, that is:

import React, { PureComponent } from ‘react’;

export default class Widget extends PureComponent {
 render () {
   const { data } = this.props;
   return(
     <div> 
       <div>{ data.widgetTitle }</div>
       <div>{ data.widgetBody }</div>
       <div>{ data.widgetFooter }</div>
     </div>
   )
 }
}

This is how I reused the above component in each of the widgets.

Take Aways:

  1. When to create container component and when to create Presentation component
  2. Handling multiple API calls
  3. How to create reusable components

I hope this will help you.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s