Hello World Master

Tutorials, articles and quizzes on Software Development

React > Articles

How to use React Suspense

When building our React application, sometimes has to fetch data from somewhere usually from a server using the Fetch api. Normally, I would set up my React application to fetch data like this.

import React, { useState } from 'react;
import LoadingScreen from '../LoadingScreen';

function MyComponent(props) {
  const [fetchedData, setFetchedData] = useState(null);

  useEffect(() => {
      fetch('http://datafromserver.com/data')
         .then(res => {
            return res.json();
         })
         .then(data => {
            setFetchedData(data);
         })
  }, [])

  return fetchedData ? (<Content {...fetchedData} />) : <LoadingScreen/>
}

Which is fine and works but has some issues

  1. We call fetch only after the component has rendered. This means our component isn’t doing fetching while the component is rendering. So our flow will be Rendered No Content -> Fetching -> Rendered with content
  2. When we start using this approach in different layers (child components) of our React application, fetches in children end up depending on fetches from the parent component, even if the request inside the child component is unrelated to any of the data in fetched in parent components.

Then another option you might consider would be to just fetch everything at the highest level component. This wont be favorable because

  1. We’re making one big request and our component wont render until that request is done
  2. We end up in the same situation where different layers cant render because they depend on something higher up (but in this case instead of waiting for a component to render they wait for our one fetch to complete)

React now comes with the Suspense component that helps alleviate these issues. The suspense component allows us to hold off code from loading until we’ve finished fetching data

First what we want to do is create a Component that will wrap suspense around another component

import React, {Suspense} from 'React'
import ComponentWithFetch from '../ComponentWithFetch
function MyComponent(props) {
  return (
      <Suspense fallback={<h1>Loading....</h1>}>
        <ComponentWithFetch/>
      </Suspense>
   )
}

Now we want to build out our component that calls an api through fetch.

Lets build out the scaffolding first

import React, {Suspense} from 'React'

function ComponentWithFetch(props) {
  const myUrlPath = 'http://myUrlPath.com/data';

  function getPromise() {
     return fetch(myUrlPath)
              .then(res => res.json()); 
  }

  function wrappedPromise(promise) {

     let status = "pending";
     let result;

     const suspender = promise.then(
    r => {
      status = "success";
      result = r;
    },
    e => {
      status = "error";
      result = e;
    });

    return {
      read() {
        if (status === "pending") {
          throw suspender;
        } else if (status === "error") {
          throw result;
        } else if (status === "success") {
          return result;
        }
    }
  };
  }

  useEffect(() => {
    
  }, [])

  return (
      <div>
        {wrappedPromise(getPromise()).read()}
      </div>
   )
}

This is a lot to unpack, so lets start with the getPromise function.

  function getPromise() {
     return fetch(myUrlPath)
              .then(res => res.json()); 
  }

This function returns the promise we want to run. In this case that promise will be a fetch function. The call to .then appended to the fetch function returns the response in a format we can use.

The function we want to look at is wrappedPromise

  function wrappedPromise(promise) {

     let status = "pending";
     let result;

     const suspender = promise.then(
    r => {
      status = "success";
      result = r;
    },
    e => {
      status = "error";
      result = e;
    });

    return {
      read() {
        if (status === "pending") {
          throw suspender;
        } else if (status === "error") {
          throw result;
        } else if (status === "success") {
          return result;
        }
    }
  };
  }

The wrapped promise function pretty much just wraps itself around our chosen promise and gives us the status of our promise if the promise is still loading , we give back the unresolved promise, and if the promise inside is done we give back the result of the promise.