Skeleton Loading State as a System

Nathaniel Kessler
Jun 9, 2020 · 7 min read

This is a walkthrough on how to implement a skeleton loading infrastructure into a front-end application. The approach builds skeleton states as a system so your front-end can scale while maintaining consistency for loading states. I’ll use React, React Context and Hooks for this example.

Basic Idea

Implement a “skeleton state” for the lowest level components in your UI. Then use the state management feature of your chosen framework to toggle on and off the skeleton state when necessary.

Image for post
Image for post
Image for post
Image for post

Since higher level components are composed of low level components, each new component moving forward (or upward) gets a skeleton state view for free. It’s this concept that allows an application to scale up without the technical overhead of building out unique skeleton states for each new component while the application grows.

Image for post
Image for post

Low level components

Good low level components to start with are <Text/>, <Image/>, and <Icon/>. These commonly found components behave as expected, i.e., text renders text, image renders an image, etc. But in this approach, they will all be built with their own subsequent “loading” view that can be toggled on and off.

Text Component

By default, the text component will render whatever text and styles are passed in. If the prop skeleton is passed in as true, it will render a bar with a grey background and rounded edges like the image examples from above.

Image component

The image component follows the same principle. It will render an image with whatever source is passed in. But if the skeleton prop is true, it will render a grey box to denote “loading”.

High level components

As I mentioned before, higher level components are composed out of the low level components and therefore come packaged with a subsequent skeleton view. A <DetailsCard/>, for example, might look like this.

Notice how code for the skeleton state isn’t required for this component, yet a skeleton view is immediately available? It’s not exactly obvious that a skeleton state is available since there’s no mention of it in the code, but that’s partly the idea. This component is composed of “skeleton-enabled” components, therefore becoming “skeleton-enabled” itself. Any new components that are built as the application evolves get on-demand loading states for free.

Scalability is also notably achieved because managing the look and feel of your application’s skeleton state happens in a completely different area of the app (low level components).To illustrate this point, imagine introducing an animation effect on all the skeleton states across an application. By applying this to a low level component, the animation effect immediately gets applied to every component.

That’s really it for the bones of this approach. The next part goes into how skeleton states are toggled on and off.

Communicating state

Consider an application that has a search field to find github repos. Entering a term in the search field kicks off an API request to github. The response comes back in the form of a list that populates below the search field. While the request is on its way out, and back, the UI reveals a skeleton state to help the user understand what’s going on.

Image for post
Image for post

A simple implementation of this in React might have a <Header/> component for the title area, a <Search/> component for the search box, and a <RepoList/> component to render the search result.

Note that the onSubmit callback on line 25 fires the handleSearchSubmit function to make the API request. Line 7 sets the local isLoading state to true when the request starts, and returns to false on line 19 when the request completes. On Line18, the response from the API request is stored in the gitRepos local state. Lastly, the gitRepos and isLoading states are passed into the <RepoList/> component.

The <RepoList/> component, which renders the results from the search query, takes an array of data and renders a list of <DetailsCard/> components. It also takes an isLoading prop to understand when data is being fetched. In the example below, it swaps the real data for a list of fake placeholder data (line 11) when isLoading is set to true.

Passing data downwards

While the API request is in progress, you’ll need to tell the low level components to enter their skeleton states. In React, you can pass props downwards but this would require prop drilling in order to reach the low level components. It’s very cumbersome to keep track of props as more and more components are nested, which is why prop drilling is generally not advised.

Image for post
Image for post

Instead, we’ll use React Context. With React Context, you can share state with any child component regardless of how many levels deep.

Image for post
Image for post

To do this, we’ll create a new file called skeleton.js. It has a SkeletonContext variable to store a boolean value for toggling on and off the skeleton state. And a <SkeletonArea/> provider component to share the state.

Next we’ll wrap the entire application in app.js with the new <SkeletonArea/> component (lines 22 and 28 below) and pass in the isLoading property. By passing in the isLoading property to the provider, you’re effectively sharing that value as global state for all of its child components.

Lastly, we’ll update all the low level components with the new context provider so they know when to enter their “skeleton” state. Each file below has a new boolean variable called isLoading that is set by the context provider.

Isolated skeleton areas

There is an issue with this solution. Since all the components are using the same state you might get a skeleton view for an area of a page that should not be associated with “loading”. The header here, for example, should not enter a skeleton state when the search response is being fetched (“loading”).

Image for post
Image for post

To get around this, you can nest React context providers at each point where the boundary of a group of components should share the same skeleton state. In the diagram below, the provider only wraps the list of results which creates an isolated area of global state for that particular tree of components.

Image for post
Image for post

Let’s update the code to isolate the list results, like the diagram above. Since we only want the skeleton state to appear on the results list, we’ll remove it from app.js and add it to the <RepoList/> component.

Now we have the desired behaviour. Only the list area shows a skeleton state.

Image for post
Image for post

UseSkeleton Hook

Everything works now, but the code needs to be packaged up for reuse. For that, I’ll capture the pattern details in a React hook and expose a small API for reuse. I’ll call the hook useSkeletonData and the API for the hook will take 3 parameters.

  • fakeData: this is data for components to consume while they’re in a skeleton state. Think of it as placeholder data.
  • realData: this is data for components to consume while they’re not in a skeleton state.
  • isLoadingPredicate: this takes a function that returns a boolean. Its purpose is to remotely control when any low level child components should enter their skeleton state.

The hook (line 18) will return a data object, either real or placeholder, depending on the predicate function described above, and a “connector” object. (The connector is a necessary piece that allows the hook to control the skeleton state of the low level components).

With the new hook available, I’ll apply it to the <RepoList/> component.

Line 16 takes the real data, line 17 takes the fake data, and line 18 takes the predicate function. In this case it’s returning the isLoading prop to control the skeleton state. Lastly, line 10 has the connector object passed in the context component.

Back on the skeleton.js file, I’ll create one more hook for the low level components (line 16 below), useIsSkeletonLoading.

Any low level components moving forward will be able to use this hook instead of importing React Context directly to toggle on and off their skeleton view. Here’s how the <Text/> component would be updated.

All the other low level components would be updated as well.

Sandbox

We’ve solved a number of complicated problems:

  • On demand skeleton components
  • Isolated skeleton areas
  • Reusable API

This sandbox has a running example.

Thanks for reading ❤

- —— -
Nathaniel is a Solution Architect at Rangle.io

JavaScript In Plain English

New JavaScript + Web Development articles every day.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store