Creating a Custom Suspense Component
Creating a Custom Suspense Component
June 24, 2020

Creating a Custom Suspense Component

# How to Create a LetSuspense Component in React

Featured Image

Introduction

In this article, you will learn how to create a useful component using Composition in React to present placeholders on your page while data is being fetched in the background; When your content is available, it will simply swap the placeholder for the actual data.

What you need to know

  • Some React experience
  • Knowledge of React hooks (useEffect & useState)

Why do we need this?

We’re used to indeterminate spinners in web design because they’re quick and easy to implement. A better approach to user experience would be to let the loader simulate the expected content; Most developers use skeleton loaders.

YouTubeLinkedIn
YTLN

In summary, this component will help us:

  1. Improve the user’s visual experience.
  2. Write less code since our loader logic will be encapsulated and re-used in a declarative manner.

Setting up the Application

This tutorial focuses on creating a LetSuspense component so a simple demo has already been created. You can find a link to the source code here, in addition, you can also have a look at the live demo.

//App.js
function App() {
  const [movies, setMovies] = useState([]);
  const [query, setQuery] = useState('');

  const handleSearch = (query) => {
    setQuery(query);
  };

  useEffect(() => {
    fetchData();
  }, [query]);

  const fetchData = async () => {
    const result = await fakeApi(query);
    setMovies(result);
  };
  return (
    <div className="App">
      <div className="container">
        <h2 style={{ textAlign: 'center', color: '#fff' }}>Vuetube</h2>
        <SearchBar query={query} handleSearch={handleSearch} />
        <div className="card-container">
          {movies.map(({ id, title, img, desc }) => (
            <DataCard key={id} title={title} img={img} desc={desc} /> // render card
          ))}
        </div>
      </div>
    </div>
  );
}

Here is a snippet of the App.js file, at this stage it just fetches data from the fakeApi and renders the data, no loader is displayed.

Creating the LetSuspense Component

The LetSuspense is quite simple, it needs to accept a placeholder component, the actual component and a condition. It should render the placeholder as long as the condition is not met and the actual component when the condition is met.

I’ll define a few more properties to enhance the LetSuspense.

Properties

PropertyDescription
condition(bool) [false] Determines when components should be rendered
placeholder(React.Component) [undef] A React component that will be rendered as long as the condition is false
children(JSX) [undef] The actual component(s) that will be rendered when the condition is true
multiplier(Number) [1] The number of placeholders to be rendered
initialDelay(Number) [0] Minimum time in milliseconds before a component is rendered
checkOnce(bool) [false] Checks the condition just once, useful for data that doesn’t need to be re-rendered even if the condition changes.

The Code

First we need to import React and a few hooks and also destructure our expected props in the parameter; this helps with code completion.

//LetSuspense.js
import React, { Fragment, useEffect, useState } from 'react';

export default function LetSuspense({
  condition,
  placeholder: Placeholder,
  multiplier,
  initialDelay,
  checkOnce,
  children,
}) {
  return <Fragment></Fragment>;
}

Next we need to manage states; The condition (placeholder or actual component) and whether the condition has been checked.

//LetSuspense.js
...;
    const [component, setComponent] = useState([]);
    const [isChecked, setIsChecked] = useState(false);

    return (
        <Fragment>
        </Fragment>
    );

It’s time to utilize useEffect, useEffect has a 2nd argument which takes an array and causes the component to re-render, we should add both the condition & children props to that array so whenever they change in the parent component, there will be updated here as well.

Note: children here refers to a special props in React, props.children, which means that anything passed into our <LetSuspense> tag will be available and implicitly referenced as children.

//LetSuspense.js
useEffect(() => {
  // make sure the condition check happens just once if checkOnce is true
  if (checkOnce && isChecked) {
    setComponent([children]);
    return;
  }
  let delay = initialDelay || 0;
  let delayedTimeout = null;
  // render actual component when condition is true
  if (condition) {
    if (initialDelay) {
      delayedTimeout = setTimeout(() => {
        setComponent([children]);
      }, delay);
    } else {
      setComponent([children]);
    }
    setIsChecked(true);
  } else {
    // render placeholder as long as condition is false
    let tempComponent = [];
    multiplier = multiplier || 1;
    for (let i = 0; i < multiplier; i++) {
      tempComponent.push(<Placeholder key={i} />);
    }
    setComponent(tempComponent);
  }
  // cleanup side effects
  return () => {
    if (delayedTimeout) {
      clearTimeout(delayedTimeout);
    }
  };
}, [condition, children]);
return <Fragment></Fragment>;

The useEffect will populate the component array with the placeholders or actual component that need to be rendered. We can map that array to return a new component.

//LetSuspense.js
...;
  return (
    <Fragment>
      {component.map((component, index) => (
        <Fragment key={index}>{component} </Fragment>
      ))}
    </Fragment>
  );

Great! that’s all you need to create your LetSuspense Component, let’s import that to our App.js file and see how we can wrap it around our Datacard.

//App.js
const fetchData = async () => {
  const result = await fakeApi(query);
  setMovies(result);
};

return (
  <div className="App">
    <div className="container">
      <h2 style={{ textAlign: 'center', color: '#fff' }}>Vuetube</h2>
      <SearchBar query={query} handleSearch={handleSearch} />
      <div className="card-container">
        {/* LetSuspense Added, it encapsulates its children/actual component*/}
        <LetSuspense
          condition={movies.length > 0}
          placeholder={Placeholder}
          multiplier={3}
          initialDelay={200}
        >
          {movies.map(({ id, title, img, desc }) => (
            <DataCard key={id} title={title} img={img} desc={desc} /> //render card
          ))}
        </LetSuspense>
      </div>
    </div>
  </div>
);

Final Notes

Hopefully, this tutorial showed you how to create a useful component that will come in handy for most of your projects.

Try adding some new props to enhance your LetSuspense like a timeout and an alternative placeholder for when it times out (could just be a text with a message or a button to retry fetching data).

This article was written by Everistus Akpabio, you can visit his website and Github profile.