# The `use` Hook in React 19: Suspense Without the Boilerplate
By Govind Bajaj · 2026

Tags: react, react-19, suspense, hooks, data-fetching
React 19's `use` hook collapses three patterns into one. No more wrapper components. No more useEffect data fetching. Here's the before and after from a real migration.
I migrated a dashboard page from the old Suspense patterns to the React 19 `use` hook last week.

The component went from 47 lines to 12. Three files collapsed into one. And the error boundary actually caught errors correctly for the first time.

The `use` hook is the most underrated feature in React 19. It sounds simple — "a hook that lets you read a Promise or Context" — but it eliminates entire categories of boilerplate.

## The Old Way: A Mess of Wrappers

Before React 19, using Suspense with data fetching required one of two approaches:

**Approach 1: Fetch-then-render (outside React)**

```tsx
// You fetched data at the route level, then passed it down
export default async function Page() {
  const data = await fetchDashboardData() // fetch here
  return <Dashboard data={data} /> // pass down
}

// Dashboard had to accept data as props
function Dashboard({ data }: { data: DashboardData }) {
  return <div>{/* use data */}</div>
}
```

This blocked the entire page on one data fetch. Parallel fetches required Promise.all. The component wasn't reusable because it needed data passed in.

**Approach 2: Wrapper components for every Suspense boundary**

```tsx
// You needed a separate component that throws the promise
function DashboardData({ children }: { children: (data: DashboardData) => React.ReactNode }) {
  const data = useSyncExternalStore(
    // ... complex setup to integrate with your cache
  )
  return children(data)
}

// Then wrap it in Suspense
export default function Page() {
  return (
    <Suspense fallback={<Loading />}>
      <DashboardData>
        {data => <Dashboard data={data} />}
      </DashboardData>
    </Suspense>
  )
}
```

This worked but was verbose. Every Suspense boundary needed a wrapper component. Error boundaries were separate. The composition was clunky.

## The `use` Way: Read Promises Directly

The `use` hook lets you read a Promise inside any component. If the Promise isn't resolved, the component suspends — automatically. No wrapper needed.

```tsx
import { use, Suspense } from 'react'

// Fetch returns a Promise — don't await it
function Dashboard() {
  const data = use(fetchDashboardData()) // suspends until resolved
  return <div>{/* use data directly */}</div>
}

// Wrap in Suspense at the usage site
export default function Page() {
  return (
    <Suspense fallback={<Loading />}>
      <Dashboard />
    </Suspense>
  )
}
```

The key: `fetchDashboardData()` returns a Promise. `use()` reads it. If the Promise is pending, the component suspends. If it resolves, you get the value. If it rejects, the nearest Error Boundary catches it.

## Why This Is Better

**No wrapper components**: The component that needs the data fetches it directly. Composition is natural.

**Parallel fetches for free**: Multiple `use()` calls in the same component suspend independently.

```tsx
function Dashboard() {
  const orders = use(fetchOrders())    // suspends
  const users = use(fetchUsers())      // also suspends, in parallel
  const metrics = use(fetchMetrics())  // also suspends, in parallel
  
  return <div>{/* all three available here */}</div>
}
```

**Works with Client Components**: Unlike async Server Components, `use()` works in Client Components too. You can suspend on any Promise.

```tsx
'use client'

import { use, Suspense } from 'react'

function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise) // works in Client Components
  return <div>{user.name}</div>
}
```

**Streaming SSR**: The server streams the Suspense fallback immediately, then streams the resolved content when the Promise settles. The user sees something instantly.

## The Context Integration

`use` also works with React Context, which solves a subtle but painful problem.

```tsx
// Old way: useContext must be called in the same component as the render
function Button() {
  const theme = useContext(ThemeContext)
  // ...
}

// New way: use can be called conditionally
function Button({ overrideTheme }: { overrideTheme?: Theme }) {
  const contextTheme = use(ThemeContext)
  const theme = overrideTheme ?? contextTheme
  // ...
}
```

Unlike `useContext`, `use` can be called conditionally. It's not subject to the Rules of Hooks. This is a small change with big implications for component flexibility.

## Real Migration: The Sans Herbals Product Page

The product detail page had this structure before:

```tsx
// 3 files, 80+ lines
export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await getProduct(params.id)
  const related = await getRelatedProducts(params.id)
  
  return (
    <div>
      <ProductDetails product={product} />
      <RelatedProducts products={related} />
    </div>
  )
}
```

After migration with `use`:

```tsx
// 1 file, 25 lines
import { use, Suspense } from 'react'
import { getProduct, getRelatedProducts } from './data'

function ProductDetails({ id }: { id: string }) {
  const product = use(getProduct(id))
  return <div>{/* ... */}</div>
}

function RelatedProducts({ id }: { id: string }) {
  const products = use(getRelatedProducts(id))
  return <div>{/* ... */}</div>
}

export default function Page({ params }: { params: { id: string } }) {
  return (
    <div>
      <Suspense fallback={<ProductSkeleton />}>
        <ProductDetails id={params.id} />
      </Suspense>
      <Suspense fallback={<RelatedSkeleton />}>
        <RelatedProducts id={params.id} />
      </Suspense>
    </div>
  )
}
```

Each section has its own Suspense boundary and its own fallback. The main product details stream first, related products stream after. The user sees the important content faster.

## Takeaways

- `use()` reads Promises inside components; if pending, the component suspends automatically
- Eliminates wrapper components and `useEffect` data fetching in Client Components
- Multiple `use()` calls in the same component suspend in parallel
- Works in both Server and Client Components (unlike async/await, which is Server-only)
- Can also read Context, and unlike `useContext`, it can be called conditionally
- The migration from fetch-then-render to `use` + Suspense reduces files and improves perceived performance