React Suspense Utilities
suspend-react is a utility library for integrating asynchronous operations seamlessly with React Suspense. It allows developers to define functions that return promises, which then 'suspend' rendering until the promise resolves, effectively bringing an `async/await` like paradigm to React components. The library manages pending and error states at a higher level via React's `<Suspense>` and Error Boundaries, reducing boilerplate in individual components. It features a global, key-based caching mechanism, similar to `useMemo` but with application-wide scope, supporting configurable cache invalidation via `lifespan` and custom equality functions. Additionally, it offers preloading, cache busting, and direct cache peeking utilities. The current stable version is 0.1.3, with recent patch releases in June 2023, indicating a stable yet actively maintained project under the pmndrs umbrella. It simplifies data fetching and resource loading within Suspense-enabled React applications.
Common errors
-
Error: A component suspended while rendering, but no fallback UI was specified. This means that a 'Suspense' component higher in the tree needs to provide a fallback prop.
cause A component using `suspend` rendered without being wrapped by a `<React.Suspense>` component, or the `fallback` prop on `<Suspense>` was omitted.fixWrap the suspending component (or a common ancestor) with `<React.Suspense fallback={<div>Loading...</div>}>` and ensure the `fallback` prop is provided with a valid React element. -
Module not found: Error: Can't resolve 'suspend-react' in '...' OR Cannot find module 'suspend-react'
cause The `suspend-react` package has not been installed or is not correctly linked in your project.fixInstall the package using your package manager: `npm install suspend-react` or `yarn add suspend-react`. -
TypeError: suspend is not a function OR suspend is not defined
cause The `suspend` function was either not imported correctly (e.g., attempting a default import or using CommonJS `require` syntax incorrectly) or was not imported at all.fixEnsure you are using a named import for `suspend`: `import { suspend } from 'suspend-react';`
Warnings
- gotcha The caching mechanism in `suspend-react` operates on a global cache by default. While this simplifies usage, it requires developers to ensure their cache keys are sufficiently unique to prevent unintended conflicts or overwrites if the same keys could logically refer to different data across disparate parts of the application.
- gotcha While the README states compatibility with 'React versions >= 16.6', the declared peer dependency is `"react": ">=17.0"`. Installing with React versions 16.x (specifically 16.6-16.9 which introduced Suspense for data fetching) might result in peer dependency warnings or unexpected behavior if certain React 17+ APIs are implicitly relied upon. Always respect the peer dependency range for stable operations.
- gotcha When using `suspend`, errors thrown by the promise-returning function will propagate up to the nearest React Error Boundary. If no Error Boundary is present in the component tree above the suspending component, the application will crash. This is standard React Suspense behavior but a common oversight.
Install
-
npm install suspend-react -
yarn add suspend-react -
pnpm add suspend-react
Imports
- suspend
const suspend = require('suspend-react').suspendimport { suspend } from 'suspend-react' - preload
import preload from 'suspend-react'
import { preload } from 'suspend-react' - clear
const clear = require('suspend-react').clearimport { clear } from 'suspend-react' - peek
import { peek } from 'suspend-react'
Quickstart
import { Suspense } from 'react';
import { suspend } from 'suspend-react';
function Post({ id, version }) {
const data = suspend(async () => {
// In a real app, use a more robust fetching mechanism and error handling
const res = await fetch(`https://hacker-news.firebaseio.com/${version}/item/${id}.json`);
if (!res.ok) {
throw new Error(`Failed to fetch post ${id}: ${res.statusText}`);
}
return res.json();
}, [id, version]);
return (
<div>
<h3>{data.title}</h3>
<p>by {data.by}</p>
<p>Score: {data.score}</p>
</div>
);
}
function App() {
return (
<Suspense fallback={<div>Loading post...</div>}>
<h1>Hacker News Post</h1>
<Post id={8863} version="v0" /> {/* A classic HN post ID */}
</Suspense>
);
}
// To run this in a simple environment, you might need a root component:
// import ReactDOM from 'react-dom/client';
// const root = ReactDOM.createRoot(document.getElementById('root'));
// root.render(<App />);