React 19 introduced a new render-time API: use().
If you’ve ever felt that handling async data with useEffect + useState + “loading” + “error” flags is too much boilerplate, use() is basically React saying:
“What if you could just await a Promise directly inside your component?”
In this post, we’ll cover:
- What
use()actually is (and why it’s not just another hook). - How
use()works with Promises and Context. - Why and when to use
use()instead ofuseEffect. - The real benefits of
use()vsuseEffect, with simple code examples.
What is use() in React 19?
use() is a React render-time API that lets your component read the value of a resource, currently:
- A Promise (for async data)
- A Context (like
ThemeContext,AuthContext, etc.)
import { use, Suspense } from "react";
function Message({ messagePromise }) {
const message = use(messagePromise); // read Promise
const theme = use(ThemeContext); // read Context
return (
<div style={{ color: theme.primaryColor }}>
{message.text}
</div>
);
}
Key ideas:
const value = use(resource);resourcecan be a Promise or a Context.use()must be called inside a component or a custom hook.- Unlike regular hooks,
use()can be called inside conditionals and loops.
How use() Works Under the Hood (Simple Mental Model)
When you call use(promise):
- If the Promise is still pending
- React suspends the component.
- If the component is wrapped in
<Suspense>, React shows the fallback UI.
- When the Promise resolves
- React re-renders the component.
- This time,
use(promise)returns the resolved value.
- If the Promise rejects
- React throws the error to the nearest Error Boundary, which can show a graceful error UI.
This tightly integrates use() with Suspense and Error Boundaries — you don’t manually manage isLoading or error state.
Example: Data Fetching Without useEffect
Old way: useEffect + useState
import { useEffect, useState } from "react";
function Weather() {
const [weather, setWeather] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
async function load() {
try {
const res = await fetch("/api/weather");
if (!cancelled) {
const data = await res.json();
setWeather(data);
}
} catch (e) {
if (!cancelled) setError(e);
} finally {
if (!cancelled) setLoading(false);
}
}
load();
return () => {
cancelled = true;
};
}, []);
if (loading) return <p>Loading…</p>;
if (error) return <p>Something went wrong.</p>;
return <p>Current temperature: {weather.temp}°C</p>;
}
That’s a lot of boilerplate for something conceptually simple: “fetch data and render it”.
New way: use() + Suspense
Imagine you have a cached Promise (typically created in a Server Component or a loader):
// server / loader code (simplified)
export function getWeather() {
return fetch("/api/weather").then((res) => res.json());
}
Now use it in a component:
import { use, Suspense } from "react";
import { getWeather } from "./weatherApi";
const weatherPromise = getWeather(); // ideally created/cached at a higher level
function Weather() {
const weather = use(weatherPromise);
return <p>Current temperature: {weather.temp}°C</p>;
}
export default function WeatherSection() {
return (
<Suspense fallback={<p>Loading weather…</p>}>
<Weather />
</Suspense>
);
}
What changed?
- No
useEffect. - No
useState. - No manual
loading/errorflags. - Suspense handles loading UI.
- Error Boundary can handle failures.
Cleaner, more declarative and easier to follow.
Using use() with Context
use() can also read from a Context directly:
import { createContext, use } from "react";
const ThemeContext = createContext(null);
function ThemedButton() {
const theme = use(ThemeContext);
return (
<button style={{ background: theme.bg, color: theme.fg }}>
Click me
</button>
);
}
This is mostly ergonomic sugar over useContext(ThemeContext) — but it’s powerful because the same use() works for both Promises and Context, and it can be used inside conditionals and loops.
Why Use use()? (Practical Benefits)
1. Less Boilerplate for Async Data
With useEffect, you usually need:
useStatefor data.useStatefor loading/error.- Cleanup logic.
- Edge cases (component unmount, race conditions).
With use():
- You just read the Promise.
- Suspense + Error Boundaries deal with loading and error UI.
👉 Result: Fewer lines of code, fewer moving parts, fewer bugs
2. More Declarative, “Synchronous”-Feeling Code
use() lets your React code read like normal await logic:
const weather = use(weatherPromise);
// immediately use `weather`
Your component describes what it needs, not how to orchestrate the async steps.
3. Works Inside Conditionals and Loops
This is a big change from regular hooks.
You cannot do this with useEffect or other Hooks:
// invalid
if (someCondition) {
const data = useEffect(...);
}
But with use() you can:
function Post({ showAuthor, postPromise, authorPromise }) {
const post = use(postPromise);
let authorInfo = null;
if (showAuthor) {
const author = use(authorPromise); //
authorInfo = <p>By {author.name}</p>;
}
return (
<article>
<h1>{post.title}</h1>
{authorInfo}
</article>
);
}
use() isn’t a “stateful hook” that depends on call order like useState or useEffect, so it doesn’t follow the same Rules of Hooks. That’s why it’s allowed in conditionals and loops.
4. Built-in Suspense and Error Handling
use() is designed to work with:
<Suspense>for loading states.- Error Boundaries for failures.
You architect your UI in boundaries, and use() plugs into that naturally. No more repeating the same loading / error template across components.
use() vs useEffect: When to Use What
When use() is better than useEffect
Use use() when:
- You want to read async data (Promises) during render.
- You’re already using React 19 with Suspense boundaries.
- The Promise is created and cached at a higher level (often in Server Components / loaders).
- You want declarative data fetching without
useState/useEffectboilerplate.
Typical use cases:
- Page or section data loading.
- Reading data returned from a Server Action.
- Streaming data from server to client in modern frameworks (Next.js, Remix, React Router v7).
When useEffect is still the right choice
use() does not kill useEffect. You still need useEffect for imperative side effects, such as:
- Subscribing/unsubscribing to WebSockets or event listeners.
- Managing timers (
setInterval,setTimeout). - Direct DOM manipulation or integration with non-React UI libraries.
- Analytics, logging, or any effect that should run after paint.
Example that still needs useEffect:
import { useEffect } from "react";
function OnlineStatusIndicator() {
useEffect(() => {
function handleOnline() {
console.log("User is online");
}
window.addEventListener("online", handleOnline);
return () => {
window.removeEventListener("online", handleOnline);
};
}, []);
return <span>●</span>;
}
Rule of thumb:
- Reading data? → Prefer
use()(with Suspense). - Causing side effects (subscriptions, timers, DOM, logging)? → Use
useEffect.
Best Practices for Using use() in Real Apps
- Cache Promises
Don’t create a new Promise on every render. Create it in a Server Component, loader, or module scope, then pass it down. - Wrap components in
<Suspense>
Any component callinguse(promise)should have a Suspense boundary above it with a meaningful fallback. - Use Error Boundaries
Pairuse()+ Suspense with Error Boundaries for a clean error UX. - Don’t use
use()for everything
Continue to useuseEffectfor non-data side effects.use()is about reading values, not causing effects. - Keep components small and focused
Sinceuse()can appear in conditionals and loops, keep components easy to reason about — small, focused pieces instead of giant “god components”.
Quick Summary: use() vs useEffect
| Feature / Use case | use() (React 19.3) | useEffect |
|---|---|---|
| Type | Render-time API | Hook |
| Purpose | Read Promises/Context during render | Run side effects after render |
| Handles loading/error UI | Via Suspense + Error Boundary | Manually (loading, error state) |
| Can be used in conditionals/loops | Yes | No |
Needs useState for data storage | No (value returned directly) | Usually yes |
| Best for | Data fetching, async resources, Context | Subscriptions, timers, DOM, logging |
Conclusion
React 19.3’s use() marks a big shift in how we think about async data in React:
- Data fetching becomes cleaner and more declarative.
- You write less boilerplate compared to
useEffect+useState. - Suspense and Error Boundaries become central to your UX, not afterthoughts.
Don’t think of use() as a replacement for useEffect, but as a new tool:
- Use
use()to read async resources during render. - Use
useEffectto perform side effects after render.
If you’re already setting up React 19 in your project, start small: migrate one data-loading component from useEffect to use() with Suspense, and you’ll immediately feel the difference.

