Building Skeleton Components with React

Avatar of Mathias Rechtzigel
Mathias Rechtzigel on

One of the advantages of building a Single Page Application (SPA) is the way navigating between pages is extremely fast. Unfortunately, the data of our components is sometimes only available after we have navigated to a specific part of our application. We can level up the user’s perceived performance by breaking the component into two pieces: the container (which displays a skeleton view when it’s empty) and the content. If we delay the rendering of the content component until we have actually received the content required, then we can leverage the skeleton view of the container thus boosting the perceived load time!

Let’s get started in creating our components.

What we’re making

We will be leveraging the skeleton component that was built in the article, “Building Skeleton Screens with CSS Custom Properties.”

This is a great article that outlines how you can create a skeleton component, and the use of the :empty selector allows us to cleverly use {this.props.children} inside of our components so that the skeleton card is rendered whenever the content is unavailable.

See the Pen React 16 — Skeleton Card – Final by Mathias Rechtzigel (@MathiasaurusRex) on CodePen.

Creating our components

We’re going to create a couple of components to help get us started.

  1. The outside container (CardContainer)
  2. The inside content (CardContent)

First, let’s create our CardContainer. This container component will leveraging the :empty pseudo selector so it will render the skeleton view whenever this component doesn’t receive a child.

class CardContainer extends React.Component {
  render() {
    return (
      <div className="card">
        {this.props.children}
      </div>
    );
  }
}

Next, let’s create our CardContent component, which will be nested inside of our CardContainer component.

class CardContent extends React.Component {
  render() {
    return (
      <div className="card--content">
        <div className="card-content--top">
          <div className="card-avatar">
            <img 
              className="card-avatar--image"
              src={this.props.avatarImage}
              alt="" />
            <span>{this.props.avatarName}</span>
          </div>
        </div>
        <div className="card-content--bottom">
          <div className="card-copy">
            <h1 className="card-copy--title">{this.props.cardTitle}</h1>
            <p className="card-copy--description">{this.props.cardDescription}</p>
          </div>
          <div className="card--info">
            <span className="card-icon">
              <span className="sr-only">Total views: </span>
              {this.props.countViews}
            </span>
            <span className="card-icon">
              <span className="sr-only">Total comments: </span>
              {this.props.countComments}
            </span>
          </div>
        </div>
      </div>
    );
  }
}

As you can see, there’s a couple of spaces for properties that can be accepted, such as an avatar image and name and the content of the card that is visible.

Putting the components together allows us to create a full card component.

<CardContainer>
  <CardContent
    avatarImage='path/to/avatar.jpg'
    avatarName='FirstName LastName'
    cardTitle='Title of card'
    cardDescription='Description of card'
    countComments='XX'
    countViews='XX'
  />
</CardContainer>

See the Pen React 16 — Skeleton Card – Card Content No State by Mathias Rechtzigel (@MathiasaurusRex) on CodePen.

Using a ternary operator to reveal contents when the state has been loaded

Now that we have both a CardContainer and CardContent component, we have split our card into the necessary pieces to create a skeleton component. But how do we swap between the two when content has been loaded?

This is where a clever use of state and ternary operators comes to the rescue!

We’re going to do three things in this section:

  1. Create a state object that is initially set to false
  2. Update our component to use a ternary operator so that the cardContent component will not be rendered when the state is false
  3. Set the state to be the content of our object once we receive that information

We want to set the default state of our content to be set to false. This hides the card content and allows the CSS :empty selector to do it’s magic.

this.state = {
  cardContent: false
};

Now we’re got to update our CardContainer children to include a ternary operator. In our case, it looks at this.state.cardContent to see whether or not it resolves to true or false. If it’s true, it does everything on the left side of the colon (:). Conversely, if it’s false, it does everything on the right hand of the colon. This is pretty useful because objects will resolve to true and if we set the initial state to false, then our component has all the conditions it needs to implement a skeleton component!

Let’s combine everything together inside of our main application. We wont worry about the state inside CardContent quite yet. We’ll bind that to a button to mimic the process of fetching content from an API.

<CardContainer>
  {this.state.cardContent 
    ? 
      <CardContent 
      avatarImage={this.state.cardContent.card.avatarImage}
      avatarName={this.state.cardContent.card.avatarName}
      cardTitle={this.state.cardContent.card.cardTitle}
      cardDescription={this.state.cardContent.card.cardDescription}
      countComments={this.state.cardContent.card.countComments}
      countViews={this.state.cardContent.card.countViews}/>
    : 
    null
  }          
</CardContainer>

Boom! As you can see, the card is rendering as the skeleton component since the state of cardContent is set to false. Next, we’re going to create a function that sets the state of cardContent to a mock Card Data Object (dummyCardData):

populateCardContent = (event) => {
    const dummyCardData =  {
      card: {
        avatarImage: "https://gravatar.com/avatar/f382340e55fa164f1e3aef2739919078?s=80&d=https://codepen.io/assets/avatars/user-avatar-80x80-bdcd44a3bfb9a5fd01eb8b86f9e033fa1a9897c3a15b33adfc2649a002dab1b6.png",
        avatarName: "Mathias Rechtzigel",
        cardTitle: "Minneapolis",
        cardDescription:"Winter is coming, and it will never leave",
        countComments:"52",
        countViews:"32"
      }
    }
    const cardContent = dummyCardData
    this.setState({
      cardContent
    })
  }

In this example, we’re setting the state inside of a function. We could also leverage React’s lifecycle methods to populate the component’s state. We would have to take a look at the appropriate method to use, depending on our requirements. For example, if I’m loading an individual component and want to get the content from the API, then we would use the ComponentDidMount lifecycle method. As the documentation states, we have to be careful of using this lifecycle method in this way as it could cause an additional render — but setting the initial state to false should prevent that from happening.

See the Pen React 16 — Skeleton Card – Final by Mathias Rechtzigel (@MathiasaurusRex) on CodePen.

The second card in the list is hooked up to the click event that sets the cardContent state. Once the state is set to the content’s object, the skeleton version of the card disappears and the content is shown, ensuring the that the user doesn’t see a flash of UI (FLU season is coming so we don’t want to give the users the F.L.U.!).

Let’s review

We covered quite a bit, so let’s recap what we did.

  1. We created a CardContainer. The container component is leveraging the :empty pseudo selector so that it renders the skeleton view of the component when it is empty.
  2. We created the CardContent component that is nested within CardContainer that we pass our state to.
  3. We set the default state of the cardContent to false
  4. We use a ternary operator to render the inner content component only when we receive the content and put it in our cardContent state object.

And there we have it! A perceived boost in performance by creating an interstitial state between the UI being rendered and it receiving the data to populate content.