The effect to be achieved

Let’s take a look at the final result

The preloaded component is displayed first and the actual component is not displayed until the data is retrieved

Basic concept

Introduction on the official website:

React.SuspenseYou can specify loading indicators in case some child components in its component tree are not yet ready to render.

That is, put the components you want to show in React.suspense and indicate the load indicator. A load indicator is displayed until the component to be shown is ready. (If the component you want to display needs to be loaded using lazy)

By “not ready,” I mean not fetching data, not loading components, etc

Official website example:

// This component is dynamically loaded
const OtherComponent = React.lazy(() = > import('./OtherComponent'));

function MyComponent() {
  return (
    // Display the 
      
        component until OtherComponent is loaded
      
    <React.Suspense fallback={<Spinner />} ><div>
        <OtherComponent />
      </div>
    </React.Suspense>
  );
}
Copy the code

implementation

background

The reason for writing this example is that when WRITING the 24th Demo of 50 Projects in 50 Days, I remembered react. Suspense, which I had seen on the official website, and tried to implement it. So record it.

Realize the principle of

Suspense allows React to pause the rendering of components until certain conditions are met.

In this case, fetching data, a Promise is thrown in the component, and the display of the component is paused until the Promise is processed, during which time the specified load indicator is displayed

To prepare

Because I use the Gravitation-Components library in my implementation

So don’t worry if you don’t, you just need to know what it does (with the exception that you can understand it).

The first is the Main style block, just to make it look good

export const Main = styled.main` height: 100vh; background-color: steelblue; display: flex; justify-content: center; align-items: center; `;
Copy the code

Then there is the Loading style block, which is the Loading indicator mentioned above, which is actually a Loading effect

const animationBg = keyframes` 0% { background-position: 50% 0; } 100% { background-position: -150% 0; } `;

export const Loading = styled.div`
  width: 350px;
  height: 400px;
  box-shadow: 0 0 15px rgba(0, 0, 0, 0.7);
  background-image: linear-gradient(
    to right,
    #f6f7f8 0%,
    #edeef1 10%,
    #f6f7f8 20%,
    #f6f7f8 100%
  );
  background-size: 200% 100%;
  animation: ${animationBg} 1s linear infinite;
`;
Copy the code

Begin to implement

Start by implementing the components that will actually be displayed. As mentioned above, you need to throw a Promise, so start by defining a hook that gets “data”

/** * create a simple hook for retrieving data */
import {useState, useEffect} from 'react'

function useCustomFetch(fetcher) { // Pass in a function that actually gets the data
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  
  useEffect(() = > {
    setLoading(true);
    // Process data
    fetcher()
      .then((res) = > {
        console.log('fetch', res);
        setData(res);
      })
      .finally(() = > {
        setLoading(false);
      });
  }, [fetcher]);

  // The value will be set to false only after the data is retrieved, i.e. the data will be returned
  if (loading) {
    throw Promise.resolve(null); // This throws a Promise
  } else {
    returndata; }}export default useCustomFetch;
Copy the code

The hook that needs to be raised in the component is implemented, and the next step is to use the hook in the component

import React, {useState, useEffect} from "react";
import styled from "styled-components";
import useCustomFetch from "./useCustomFetch";

// This is a div for displaying information
const CardDiv = styled.div` width: 350px; height: 400px; background-color: #e3e3e3; Box-shadow: 0 0 15px rgba(0, 0, 0, 0.7); display: flex; flex-direction: column; img { object-fit: cover; height: 100%; width: 100%; } .info { padding: 10px; h3 { text-align: center; } p { text-align: center; }} `;

// Pass the useCustomFetch callback function
// Simulate sending data
function fetcher() {
 return new Promise((resolve) = > {
   setTimeout(() = > {
     resolve({
       imgUrl: "https://source.unsplash.com/random/350x200".title: "Lorem ipsum dolor sit amet".desc: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolore perferendis"}); },2000);
 });
}

// Initial data
const initialState = {
  imgUrl: "".title: "".desc: ""
};

function Card() {

  const [data, setData] = useState(initialState);
  const res = useCustomFetch(fetcher); // Custom hooks are used here

  useEffect(() = > {
    // Avoid meaningless repetitions
    if(res ! = =null) {
      setData(res);
    }
  }, [res]);

  return (
    <CardDiv>
      <img src={data.imgUrl} alt="" />

      <div className="info">
        <h3>{data.title}</h3>
        <p>{data.desc}</p>
      </div>
    </CardDiv>
  );
}

export default Card;
Copy the code

All the required hooks and components are implemented, and finally use React.suspense

import React from 'react'
import { Main, Loading } from "./styled"; // Main and Loading are both style divs defined above

// Remember to load the Card component with lazy
const Card = React.lazy(() = > import("./card.js"));

function ContentPlaceholder() {
  return (
    <Main>{/* Where fallback specifies the load indicator */}<React.Suspense fallback={<Loading />} ><Card />
      </React.Suspense>
    </Main>
  );
}

export default ContentPlaceholder
Copy the code

The last

The full source code for this blog is here

I implemented all of the 50 Projects in 50 Days examples using React Hooks.

Github addresses: 50-mini-projects-with-react

Comments are welcome if any are inappropriate