DEV Community

Cover image for Implementing Skeleton Loading in React
Adrian Bece for PROTOTYP

Posted on • Edited on • Originally published at blog.prototyp.digital

Implementing Skeleton Loading in React

The idea and concept of Skeleton Loading or Skeleton Screens was introduced in 2013. in this blog post by Luke Wroblewski. It describes the concept of a blank screen where dynamic content is replaced by styled blocks (skeleton) and is replaced with real content once it's finished loading.

Alt Text

Skeleton loading is better than a spinner

When a user looks at the page with a standard loading spinner, they're thinking "I'm waiting for it to load". We're making the user watch a dull repeating animation and the user feels like it's staring at the clock.

When a user looks at the page with skeleton loading or skeleton screen, they're thinking "It's almost loaded, something is happening". The user is watching a page that seems like it's right about to finish loading. User is watching at a page that is not empty or dull, even though we haven't loaded any content yet. It gives an illusion of speed.

But there is something to keep in mind about skeleton loading...

Skeleton loading is used when the final loaded state is predictable

We cannot simply replace every dynamically loaded element on the page with skeleton loading. In some cases, we cannot predict how the final loaded state of the page is going to look like.

For example, if we try and assume a final state of the page (let's say we chose 1 out of 4 wildly different possible states) and create a skeleton screen based on our assumption. When a user looks at the skeleton and the loaded content wildly differs from the skeleton, the transition looks very out of place and jarring. This may hurt the UX and make your app feel chaotic.

Let's assume we're looking at an eCommerce site and we are looking at the homepage. eCommerce homepages often change their look and layout, depending on the events or sales that are going on. It wouldn't be a good idea to implement the skeleton loading here.

Looking at the catalog page, where products are displayed in 4-column layout, with 24 items per page. We can safely assume that this state is not going to change. Even if we finally load 12 or 15 products, instead of assumed 24 the transition remains smooth. Catalog page loading is also very load-intensive (if pagination, sorting, and filters are used) so skeleton loading may also help in keeping the users on the page even in cases when loading times are larger than expected.

Implementing Skeleton loading in React

In the following example, we are implementing a skeleton loading for the food recipe card component. This is how the component looks like.

Alt Text

import * as React from 'react';
import { Link } from 'react-router-dom';
import { LazyImage } from 'components';
import { getUri } from 'util/getUri';

export const RecipeCard = ({
  calories,
  ingredients,
  image,
  label,
  source,
  uri
}) => {
  return (
    <li className="recipeCard">
      <Link className="recipeCard__link" to={`/recipes/recipe/${getUri(uri)}`}>
        <LazyImage className="recipeCard__image" src={image} alt={label} />
        <div className="recipeCard__wrapper">
          <div>
            <h3 className="recipeCard__title">{label}</h3>
            <p className="paragraph">
              <strong>
                By <span className="gradient--text">{source}</span>
              </strong>
            </p>
          </div>

          <div className="recipeCard__info">
            <strong className="gradient--text">{Math.ceil(calories)}</strong>{' '}
            calories |{' '}
            <strong className="gradient--text">{ingredients.length}</strong>{' '}
            ingredients
          </div>
        </div>
      </Link>
    </li>
  );
};

Enter fullscreen mode Exit fullscreen mode

This is a simple skeleton that doesn't use any animations, just a plain color which also looks fine. You can easily add the animation by animating the background gradient.

First, we need to set up the styles for our skeleton components. We are setting up our base skeleton styles in .recipeCard__skeleton class. We are setting inline-block to mimic content behavior (like alignment), we use padding to add height (equal to 1 unit of line-height, in this case) to an element. We also have two modifier classes that change the width of the content and additional class for an image that has aspect ratio 1:1 (this is why padding is set to 100%).

.recipeCard__skeleton {
    display: inline-block;
    background-color: var(--color__gray--lighter);
    padding-bottom: var(--spacing__vertical--1);
}

.recipeCard__skeleton--medium {
    width: 33%;
}

.recipeCard__skeleton--large {
    width: 100%;
}
.recipeCard__image--skeleton {
    padding-bottom: 100%;
    background-color: var(--color__gray--lighter);
}

Enter fullscreen mode Exit fullscreen mode

Let's create our Skeleton component:

  1. Copy the content of the "real" component and paste it into the Skeleton component. Change the const name and export.
  2. Replace all dynamic content (coming from props) to skeleton elements. Spans work fine due to them not having any default styles. Let the layout styles and grids handle everything else and keep the skeletons in place.
  3. Conditionally load the skeleton in the main component
import * as React from 'react';
import { Link } from 'react-router-dom';

export const Skeleton = () => {
  return (
    <li className="recipeCard">
      <div className="recipeCard__link">
        <div className="recipeCard__image--skeleton" />
        <div className="recipeCard__wrapper">
          <div>
            <h3 className="recipeCard__title">
              <span className="recipeCard__skeleton recipeCard__skeleton--large"></span>
            </h3>
            <p className="paragraph">
              <span className="recipeCard__skeleton recipeCard__skeleton--large"></span>
            </p>
          </div>

          <div className="recipeCard__info">
            <span className="recipeCard__skeleton recipeCard__skeleton--medium"></span>{' '}
            <span className="recipeCard__skeleton recipeCard__skeleton--medium"></span>
          </div>
        </div>
      </div>
    </li>
  );
};

Enter fullscreen mode Exit fullscreen mode

And this is how the final component will look like.

Alt Text

Pretty simple, right? Once you get a hang of replacing the content with skeletons, you can set up a very versatile library of classes for replacing skeleton content and create the skeleton screens really quickly.


These articles are fueled by coffee. So if you enjoy my work and found it useful, consider buying me a coffee! I would really appreciate it.

Buy Me A Coffee

Thank you for taking the time to read this post. If you've found this useful, please give it a ❤️ or 🦄, share and comment.

Top comments (5)

Collapse
 
jigneshhdalal profile image
Jignesh Dalal • Edited

Mind elaborating on "#3 Conditionally load the skeleton in the main component" by posting the main component code?

TIA

Collapse
 
adrianbdesigns profile image
Adrian Bece

Thank you for reading the article. Skeleton loading needs to be displayed until dynamic data is loaded.

Basically, if you have an isLoading prop on your component. While isLoading is true, component needs to display the skeleton. When data has finished loading and is ready to be displayed, isLoading is set to false and then you display the "real" component with data.

This is how it looks for my use-case.

      <ul className="recipeList">
        {isLoading
          ? [...new Array(24)].map((item, index) => (
              <Skeleton key={`skeleton-${index}`}></Skeleton>
            ))
          : hits.map(({ recipe }: any) => (
              <RecipeCard key={recipe.uri} {...recipe} />
            ))}
      </ul>
Collapse
 
jigneshhdalal profile image
Jignesh Dalal

Gotcha.. Thanks much!

Collapse
 
mfrachet profile image
Marvin

This seems to be a place and a good opportunity to put this here github.com/mfrachet/rn-placeholder :p

Collapse
 
adrianbdesigns profile image
Adrian Bece

That's really cool. Great work. I do prefer writing the Skeleton component from scratch but this is great for people who want to have the components out of the box.