Understanding Suspense and React Hooks

7.2k views Asked by At

I'm struggling to find problems with using Suspense and React hooks.

There are several key problems with the React code below.

import { Suspense, useState, useEffect } from 'react';

const SuspensefulUserProfile = ({ userId }) => {
  const [data, setData] = useState({});
  useEffect(() => {
    fetchUserProfile(userId).then((profile) => setData(profile));
  }, [userId, setData])
  return (
    <Suspense>
      <UserProfile data={data} />
    </Suspense>
  );
};
const UserProfile = ({ data }) => {
  return (
    <>
      <h1>{data.name}</h1>
      <h2>{data.email}</h2>
    </>
  );
};
const UserProfileList = () => {
  <>
    <SuspensefulUserProfile userId={1} />
    <SuspensefulUserProfile userId={2} />
    <SuspensefulUserProfile userId={3} />
  </>
};

Let me know what they are.

I found two key problems.

  • Misuse of setdata in useEffect dependency array
  • Didn't provide suspense fallback props.

I think there is still one key problem remaining.

One weird thing is why userId needs to be included in the dependency array.

2

There are 2 answers

0
Mordechai On BEST ANSWER

You misused Suspense to its core, at least until suspense for data fetching is available.

Suspense only currently works with React.lazy components, not with arbitrary 'loading' states of your application. For instance, how should React figure out that your data is loading?

The only use for Suspense is to allow showing some fallback while React loads a lazy component. For other types of lazy loading app data you can implement your own fallback, as in:

const SuspensefulUserProfile = ({ userId }) => {
  const [data, setData] = useState();

  useEffect(() => {
    fetchUserProfile(userId).then(setData);
  }, [userId])

  return data ? <UserProfile data={data} /> : 'Loading...';
};
0
Patrick Roberts On

The main issue is that you need to use what's called a Suspense integration in order to perform the data fetching and interface with the <Suspense> component.

Typically the <UserProfile> component would consume a resource synchronously (your data in this case) and suspend the component when the resource is not available yet, causing the <Suspense> to temporarily render its fallback prop (which you haven't specified). When the resource becomes available, the <UserProfile> will re-render, and the consumed resource is returned synchronously.

I've published a Suspense integration called suspense-service that lets you consume resources defined by an async function using a service that encapsulates the React Context API.

Here's a demonstration of suspense-service below by slightly modifying your example code:

// import { Fragment, Suspense } from 'react';
const { Fragment, Suspense } = React;
// import { createService, useService } from 'suspense-service';
const { createService, useService } = SuspenseService;

const fetchUserProfile = userId => {
  return new Promise(resolve => {
    setTimeout(resolve, 1000 + Math.random() * 1000);
  }).then(() => {
    return {
      name: `User ${userId}`,
      email: `user${userId}@example.com`
    };
  });
};

const UserProfileService = createService(fetchUserProfile);

const SuspensefulUserProfile = ({ userId }) => {
  return (
    <UserProfileService.Provider request={userId}>
      <Suspense fallback={<h1>Loading User Profile...</h1>}>
        <UserProfile />
      </Suspense>
    </UserProfileService.Provider>
  );
};

const UserProfile = () => {
  const data = useService(UserProfileService);

  return (
    <Fragment>
      <h1>{data.name}</h1>
      <h2>{data.email}</h2>
    </Fragment>
  );
};

const UserProfileList = () => {
  return (
    <Fragment>
      <SuspensefulUserProfile userId={1} />
      <SuspensefulUserProfile userId={2} />
      <SuspensefulUserProfile userId={3} />
    </Fragment>
  );
};

ReactDOM.render(<UserProfileList />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/[email protected]"></script>
<div id="root"></div>