Back to all notes
use() with Promises : Async Data with Suspense in React 19
Pass a promise to use() and let Suspense handle loading and rendering.
Introduction
use() can also consume promises. When you pass a promise to use(), React suspends while it’s pending and resumes when it resolves, integrating async data with Suspense.
Why this matters
use() integrates async values with Suspense. When you pass a promise to use(), React will suspend while it’s pending and resume when it resolves—no manual state juggling.
The problem
Fetching inside components with useEffect and useState forces you to juggle loading/error state and handle race conditions.
Inefficient approach
Manual state orchestration is verbose and error‑prone:
import { useState, useEffect } from "react";
function fetchMessage() {
return new Promise((resolve) => {
setTimeout(() => resolve("Hello from the promise!"), 2000);
});
}
function Message() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [message, setMessage] = useState("");
useEffect(() => {
let cancelled = false;
fetchMessage()
.then((m) => !cancelled && setMessage(m))
.catch((e) => !cancelled && setError(e))
.finally(() => !cancelled && setLoading(false));
return () => {
cancelled = true;
};
}, []);
if (loading) return <p>⌛ Loading message...</p>;
if (error) return <p>Something went wrong</p>;
return <p>Here is the message: {message}</p>;
}
function App() {
return (
<div>
<h1>Use Hook with Promises</h1>
<Message />
</div>
);
}
export default App;
The solution
Create a promise and read it with use() inside a Suspense boundary.
import { use, Suspense, useMemo } from "react";
function fetchMessage() {
return new Promise((resolve) => {
setTimeout(() => resolve("Hello from the promise!"), 2000);
});
}
function Message({ messagePromise }) {
const messageContent = use(messagePromise);
return <p>Here is the message: {messageContent}</p>;
}
function App() {
const messagePromise = useMemo(() => fetchMessage(), []);
return (
<div>
<h1>Use Hook with Promises</h1>
<Suspense fallback={<p>⌛ Loading message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
</div>
);
}
export default App;
Step-by-step
- Create or obtain a promise (e.g.,
fetchMessage()). - Read the promise with
use(promise)inside a component. - Wrap the component in
<Suspense fallback={...}>to show loading UI. - Keep the promise stable between renders (create it above render or memoize).
- Pair with an error boundary to handle rejections.
Tips
- Keep promises stable between renders, or you’ll keep suspending.
- Fetch at the route or loader level when possible; pass promises down.
- Pair with error boundaries to handle rejected promises.
Knowledge base
Problem snippet:
useEffect(() => {
/* fetch and set state */
}, []);
Solution snippet:
<Suspense fallback={<p>⌛ Loading...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>