What is React Suspense and the use() API?
React Suspense is a rendering coordination mechanism that lets a component tell React, “I’m not ready to render yet.” When a Suspense boundary is encountered, React pauses rendering of that subtree and shows a fallback UI until the required data becomes available.
The use() API, introduced in React 19, accepts a promise during render and returns its resolved value. If the promise is still pending, rendering is suspended; if it rejects, the error is thrown. This makes asynchronous data consumption declarative and eliminates the need for useEffect, loading flags, or manual state updates.
Why use Suspense and use() for data fetching?
- Render‑as‑you‑fetch: Data fetching can start before the UI is committed, removing the render‑fetch‑re‑render loop of the traditional
useEffectapproach. - Declarative dependencies: Components declare the data they need; React coordinates fetching automatically based on those declarations.
- Reduced boilerplate: No separate loading, error, or success states are required in each component.
- Parallelism by default: Multiple Suspense boundaries can fetch data concurrently, avoiding hidden waterfalls.
- Graceful error handling: Combined with Error Boundaries, failures are caught and displayed without crashing the whole tree.
How to implement Suspense with use() in a React app
- 1. Set up the project – Use Vite or Create React App with React 19 enabled.
- 2. Centralise async resources – Create a module that initiates promises (e.g.,
fetchUser(),fetchOrders(),fetchAnalytics()) and exports them. - 3. Build components that consume promises – Inside a component, call
const data = use(promise). Render the UI usingdatadirectly. - 4. Wrap each async component with
<Suspense>– Provide a fallback UI (spinner, skeleton, etc.) that will be shown while the promise is pending. - 5. Combine with Error Boundaries – Create a class‑based Error Boundary that catches errors thrown by
use(). Wrap each<Suspense>subtree with this boundary to display a retry UI on failure.
How to handle error scenarios with Error Boundaries
- Define a boundary – Extend
React.Component, implementstatic getDerivedStateFromErrorandcomponentDidCatchto capture errors. - Render a fallback – Show an error message and a “Retry” button that resets the error state and optionally re‑creates the async resources.
- Scope the boundary – Wrap individual
<Suspense>sections rather than the entire app so that a failure in one part does not affect unrelated UI.
Best practices and common pitfalls
- Initiate promises at application start (e.g., in
main.jsx) to avoid recreating them on every render. - Keep resource creation pure – do not embed side‑effects inside the promise factories.
- Avoid mixing
useEffectloading flags withuse(); choose one pattern per data source. - Provide meaningful fallback UI that matches the shape of the final component to minimise layout shift.
- Test error paths by deliberately rejecting promises; ensure the Error Boundary UI is accessible and offers a retry mechanism.