I run into this scenario all the time: I have a React component, it pulls content from an API. I need to make a call to get new content, and display a visual indicator that the content is loading; i.e. the user’s input has been acknowledged, and we’re just waiting for the content to download. The first couple of times I did this, my instinct was to declare a loading property on the component’s state, then swap out some loading animation or text for the actual content when API calls were being made.

The problem was once I had two components that needed to show loading animations, the code wasn't reusable and the markup got pretty messy. So the obvious solution is to create a functional loader component.

Here’s an example implementation. This is the interface component:

It loads a random image from Unsplash based on the search parameter passed in. Depending on how fast your internet connection is, it either loads a new image almost right away, or it hangs awkwardly after you hit “return” in the search field.

So to make it a little more obvious what’s going on, I wanted to show a loading animation while the image loads. This is the loading animation:

The loader is pretty simple, it has some markup that creates the loading animation:

  const loader = (
  <div className="loader component" key="loader">
  <div className="loader__dots">
    <span className="loader__dots__dot"/>
    <span className="loader__dots__dot"/>
    <span className="loader__dots__dot"/>
  </div>
</div>
)      

and a render method that is simply:

  return (<div>{loading ? loader : children}</div>);

That is, if the loading property is true, show the loading animation, if not, show the children. You wrap any components that need to show the loading animation with this component:

  <Loader loading={this.state.loading}>
  <div className="component" key="component">
    <form onSubmit={this.search}>
      <input className="search" type="text" placeholder="search" ref="search"/>
    </form>
  </div>
</Loader>

The main component (the Unsplash image search) is handling all of the calls for a new image, so it knows when it is loading and when it isn’t. This is a relatively simple example, but the image search method looks like this:

  getImage(search='flower'){
  this.setState({
    loading: true
  });
  let img = new Image;
  let src='https://source.unsplash.com/featured/?' + search;
  img.src = src;
  img.onload = (e)=>{
    this.setState({
      image: src,
      loading: false
    })
  } 
}

As soon as you call getImage it sets the loading state to true, and once the image has loaded (whenever that might be) it sets the loading state to false. You can do this with anything async – promise.then being the most likely use case.

This solves the problem of showing that the component is doing something when the user submits a new search term, but switching from image to loader to image (especially if the image loads very quickly) is jarring. So I added in ReactCSSTransitionGroup, and wrapped the loader with it. The loader’s render method now looks like this:

  return (
  <ReactCSSTransitionGroup 
    className="stage"
    transitionName="crossfade"
    transitionAppear={true} 
    transitionAppearTimeout={500}
    transitionEnterTimeout={500} 
    transitionLeaveTimeout={300}
    component="div"
    aria-busy={loading} // easy aria-role attribute while we’re here
  >
    {loading ? loader : children}
  </ReactCSSTransitionGroup>
);

The crossfade transition fades elements down to 0 opacity. This does require that the loader and the children be position absolutely (so they fade in and out on top of each other), but could easily be replaced with another animation when necessary. The finished product looks like this:

If the images are loading very quickly for you and you want to see the flow in action, you can adjust your network speed. In Chrome, open the Developer Tools, go to the Network tab, and set Network Throttling to something slower like a 2 or 3G connection. Run two or three searches and you’ll get plenty of time to watch the fade-in and fade-out of the loading animation – and a pretty good idea of why you shouldn’t load random full-sized images from Unsplash!


3,369 3 30