You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Enhance documentation on using Promises in Client Components with use
This commit adds important guidelines regarding the caching and stability of Promises passed to the `use` API in Client Components. It includes examples demonstrating how to properly cache Promises to prevent components from re-suspending on every render, as well as addressing pitfalls related to using uncached and chained Promises.
Copy file name to clipboardExpand all lines: src/content/reference/react/use.md
+167Lines changed: 167 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -50,6 +50,9 @@ The `use` API returns the value that was read from the resource like the resolve
50
50
* The `use` API must be called inside a Component or a Hook.
51
51
* When fetching data in a [Server Component](/reference/rsc/server-components), prefer `async` and `await` over `use`. `async` and `await` pick up rendering from the point where `await` was invoked, whereas `use` re-renders the component after the data is resolved.
52
52
* Prefer creating Promises in [Server Components](/reference/rsc/server-components) and passing them to [Client Components](/reference/rsc/use-client) over creating Promises in Client Components. Promises created in Client Components are recreated on every render. Promises passed from a Server Component to a Client Component are stable across re-renders. [See this example](#streaming-data-from-server-to-client).
53
+
* A Promise used in Client Components and passed to `use` must be cached or stable between renders (e.g. not recreated between renders); otherwise each render creates a new Promise and the component may suspend indefinitely.
54
+
* A Promise passed to `use` that comes from chaining [`.then`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then), [`.catch`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch), or [`.finally`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally) must also be cached or stable between renders; each call returns a new Promise.
55
+
53
56
54
57
---
55
58
@@ -316,6 +319,139 @@ But using `await` in a [Server Component](/reference/rsc/server-components) will
316
319
317
320
</DeepDive>
318
321
322
+
### Using Promises in Client Components {/*using-promises-in-client-components*/}
323
+
324
+
When you pass a Promise to `use` that was created in a Client Component, it must be cached or stable between renders. One way to do that is to memoize the async work by input—for example, a cache keyed by the same arguments returns the same Promise for the same arguments. In this example, `getDoubleCountCached(count)` returns the same Promise for a given `count`, so the component does not re-suspend when re-rendering with the same count.
##### Using an uncached Promise in a Client Component keeps the app in the loading state. {/*pitfall-uncached-client-promise*/}
414
+
415
+
If you pass `getDoubleCount(count)` instead of `getDoubleCountCached(count)` to `use`, a new Promise is created on every render. React treats it as a new resource each time, so the component suspends again and the Suspense fallback stays visible. The app will appear stuck on the loading state (or flicker between loading and content). Always cache or otherwise stabilize client-created Promises passed to `use`.
416
+
417
+
```js
418
+
// ❌ New Promise every render — component re-suspends each time
419
+
constdoubleCount=use(getDoubleCount(count));
420
+
421
+
// ✅ Same Promise for same count — suspends once per count
##### Chained Promises (`.then`, `.catch`, `.finally`) must be cached too. {/*pitfall-chained-promise*/}
426
+
427
+
Each call to `.then`, `.catch`, or `.finally` returns a new Promise. If you pass that chained Promise to `use` without caching it, you get a new Promise every render and the component will re-suspend each time.
428
+
429
+
```js
430
+
// ❌ New Promise every render — .then() returns a new Promise each time
431
+
constdata=use(fetch(url).then((r) =>r.json()));
432
+
433
+
constfetchJsonCached= (() => {
434
+
constcache=newMap();
435
+
return (url) => {
436
+
if (!cache.has(url)) {
437
+
cache.set(url, fetch(url).then((r) =>r.json()));
438
+
}
439
+
returncache.get(url);
440
+
};
441
+
})();
442
+
443
+
functionMyComponent() {
444
+
// ✅ Cache the chained Promise so the same reference is used for the same url
445
+
constdata=use(fetchJsonCached('/api/my-api'));
446
+
447
+
return<div> {data} </div>
448
+
}
449
+
```
450
+
451
+
</Pitfall>
452
+
453
+
---
454
+
319
455
### Dealing with rejected Promises {/*dealing-with-rejected-promises*/}
320
456
321
457
In some cases a Promise passed to `use` could be rejected. You can handle rejected Promises by either:
@@ -438,6 +574,36 @@ To use the Promise's <CodeStep step={1}>`catch`</CodeStep> method, call <CodeSte
438
574
439
575
## Troubleshooting {/*troubleshooting*/}
440
576
577
+
### My component stays on the loading state (or keeps flickering) when I use a Promise with `use` {/*promise-not-stable*/}
578
+
579
+
The Promise you pass to `use` is likely recreated on every render. React treats a new Promise as a new resource, so the component suspends again each time and the Suspense fallback stays visible (or the UI flickers between fallback and content). This often happens when the Promise is created inside a Client Component without being cached or stored, or when you pass the result of `.then()`, `.catch()`, or `.finally()`—each call returns a new Promise, so that chained result must be cached too.
580
+
581
+
```jsx
582
+
// ❌ New Promise every render — component re-suspends each time
583
+
functionMyComponent({ id }) {
584
+
constdata=use(fetchData(id)); // fetchData(id) returns a new Promise each render
585
+
return<p>{data}</p>;
586
+
}
587
+
```
588
+
589
+
Cache or otherwise stabilize the Promise between renders. For example, store it in state, or use a cache keyed by the same inputs so the same Promise is returned for the same arguments. [See the Client Component caching example.](#using-promises-in-client-components)
590
+
591
+
```jsx
592
+
// ✅ Same Promise for same id — cache returns stable reference per argument
593
+
constpromiseCache=newMap();
594
+
functionfetchDataCached(id) {
595
+
if (!promiseCache.has(id)) promiseCache.set(id, fetchData(id));
596
+
returnpromiseCache.get(id);
597
+
}
598
+
599
+
functionMyComponent({ id }) {
600
+
constdata=use(fetchDataCached(id));
601
+
return<p>{data}</p>;
602
+
}
603
+
```
604
+
605
+
---
606
+
441
607
### "Suspense Exception: This is not a real error!" {/*suspense-exception-error*/}
442
608
443
609
You are either calling `use` outside of a React Component or Hook function, or calling `use` in a try–catch block. If you are calling `use` inside a try–catch block, wrap your component in an Error Boundary, or call the Promise's `catch` to catch the error and resolve the Promise with another value. [See these examples](#dealing-with-rejected-promises).
@@ -460,3 +626,4 @@ function MessageComponent({messagePromise}) {
0 commit comments