---
title: React 19 Suspense for Data Fetching Pattern Reference
---
# React 19 Suspense for Data Fetching Pattern Reference
React 19's new Suspense integration for **data fetching** is a preview feature that allows components to suspend (pause rendering) until data is available, without `useEffect + state`.
**Important:** This is a **preview** it requires specific setup and is not yet stable for production, but you should know the pattern for React 19 migration planning.
---
## What Changed in React 19?
React 18 Suspense only supported **code splitting** (lazy components). React 19 extends it to **data fetching** if certain conditions are met:
- **Lib usage** the data fetching library must implement Suspense (e.g., React Query 5+, SWR, Remix loaders)
- **Or your own promise tracking** wrap promises in a way React can track their suspension
- **No more "no hook after suspense"** you can use Suspense directly in components with `use()`
---
## React 18 Suspense (Code Splitting Only)
```jsx
// React 18 Suspense for lazy imports only:
const LazyComponent = React.lazy(() => import('./Component'));
function App() {
return (
}>
);
}
```
Trying to suspend for data in React 18 required hacks or libraries:
```jsx
// React 18 hack not recommended:
const dataPromise = fetchData();
const resource = {
read: () => {
throw dataPromise; // Throw to suspend
}
};
function Component() {
const data = resource.read(); // Throws promise → Suspense catches it
return
{data}
;
}
```
---
## React 19 Suspense for Data Fetching (Preview)
React 19 provides **first-class support** for Suspense with promises via the `use()` hook:
```jsx
// React 19 Suspense for data fetching:
function UserProfile({ userId }) {
const user = use(fetchUser(userId)); // Suspends if promise pending
return
{user.name}
;
}
function App() {
return (
}>
);
}
```
**Key differences from React 18:**
- `use()` unwraps the promise, component suspends automatically
- No need for `useEffect + state` trick
- Cleaner code, less boilerplate
---
## Pattern 1: Simple Promise Suspense
```jsx
// Raw promise (not recommended in production):
function DataComponent() {
const data = use(fetch('/api/data').then(r => r.json()));
return
{JSON.stringify(data, null, 2)}
;
}
function App() {
return (
}>
);
}
```
**Problem:** Promise is recreated every render. Solution: wrap in `useMemo`.
---
## Pattern 2: Memoized Promise (Better)
```jsx
function DataComponent({ id }) {
// Only create promise once per id:
const dataPromise = useMemo(() =>
fetch(`/api/data/${id}`).then(r => r.json()),
[id]
);
const data = use(dataPromise);
return
{JSON.stringify(data, null, 2)}
;
}
function App() {
const [id, setId] = useState(1);
return (
}>
);
}
```
---
## Pattern 3: Library Integration (React Query)
Modern data libraries support Suspense directly. React Query 5+ example:
```jsx
// React Query 5+ with Suspense:
import { useSuspenseQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
// useSuspenseQuery throws promise if suspended
const { data: user } = useSuspenseQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
return
{user.name}
;
}
function App() {
return (
}>
);
}
```
**Advantage:** Library handles caching, retries, and cache invalidation.
---
## Pattern 4: Error Boundary Integration
Combine Suspense with Error Boundary to handle both loading and errors:
```jsx
function UserProfile({ userId }) {
const user = use(fetchUser(userId)); // Suspends while loading
return
{user.name}
;
}
function App() {
return (
}>
}>
);
}
class ErrorBoundary extends React.Component {
state = { error: null };
static getDerivedStateFromError(error) {
return { error };
}
render() {
if (this.state.error) return this.props.fallback;
return this.props.children;
}
}
```
---
## Nested Suspense Boundaries
Use multiple Suspense boundaries to show partial UI while waiting for different data:
```jsx
function App({ userId }) {
return (
}>
}>
);
}
function UserProfile({ userId }) {
const user = use(fetchUser(userId));
return
;
}
```
Now:
- User profile shows spinner while loading
- Posts show spinner independently
- Both can render as they complete
---
## Sequential vs Parallel Suspense
### Sequential (wait for first before fetching second)
```jsx
function App({ userId }) {
const user = use(fetchUser(userId)); // Must complete first
return (
}>
{/* Depends on user */}
);
}
function UserPosts({ userId }) {
const posts = use(fetchUserPosts(userId));
return
{posts.map(p =>
{p.title}
)}
;
}
```
### Parallel (fetch both at once)
```jsx
function App({ userId }) {
return (
}>
}>
{/* Fetches in parallel */}
);
}
```
---
## Migration Strategy for React 18 → React 19
### Phase 1 No changes required
Suspense is still optional and experimental for data fetching. All existing `useEffect + state` patterns continue to work.
### Phase 2 Wait for stability
Before adopting Suspense data fetching in production:
- Wait for React 19 to ship (not preview)
- Verify your data library supports Suspense
- Plan migration after app stabilizes on React 19 core
### Phase 3 Refactor to Suspense (optional, post-preview)
Once stable, profile candidates:
```bash
grep -rn "useEffect.*fetch\|useEffect.*axios\|useEffect.*graphql" src/ --include="*.js" --include="*.jsx"
```
```jsx
// Before (React 18):
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
if (!user) return ;
return
{user.name}
;
}
// After (React 19 with Suspense):
function UserProfile({ userId }) {
const user = use(fetchUser(userId));
return
{user.name}
;
}
// Must be wrapped in Suspense:
}>
```
---
## Important Warnings
1. **Still Preview** Suspense for data is marked experimental, behavior may change
2. **Performance** promises are recreated on every render without memoization; use `useMemo`
3. **Cache** `use()` doesn't cache; use React Query or similar for production apps
4. **SSR** Suspense SSR support is limited; check Next.js version requirements