The use API
Read a promise or a context while rendering with React's use API: Suspense, error boundaries, promises that last, and calling use inside an if.
- 01Read a promise with useCall
use(promise)inside a Suspense boundary, and say what it does while the promise is pending, fulfilled, or rejected. - 02Give use a promise that lastsGet the promise from a cache, a parent's state, or a Server Component, and never make it in the component's own render.
- 03Read context with useCall
use(context)after an early return or inside anif, and know the placesusecan't go, liketry.
Read a promise's value while rendering
In Suspense, a component read its data with a short line, use(fetchData(…)), and a boundary showed a fallback while it waited. This lesson is about that line. use is a function from React, added in React 19, that reads a value from a resource: a promise or a context. Here it reads an order from a pretend server. The result is live:
import { Suspense, use } from "react";import { fetchData } from "./data.js"; function OrderStatus() { const order = use(fetchData("/orders/42")); return <p>Order {order.id}: {order.status}</p>;} export default function App() { return ( <> <h3>Your orders</h3> <Suspense fallback={<p>Checking order 42…</p>}> <OrderStatus /> </Suspense> </> );}Line 5 calls fetchData("/orders/42") from the data.js tab. It starts a request and returns a promise: an object that stands for a value that isn't there yet. use takes that promise and gives back its value, the order. Line 6 shows two of the order's fields. Lines 13 to 15 wrap OrderStatus in a Suspense boundary, which shows Checking order 42… until the order arrives, one second later. Press Run again under the result to watch it once more.
Notice what isn't there: no useState for the order, no useEffect to start the request, no if (order === null) check. OrderStatus reads the order as if it were already there, and React takes care of the wait. The data.js tab is the same in every example of this lesson: it answers after a delay with setTimeout, and nothing goes over the network.
use suspends and the nearest Suspense boundary shows its fallback. Once it's fulfilled, use returns the value. If it's rejected, use throws the error, and the nearest error boundary shows its fallback.At a café's pickup counter, you show your ticket. If the order isn't ready, you step aside and the screen says "Preparing"; you come back when your number is called. If it's ready, you take it. If something went wrong, the manager deals with it, not the person at the till.
- In real life: You hand in your order ticket at the counter
- In React: OrderStatus calls use(promise)
- In real life: Not ready: you step aside, and a sign says “Preparing”
- In React: Pending: use suspends, and Suspense shows the fallback
- In real life: Ready: you get your order straight away
- In React: Fulfilled: use returns the value
- In real life: Sold out: a manager comes over and explains
- In React: Rejected: use throws, and an error boundary shows its fallback
use(resource) reads a promise or a context while a component renders. With a promise, it returns the value once the promise is fulfilled, suspends the component while it's pending (the nearest Suspense boundary shows its fallback), and throws the error if it's rejected (the nearest error boundary shows its fallback). Unlike hooks, it can be called inside if and loops.
What use does while the promise waits
use can't stop JavaScript and wait: a component is a plain function, and it has to return something now. So when the promise is still pending, use suspends the component. It stops the function right there, by throwing a special signal that only React catches, and React shows the nearest Suspense fallback instead. When the promise settles, React calls the component again, from its first line. Two log lines make it visible:
import { Suspense, use } from "react";import { fetchData } from "./data.js"; function OrderStatus() { console.log("OrderStatus starts"); const order = use(fetchData("/orders/42")); console.log(`use gave back: ${order.status}`); return <p>Order {order.id}: {order.status}</p>;} export default function App() { return ( <Suspense fallback={<p>Checking order 42…</p>}> <OrderStatus /> </Suspense> );}Open the console under the result. At first it shows OrderStatus starts but no use gave back line: line 5 ran, line 6 suspended, and lines 7 and 8 never ran. A second later, both lines appear: React called OrderStatus again, and this time line 6 returned the order straight away. You see each line twice because the preview runs in StrictMode, which renders every component twice in development to catch mistakes.
How does the second call get the value at once? React marks the promise. The first time use sees a promise, React writes status: "pending" on it and adds a callback. When the promise settles, the callback writes status: "fulfilled" and the value (or "rejected" and the reason). On the next call, use reads those fields instead of waiting. Try all three states here:
pending- status
not set- value
not set- reason
not set
use found a promise with no status, so React wrote status: "pending" on it and suspended OrderStatus. The Suspense boundary shows its fallback. Nothing after the use line ran.
Read the table row by row: the state of the promise, what use does, and what the reader sees.
| The promise is | What use does | What the reader sees |
|---|---|---|
| Pending: no answer yet | It suspends the component. The lines after it don't run. | The nearest Suspense boundary's fallback, like Checking order 42…. |
| Fulfilled: the answer came | It returns the value at once. | The component, built from the value. |
| Rejected: the request failed | It throws the reason, the error the promise rejected with. | The nearest error boundary's fallback. |
The fallback replaces everything inside the nearest Suspense boundary, and nothing outside it. In the first example, the heading Your orders sat outside the boundary, so it showed at once. With no boundary at all, React has nothing to show in its place, and the whole page waits. Exercise 2 lets you see that.
The promise must be the same on every render
The Suspense lesson showed a page that waited forever because its component made its promise while rendering. Now you can see why. React calls the component again from its first line, so any promise the component makes is made again too. Here the component calls load from data.js, which starts a new request every time, instead of fetchData:
import { Suspense, use } from "react";import { load } from "./data.js"; function OrderStatus() { // Wrong on purpose: load makes a new promise every time this runs. const order = use(load("/orders/42")); return <p>Order {order.id}: {order.status}</p>;} export default function App() { return ( <Suspense fallback={<p>Checking order 42…</p>}> <OrderStatus /> </Suspense> );}The result says Checking order 42… forever, and the console's count of Load /orders/42 goes up about once a second. Line 6 makes promise A, and use suspends. A second later promise A is fulfilled, and React calls OrderStatus again. Line 6 runs again and makes promise B, which is pending, so use suspends again. The order is loaded over and over and never shown.
This page prints no warning, because each new promise takes a full second. React can only spot the mistake when it tries a component again straight away and gets a different promise than on the first try. That happens when the new promise settles almost at once, like use(fetchData("/orders/42").then((order) => order.status)): the cached order is there, but .then makes a new promise on every render. Then React prints this error in the console: A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework.
In plain words: this promise was made while the component rendered, so React can't keep it between renders. Unless you use a library or framework that supports Suspense, get the promise from a cache. Whether React warns or not, the fix is the same.
load in the component, that new call makes promise B, which hasn't settled, so use waits again and the fallback never goes away. With fetchData, the second call gets promise A back from the cache, and use returns the order.The fix is to ask a cache. fetchData (lines 29 to 32 of data.js) keeps each promise in a Map, keyed by its url, and hands back the same promise on every call. Click Add tea after the order shows:
import { Suspense, use, useState } from "react";import { fetchData } from "./data.js"; function OrderStatus() { const order = use(fetchData("/orders/42")); return <p>Order {order.id}: {order.status}</p>;} export default function App() { const [cartCount, setCartCount] = useState(0); return ( <> <button onClick={() => setCartCount(cartCount + 1)}>Add tea ({cartCount})</button> <Suspense fallback={<p>Checking order 42…</p>}> <OrderStatus /> </Suspense> </> );}Line 5 asks the cache, so every render of OrderStatus gets the same promise. Each click on line 13 re-renders App and OrderStatus, but the order stays on screen with no fallback: the promise is already fulfilled, so use returns its value at once. The console shows one Load /orders/42, however many times you click.
Can't a hook keep the promise instead? Not in the component that suspends. A component that suspends before it ever appears on screen hasn't been kept by React yet, so React throws away its state, its refs, and its useMemo, and starts it fresh on the next try:
function OrderStatus() {
// Wrong on purpose: this component suspends before it ever appears,
// so React keeps nothing for it, not even this useMemo.
const promise = useMemo(() => load("/orders/42"), []);
const order = use(promise);
return <p>Order {order.id}: {order.status}</p>;
}This is a piece of a component, not a whole example, so it doesn't run here; the useMemo in the same component button below runs it. Line 4 makes a new promise on every try, just like load on its own. The playground runs OrderStatus with its promise from four places and counts the loads:
0 loadsPick a button above.
not startedEach button runs OrderStatus with its promise from a different place, for four seconds. The order takes one second to arrive.
Where the promise should come from
The rule from the last section is short: make the promise before the component that reads it renders, or keep it somewhere that outlives that component's renders. Three places work. The first is a cache, like fetchData. The second is an event handler: start the request when the reader clicks, and keep the promise in a parent's state.
import { Suspense, use, useState } from "react";import { load } from "./data.js"; function Receipt({ receiptPromise }) { const receipt = use(receiptPromise); return <p>{receipt.items.join(" and ")}: ${receipt.total}</p>;} export default function App() { const [receiptPromise, setReceiptPromise] = useState(null); return ( <> <button onClick={() => setReceiptPromise(load("/receipts/42"))}>Show receipt</button> {receiptPromise && ( <Suspense fallback={<p>Loading the receipt…</p>}> <Receipt receiptPromise={receiptPromise} /> </Suspense> )} </> );}Line 10 keeps receiptPromise in App's state, starting as null. The click on line 13 calls load once and saves the promise it returns. App never suspends (the boundary on line 15 is below it), so its state survives every render, and Receipt gets the same promise each time through its prop. Line 5 reads it. Click Show receipt: Loading the receipt…, then Tea and Cake: $7. Click it again: the click makes a new promise on purpose, a fresh request, so the fallback shows again for 0.8 seconds.
The third place is the server. In a framework with Server Components, like Next.js, some components run on the server. One of them can start a request and pass the promise down without waiting for it. These two files show the idea; they need a framework, so they don't run here:
// OrderPage.jsx: a Server Component. It runs on the server.
import { Suspense } from "react";
import { db } from "./db.js";
import OrderStatus from "./OrderStatus.jsx";
export default function OrderPage() {
// Start the request, but don't wait for it.
const orderPromise = db.orders.find(42);
return (
<Suspense fallback={<p>Checking order 42…</p>}>
<OrderStatus orderPromise={orderPromise} />
</Suspense>
);
}// OrderStatus.jsx: a Client Component. It runs in the browser.
"use client";
import { use } from "react";
export default function OrderStatus({ orderPromise }) {
const order = use(orderPromise);
return <p>Order {order.id}: {order.status}</p>;
}Line 8 of OrderPage.jsx starts the request and keeps the promise, with no await, so the server can send the rest of the page at once. Line 11 passes it to OrderStatus, a Client Component (one that runs in the browser, marked by "use client" on line 2). Line 6 of OrderStatus.jsx reads it with use, and the fallback shows until the server sends the value. A Server Component itself would just await the promise. The Server Components lesson covers them.
OrderStatus renders, or kept somewhere that outlives its renders: a cache, a parent's state, or a Server Component. OrderStatus only reads it. A promise made inside its own render is new each time.Read the table row by row: where the promise is made, whether the next render gets the same one, and when to use it.
| Where the promise is made | Same promise on the next render? | Use it when |
|---|---|---|
A cache, like fetchData(url) | Yes. The cache hands back the promise it saved. | Several components read the same data, or a framework or library gives you the cache. |
| An event handler, then a parent's state | Yes. State keeps it until you set a new one. | The data is needed after a click, like a receipt. |
| A Server Component, passed as a prop | Yes. The server makes it once and sends it down. | Your framework runs components on the server, like Next.js. |
| The reading component's own render | No. Every render makes a new one. | Never. The fallback stays, and the request runs again and again. |
You rarely write fetchData yourself. Frameworks and data libraries keep the cache for you, decide when an answer is old, and fetch it again. The data.js tab shows the smallest version of that idea, so you can see what they do.
A rejected promise goes to an error boundary
Requests fail. When the promise passed to use is rejected, use throws its reason, the error the promise rejected with, just as a throw would. React passes it up to the nearest error boundary: a component that catches errors thrown while its children render and shows a fallback instead. The ErrorBoundary.jsx tab holds a small one; the next lesson explains how it works. The first stock check in data.js fails, like a busy server:
import { Suspense, use } from "react";import ErrorBoundary from "./ErrorBoundary.jsx";import { fetchData } from "./data.js"; function Stock() { const left = use(fetchData("/stock/cake")); return <p>Cake: {left} left</p>;} export default function App() { return ( <> <h3>Carrot cake, $4</h3> <ErrorBoundary fallback={(error) => <p>Couldn't check the stock: {error.message}</p>}> <Suspense fallback={<p>Checking the stock…</p>}> <Stock /> </Suspense> </ErrorBoundary> </> );}Line 6 reads the stock. For 0.6 seconds, the Suspense boundary on line 15 shows Checking the stock…. Then the promise is rejected, use throws, and the ErrorBoundary on line 14 shows its fallback: Couldn't check the stock: The server is busy. Try again. The heading on line 13 stays, because it's outside the boundary. The console shows the error too: by default, React logs every error a boundary catches.
The two boundaries work as a pair. Suspense handles not yet; the error boundary handles never. Put the error boundary outside the Suspense boundary, as lines 14 and 15 do, so one fallback covers the whole part that loads. The practical example below adds a Check again button.
If a failed request has a sensible stand-in, like an empty list of reviews, you can make the cache hand out a promise that never rejects: load(url).catch(() => []). Do it inside the cache, so that promise is made once too.
use reads context too, even after an if
The second resource use reads is a context. use(SomeContext) returns the value of the nearest provider above, the same value useContext(SomeContext) returns (Passing data deeply with context covers both). The difference is where you may call it. useContext is a hook, so it has to run at the top of the component, on every render (The rules of hooks). use may run later, inside an if:
import { createContext, use, useState } from "react"; const CurrencyContext = createContext("USD"); function Price({ amount, signedIn }) { if (!signedIn) { return <p>Sign in to see prices.</p>; } const currency = use(CurrencyContext); return <p>Tea: {amount} {currency}</p>;} export default function App() { const [signedIn, setSignedIn] = useState(false); return ( <CurrencyContext value="EUR"> <button onClick={() => setSignedIn(!signedIn)}>{signedIn ? "Sign out" : "Sign in"}</button> <Price amount={3} signedIn={signedIn} /> </CurrencyContext> );}Line 3 makes CurrencyContext, and line 16 provides "EUR" to everything inside it. In Price, lines 6 to 8 return early for a shopper who isn't signed in. Only after that does line 9 call use(CurrencyContext). With useContext, line 9 would break the rules of hooks, because it doesn't run on every render. Click Sign in: the price shows as Tea: 3 EUR. Click Sign out and it goes back to the message.
Price components sit inside the provider. The signed-in one reaches use(CurrencyContext) and gets "EUR". The signed-out one returns before that line, so it never reads the context.Press Edit & run and change value="EUR" on line 16 to "GBP". After you sign in, the price reads Tea: 3 GBP. Like useContext, use looks for the nearest provider above the component; a provider that Price itself renders wouldn't count.
Where you can and can't call use
use isn't a hook, though its name starts with use. It has its own, shorter list of rules. Read the table row by row: the question, the answer for hooks, and the answer for use.
| Question | Hooks like useState and useContext | use |
|---|---|---|
Can you call it inside an if or a loop? | No. A hook must run in the same order on every render. | Yes. React doesn't need it at the same place on every render. |
Can you call it after an early return? | No. The renders that return early would skip it. | Yes. Price reads the currency after its early return. |
Can you call it inside try and catch? | No. The rules of hooks forbid it. | No. Your catch would catch React's signal, and React warns. |
| Can you call it in an event handler or an Effect? | No. Only while a component renders. | No. Only while a component renders. |
| Who may call it? | Components and hooks. | Components and hooks. |
One place is never allowed: inside try and catch. To suspend, use throws React's signal, and your catch would catch it before React does:
import { Suspense, use } from "react";import { fetchData } from "./data.js"; function OrderStatus() { let status = "unknown"; try { // Wrong on purpose: use must not sit inside try. status = use(fetchData("/orders/42")).status; } catch { status = "unknown"; } return <p>Order 42: {status}</p>;} export default function App() { return ( <Suspense fallback={<p>Checking order 42…</p>}> <OrderStatus /> </Suspense> );}The result shows Order 42: unknown straight away and keeps showing it. Line 8 threw the signal, line 9 caught it, and line 10 set unknown, so React never learned it should wait. The console shows React's warning: `use` was called from inside a try/catch block. To handle a failed promise, use an error boundary, as in the last section.
One more rule: always hand the promise to use, even when you think it has settled. Don't read its status field yourself to skip the call. Those fields are React's bookkeeping, and skipping use hides the read from React and its developer tools.
// Wrong: reading the promise's status yourself to skip use.
const order = orderPromise.status === "fulfilled" ? orderPromise.value : use(orderPromise);
// Right: always hand the promise to use, and let React decide.
const order = use(orderPromise);Line 2 is the wrong version, and line 5 the right one. These lines only show the pattern, so they don't run here. You may call use inside an if that depends on your props or state, as Price did; just don't make the choice depend on the promise itself.
An order page that reads data three ways
Here everything comes together: an order from the cache, a receipt from a click, a currency read only when needed, and a stock check that fails once and can be retried. Watch the result: the stock check fails first, then the order arrives.
import { Suspense, createContext, use, useState } from "react";import ErrorBoundary from "./ErrorBoundary.jsx";import { fetchData, load, refetchData } from "./data.js"; const CurrencyContext = createContext("USD"); function OrderStatus() { const order = use(fetchData("/orders/42")); return <p>Order {order.id}: {order.status}</p>;} function Receipt({ receiptPromise, showTotal }) { const receipt = use(receiptPromise); let total = ""; if (showTotal) { const currency = use(CurrencyContext); total = ` (${receipt.total} ${currency})`; } return <p>Receipt: {receipt.items.join(" and ")}{total}</p>;} function Stock() { const left = use(fetchData("/stock/cake")); return <p>Cake: {left} left</p>;} export default function App() { const [receiptPromise, setReceiptPromise] = useState(null); const [stockTries, setStockTries] = useState(1); function checkStockAgain() { refetchData("/stock/cake"); setStockTries(stockTries + 1); } return ( <CurrencyContext value="EUR"> <Suspense fallback={<p>Checking order 42…</p>}> <OrderStatus /> </Suspense> <button onClick={() => setReceiptPromise(load("/receipts/42"))}>Show receipt</button> {receiptPromise && ( <Suspense fallback={<p>Loading the receipt…</p>}> <Receipt receiptPromise={receiptPromise} showTotal={true} /> </Suspense> )} <ErrorBoundary key={stockTries} fallback={(error) => ( <p> {error.message} <button onClick={checkStockAgain}>Check again</button> </p> )} > <Suspense fallback={<p>Checking the stock…</p>}> <Stock /> </Suspense> </ErrorBoundary> </CurrencyContext> );}- Line 8:
OrderStatusreads the order from the cache, inside its own boundary (lines 38 to 40). - Lines 12 to 20:
Receiptreads the promise from its prop, then reads the currency withuseinside theifon line 15. The click on line 41 makes that promise. - Line 23:
Stockreads the stock from the cache. The first answer is a failure, so theErrorBoundaryon lines 47 to 58 shows the message and a Check again button. - Lines 31 to 34:
checkStockAgaindrops the failed promise from the cache withrefetchDataand adds one tostockTries. That number is the boundary'skeyon line 48, so React replaces the boundary with a fresh one, its error forgotten (Preserving and resetting state explains keys).Stockrenders again, gets a new promise, and this time the server answersCake: 3 left.
Click Check again, then Show receipt. The receipt line ends with (7 EUR), the total in the currency from line 37.
use(fetchData("/orders/42"))use(load("/orders/42"))use(fetchData("/orders/42").then((order) => order.status))use(receiptPromise) // a prop, made in a parent's click handleruse(fetch("/api/orders/42").then((r) => r.json()))use(menuPromise) // made once, at the top of the fileuse(useMemo(() => load("/orders/42"), [])) // in the component that suspendsuse(orderPromise) // a prop from a Server Component
Each card is a use call inside a component. Sort it: does the component get the same promise every time it renders, or a new one?
Common mix-ups
- "use is a hook, so the rules of hooks apply." No.
usecan be called insideif, loops, and after an early return. It still runs only while a component or hook renders, and never insidetry. - "use waits for the promise, like await." No. It can't pause the function. It stops it, React shows a fallback, and React calls the component again from the top once the promise settles.
- "I can create the promise right where I call use." No. A promise made while rendering is new on every render, so the fallback never goes away. Get it from a cache, a parent's state, or a Server Component.
- "useMemo or useState in the same component keeps the promise." No. A component that suspends before it first appears is thrown away and started fresh, hooks and all. Keep the promise above it.
- "Wrap use in try/catch to handle a failed request." No. That catches React's signal and breaks the wait. Put an error boundary around the component instead.
- "use replaces Suspense." No. They work together:
usesuspends, and the nearest Suspense boundary decides what shows meanwhile.
Practice exercises
Each exercise has a live result: click in it, change the code with Edit & run, and open the console to see what data.js loads.
Predict first: the order shows, and then you click Add tea three times. How many Load /orders/42 lines has the console printed in all? Then try it.
import { Suspense, use, useState } from "react";import { fetchData } from "./data.js"; function OrderStatus() { const order = use(fetchData("/orders/42")); return <p>Order {order.id}: {order.status}</p>;} export default function App() { const [cartCount, setCartCount] = useState(0); return ( <> <button onClick={() => setCartCount(cartCount + 1)}>Add tea ({cartCount})</button> <Suspense fallback={<p>Checking order 42…</p>}> <OrderStatus /> </Suspense> </> );}import { Suspense, use, useState } from "react";import { fetchData } from "./data.js"; function OrderStatus() { const order = use(fetchData("/orders/42")); return <p>Order {order.id}: {order.status}</p>;} export default function App() { const [cartCount, setCartCount] = useState(0); return ( <> <button onClick={() => setCartCount(cartCount + 1)}>Add tea ({cartCount})</button> <Suspense fallback={<p>Checking order 42…</p>}> <OrderStatus /> </Suspense> </> );}Line 5 asks the cache. The first render starts the one request; every later render, including the three clicks, gets the same promise back. So Load /orders/42 appears once.
The page stays empty for a second, and then the heading and the order appear together. Make the heading show at once. With your fix, what does the page show during the first second?
import { use } from "react";import { fetchData } from "./data.js"; function OrderStatus() { const order = use(fetchData("/orders/42")); return <p>Order {order.id}: {order.status}</p>;} export default function App() { return ( <> <h3>Your orders</h3> <OrderStatus /> </> );}import { Suspense, use } from "react";import { fetchData } from "./data.js"; function OrderStatus() { const order = use(fetchData("/orders/42")); return <p>Order {order.id}: {order.status}</p>;} export default function App() { return ( <> <h3>Your orders</h3> <Suspense fallback={<p>Checking order 42…</p>}> <OrderStatus /> </Suspense> </> );}Without a boundary, the whole page waited a second, heading included. The boundary limits the wait to OrderStatus: the heading shows at once, beside Checking order 42….
The receipt never shows. Fix it with one change. What does the page show once the receipt has arrived?
import { Suspense, use } from "react";import { load } from "./data.js"; function Receipt() { const receipt = use(load("/receipts/42")); return <p>{receipt.items.join(" and ")}: ${receipt.total}</p>;} export default function App() { return ( <Suspense fallback={<p>Loading the receipt…</p>}> <Receipt /> </Suspense> );}import { Suspense, use } from "react";import { fetchData } from "./data.js"; function Receipt() { const receipt = use(fetchData("/receipts/42")); return <p>{receipt.items.join(" and ")}: ${receipt.total}</p>;} export default function App() { return ( <Suspense fallback={<p>Loading the receipt…</p>}> <Receipt /> </Suspense> );}load made a new promise on every render, so the receipt was loaded again and again. fetchData keeps one promise per url, so the second render reads its value: Tea and Cake: $7.
Show the currency after each price that the shopper is allowed to see. What does the Tea line show?
import { createContext, use } from "react"; const CurrencyContext = createContext("USD"); function Price({ name, amount, signedIn }) { if (!signedIn) { return <p>{name}: sign in to see the price.</p>; } // Read the currency here, and show it after the amount. return <p>{name}: {amount}</p>;} export default function App() { return ( <CurrencyContext value="GBP"> <Price name="Tea" amount={3} signedIn={true} /> <Price name="Cake" amount={4} signedIn={false} /> </CurrencyContext> );}import { createContext, use } from "react"; const CurrencyContext = createContext("USD"); function Price({ name, amount, signedIn }) { if (!signedIn) { return <p>{name}: sign in to see the price.</p>; } const currency = use(CurrencyContext); return <p>{name}: {amount} {currency}</p>;} export default function App() { return ( <CurrencyContext value="GBP"> <Price name="Tea" amount={3} signedIn={true} /> <Price name="Cake" amount={4} signedIn={false} /> </CurrencyContext> );}use(CurrencyContext) may run after the early return, so only the signed-in price reads the context. The nearest provider says "GBP", so the line reads Tea: 3 GBP.
The receipt starts loading only after the click, so it takes 0.8 seconds to appear. Make it start loading as soon as the pointer reaches the button. Then rest the pointer on the button for a second and click. Does the receipt appear at once, or after 0.8 seconds?
import { Suspense, use, useState } from "react";import { fetchData } from "./data.js"; function Receipt() { const receipt = use(fetchData("/receipts/42")); return <p>{receipt.items.join(" and ")}: ${receipt.total}</p>;} export default function App() { const [open, setOpen] = useState(false); return ( <> <button onClick={() => setOpen(true)}>Show receipt</button> {open && ( <Suspense fallback={<p>Loading the receipt…</p>}> <Receipt /> </Suspense> )} </> );}import { Suspense, use, useState } from "react";import { fetchData } from "./data.js"; function Receipt() { const receipt = use(fetchData("/receipts/42")); return <p>{receipt.items.join(" and ")}: ${receipt.total}</p>;} export default function App() { const [open, setOpen] = useState(false); return ( <> <button onMouseEnter={() => fetchData("/receipts/42")} onClick={() => setOpen(true)} > Show receipt </button> {open && ( <Suspense fallback={<p>Loading the receipt…</p>}> <Receipt /> </Suspense> )} </> );}Moving the pointer onto the button starts the request, and the cache keeps its promise. If you wait 0.8 seconds before clicking, the promise has settled by the time Receipt reads the same promise, so the receipt appears at once instead of 0.8 seconds after the click.
After 0.6 seconds the stock check fails and the whole page goes. Keep the page, and show a message in place of the stock line. With your fix, does the heading Carrot cake, $4 stay?
import { Suspense, use } from "react";import ErrorBoundary from "./ErrorBoundary.jsx";import { fetchData } from "./data.js"; function Stock() { const left = use(fetchData("/stock/cake")); return <p>Cake: {left} left</p>;} export default function App() { return ( <> <h3>Carrot cake, $4</h3> <Suspense fallback={<p>Checking the stock…</p>}> <Stock /> </Suspense> </> );}import { Suspense, use } from "react";import ErrorBoundary from "./ErrorBoundary.jsx";import { fetchData } from "./data.js"; function Stock() { const left = use(fetchData("/stock/cake")); return <p>Cake: {left} left</p>;} export default function App() { return ( <> <h3>Carrot cake, $4</h3> <ErrorBoundary fallback={() => <p>Stock unknown. You can still order.</p>}> <Suspense fallback={<p>Checking the stock…</p>}> <Stock /> </Suspense> </ErrorBoundary> </> );}With no error boundary, the error reached the top, and React removed the whole page. The boundary catches it and shows its fallback in place of the stock line, so the heading Carrot cake, $4 stays.
Check your understanding
Question 1 of 7
OrderStatuscallsuse(promise)while the promise is still pending. What happens?Choose an answer to see the explanation.
Question 2 of 7
loadstarts a new request on every call. What does this page show five seconds later?Read the code, then predictJSXfunction OrderStatus() { const order = use(load("/orders/42")); return <p>Order 42: {order.status}</p>; } // In App: <Suspense fallback={<p>Checking order 42…</p>}> <OrderStatus /> </Suspense>Choose an answer to see the explanation.
Question 3 of 7Does
useMemokeep the same promise in this component?Read the code, then predictJSXfunction OrderStatus() { // Wrong on purpose: this component suspends before it ever appears, // so React keeps nothing for it, not even this useMemo. const promise = useMemo(() => load("/orders/42"), []); const order = use(promise); return <p>Order {order.id}: {order.status}</p>; }Choose an answer to see the explanation.
Question 4 of 7
signedInisfalseon one render andtrueon the next. Is this call touseallowed?Read the code, then predictJSXfunction Price({ amount, signedIn }) { if (!signedIn) return <p>Sign in to see prices.</p>; const currency = use(CurrencyContext); return <p>Tea: {amount} {currency}</p>; }Choose an answer to see the explanation.
Question 5 of 7The stock promise is rejected, and there's no error boundary anywhere. What does the reader see?
Read the code, then predictJSXfunction Stock() { const left = use(fetchData("/stock/cake")); // this promise rejects return <p>Cake: {left} left</p>; } // In App, with no error boundary anywhere: <Suspense fallback={<p>Checking the stock…</p>}> <Stock /> </Suspense>Choose an answer to see the explanation.
Question 6 of 7Why must
usenever sit insidetryandcatch?Choose an answer to see the explanation.
Question 7 of 7Which promise can a component safely pass to
use?Choose an answer to see the explanation.
Key takeaways
use(promise)returns the value of a fulfilled promise, suspends while it's pending, and throws if it's rejected.- A Suspense boundary shows a fallback while the component waits; an error boundary shows one if the promise is rejected.
- After a promise settles, React calls the component again from the top, so the promise must be the same one: from a cache, a parent's state, or a Server Component.
- A promise made in render, even inside
useMemoin the component that suspends, is new each time and never shows. use(context)reads the nearest provider, likeuseContext, but may run after an early return or inside anif.- Call
useonly while rendering, never insidetry, and never skip it by reading the promise's status.
use reads a resource while rendering, and React handles the wait.
Give it a promise that lasts, a Suspense boundary for the wait, and an error boundary for the failure.
Coming next: Error boundaries. It shows how to write the ErrorBoundary this lesson borrowed, what it can and can't catch, how to retry, and how to report errors.