Mastering the React 'use' Hook

React 19 introduces a powerful new API simply named use. This hook is a game-changer for handling asynchronous resources and Context consumption within your components. Unlike other hooks, use is unique: it can be called conditionally, inside loops, and even within if statements.
In this guide, we'll explore exactly how use works, why it's different, and how to apply it in real-world scenarios.
What is use?
The use hook is a versatile API that lets you read the value of a resource like a Promise or a Context.
- When passed a Promise: It suspends the component until the promise resolves.
- When passed a Context: It reads the context value, similar to
useContext, but with more flexibility.
1. Handling Data Fetching (Promises)
Before use, handling async data usually involved useEffect + useState, or an external library like React Query. With use, you can unwrap promises directly in your render phase, provided you wrap the parent in <Suspense>.
The Traditional Way (useEffect)
function UserProfile({ id }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser(id).then(data => {
setUser(data);
setLoading(false);
});
}, [id]);
if (loading) return <Spinner />;
return <div>{user.name}</div>;
}The Modern Way (use)
With the use hook, the fetching logic is decoupled from the component lifecycle. You pass the promise itself.
import { use, Suspense } from 'react';
// 1. Create the promise outside or passed as prop
const userPromise = fetchUser(1); // Ideally cached or memoized
function UserProfile({ userPromise }) {
// 2. "use" the promise to suspend and unwrap data
const user = use(userPromise);
return (
<div className="p-4 border rounded-lg shadow-sm">
<h2 className="text-xl font-bold">{user.name}</h2>
<p className="text-gray-600">{user.email}</p>
</div>
);
}
export default function Page() {
return (
<Suspense fallback={<div className="p-4">Loading user...</div>}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}The Promise passed to use should be cached. If you create a new Promise inside the component body, it will be recreated on every render, causing an infinite loop or unnecessary re-fetching.
2. Conditional Context Usage
This is arguably the most exciting feature. The standard useContext hook throws an error if called conditionally (e.g., inside an if block). The use API does not.
Practical Use Case: Optimized UI Branches
Imagine a heavy component where you only need a specific Context if a certain condition is met.
import { use } from 'react';
import { ThemeContext } from './theme-context';
function HeavyHeader({ enableTheming }) {
if (enableTheming) {
// This is valid!
const theme = use(ThemeContext);
return <header style={{ color: theme.color }}>Themed Header</header>;
}
return <header>Default Header</header>;
}This flexibility prevents you from having to read context at the top level when it might trigger unnecessary re-renders for branches that don't even use it.
3. The Pitfall: Creating Promises in Render
One of the most common mistakes when adopting use is creating the Promise inside the component. This leads to data re-fetching on every single render, or worse, infinite loops.
❌ The Wrong Way
function BadUserProfile({ id }) {
// 🔴 BAD: New Promise created on every render!
const userPromise = fetchUser(id);
// React suspends, component re-renders when data arrives...
const user = use(userPromise);
// ...but on re-render, we run line 1 again, creating a NEW promise!
// React sees a new promise, suspends again -> Infinite Loop.
return <div>{user.name}</div>;
}Why does this cause an infinite loop?
- Render 1: Component calls
fetchUser(id). A new Promise (Promise A) is created. - Suspend:
use(Promise A)sees it's pending. React suspends and waits. - Resolve:
Promise Aresolves. React attempts to re-render the component. - Render 2: Component runs again.
fetchUser(id)runs again. A new Promise (Promise B) is created. - Suspend:
use(Promise B)sees it's pending (it's brand new!). React suspends again. - Loop: This cycle repeats forever.
✅ The Right Way
Pass the promise as a prop (from a Server Component) or use a caching mechanism.
// 🟢 GOOD: Promise is created once outside or cached
const cachedUserPromise = fetchUser(1);
function GoodUserProfile() {
const user = use(cachedUserPromise);
return <div>{user.name}</div>;
}4. Streaming Server Data to Client
Combining Server Components with use on the client allows for powerful streaming patterns. You can fetch data on the server, pass the promise to a client component, and resolve it there.
Server Component (page.tsx):
import { Suspense } from 'react';
import { db } from './db';
import { Comments } from './Comments'; // Client Component
export default async function BlogPost({ id }) {
// Start fetching, don't await yet!
const commentsPromise = db.query(`SELECT * FROM comments WHERE postId = ${id}`);
return (
<section>
<h1>Blog Post Content...</h1>
<Suspense fallback={<p>Loading comments...</p>}>
{/* Pass the promise to the client component */}
<Comments commentsPromise={commentsPromise} />
</Suspense>
</section>
);
}Client Component (Comments.tsx):
'use client';
import { use } from 'react';
export function Comments({ commentsPromise }) {
// "use" unwraps the promise passed from the server
const comments = use(commentsPromise);
return (
<ul>
{comments.map(c => <li key={c.id}>{c.text}</li>)}
</ul>
);
}Comparisons
| Feature | useContext | useEffect Fetching | use API |
|---|---|---|---|
| Input | Context Object | N/A | Promise OR Context |
| Conditional? | ❌ No | ✅ Yes | ✅ Yes (Flexible) |
| Suspense Support | ❌ No | ❌ No | ✅ Yes (Native) |
| Usage | Top-level only | Top-level only | Anywhere in render |
Conclusion
The use hook simplifies many patterns that previously required awkward workarounds or external libraries. By embracing Suspense for data fetching and allowing conditional Context consumption, React is making component code cleaner and more declarative.
Try replacing some of your useEffect data fetching or complex Context selectors with use and see how much boilerplate disappears!
Loading comments...