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.
setdata
in useEffect
dependency arraysuspense
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.
Suspense is not a data fetching library. It's a mechanism for data fetching libraries to communicate to React that the data a component is reading is not ready yet. React can then wait for it to be ready and update the UI. At Facebook, we use Relay and its new Suspense integration.
Hooks are new React APIs added to React 16.8. They enable React functional components to use React features that were previously only available in React class components. In a nutshell, they are functions that bring the power of React class components to functional components, giving you a cleaner way to combine them.
Always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That's what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.
Hooks are the new feature introduced in the React 16.8 version. It allows you to use state and other React features without writing a class. Hooks are the functions which "hook into" React state and lifecycle features from function components. It does not work inside classes.
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...';
};
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With