Academind Logo
Creating a Custom Suspense Component

Creating a Custom Suspense Component

React Suspense allows you to show a placeholder whilst other content or data is being loaded. Here's how you can build your own Suspense component.

Created by $Everistus Akpabio
#

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.

YouTube

YouTube loading spinner

LinkedIn

LinkedIn loading spinner

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
children(JSX) [undef] The actual component(s) that will be rendered when the
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
#

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.

//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).

Recommended Courses