From 1e284d44bd68adab93d39d43f145bec7dbc6edbf Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Tue, 22 Sep 2026 17:08:07 -0700 Subject: [PATCH 1/3] Document conditional `use` call order on the server --- src/content/reference/react/use.md | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md index ab0db0dd8ec..e383408c234 100644 --- a/src/content/reference/react/use.md +++ b/src/content/reference/react/use.md @@ -1537,3 +1537,37 @@ const albums = use(fetchData('/albums')); ``` See [caching Promises for Client Components](#caching-promises-for-client-components) for more details. + +--- + +### `use` returns the value of a different Promise {/*wrong-promise-value*/} + +On the server, React matches each `use` call to its Promise by call order, not by the Promise itself. If a component suspends and runs again, React reuses the Promise already recorded at each position. + +If `use` is called conditionally, and the condition stops `use` from being called once its Promise resolves, the next `use` call takes the skipped position and receives the earlier Promise's value: + +```js +function Album() { + // 🔴 Called on the first attempt, which resolves `tracksPromise` + // and sets `cache.tracks`. Skipped on the attempt after that. + const tracks = cache.tracks ?? use(tracksPromise); + + // Now the 1st `use` call instead of the 2nd, so React returns + // the Promise recorded in that position: `tracksPromise`. + const artist = use(artistPromise); +} +``` + +To fix this, move the `use` calls out of the conditions so the same `use` calls run in the same order on every attempt: + +```js +function Album() { + // ✅ Always the 1st and 2nd `use` calls + const loadedTracks = use(tracksPromise); + const artist = use(artistPromise); + + const tracks = cache.tracks ?? loadedTracks; +} +``` + +This does not affect the browser. From d191c36709b6bd9850d8b531f6310acbfeba02f3 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Wed, 23 Sep 2026 15:57:54 -0700 Subject: [PATCH 2/3] Address review feedback --- src/content/reference/react/use.md | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md index e383408c234..0958f499380 100644 --- a/src/content/reference/react/use.md +++ b/src/content/reference/react/use.md @@ -1540,34 +1540,32 @@ See [caching Promises for Client Components](#caching-promises-for-client-compon --- -### `use` returns the value of a different Promise {/*wrong-promise-value*/} +### I get the value of a different Promise from `use` {/*wrong-promise-value*/} -On the server, React matches each `use` call to its Promise by call order, not by the Promise itself. If a component suspends and runs again, React reuses the Promise already recorded at each position. +On the server, React matches each `use` call to its Promise by call order, not by the Promise itself. A component suspends while the Promise passed to `use` is pending, then runs again once that Promise resolves. When resuming, React reuses the Promise already recorded at each position. -If `use` is called conditionally, and the condition stops `use` from being called once its Promise resolves, the next `use` call takes the skipped position and receives the earlier Promise's value: +If `use` causes a suspend and `use` is no longer called after resuming, every later `use` call with a Promise shifts up a position. This means `use` may be passed a Promise meant for a different callsite. This happens when a component stops calling `use` once its data is cached: ```js function Album() { - // 🔴 Called on the first attempt, which resolves `tracksPromise` - // and sets `cache.tracks`. Skipped on the attempt after that. - const tracks = cache.tracks ?? use(tracksPromise); + // 🔴 First `use` is called and causes a suspend. When resuming the + // value for `cache.tracks` is used and `use` is no longer called + const tracks = cache.tracks ?? use(fetchData('/tracks')); - // Now the 1st `use` call instead of the 2nd, so React returns - // the Promise recorded in that position: `tracksPromise`. - const artist = use(artistPromise); + // Now the first `use(Promise)` call, so the Promise for the first + // position (`fetchData('/tracks')`) is passed to it + const artist = use(fetchData('/artist')); } ``` -To fix this, move the `use` calls out of the conditions so the same `use` calls run in the same order on every attempt: +As described in [this pitfall](#conditional-use), always pass the Promise to `use` and let React read it: ```js function Album() { - // ✅ Always the 1st and 2nd `use` calls - const loadedTracks = use(tracksPromise); - const artist = use(artistPromise); - - const tracks = cache.tracks ?? loadedTracks; + // ✅ Both Promises are always passed to `use` + const tracks = use(fetchData('/tracks')); + const artist = use(fetchData('/artist')); } ``` -This does not affect the browser. +This does not affect `use` when called in the browser. From a48d506acac734247c72fdc2fdb6b64fd365f1c4 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Wed, 23 Sep 2026 16:07:35 -0700 Subject: [PATCH 3/3] Align the cache with the examples above --- src/content/reference/react/use.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md index 0958f499380..68c66455d54 100644 --- a/src/content/reference/react/use.md +++ b/src/content/reference/react/use.md @@ -1548,12 +1548,15 @@ If `use` causes a suspend and `use` is no longer called after resuming, every la ```js function Album() { + const tracksPromise = fetchData('/tracks'); + const promiseStatus = tracksPromise.status === 'fulfilled'; + // 🔴 First `use` is called and causes a suspend. When resuming the - // value for `cache.tracks` is used and `use` is no longer called - const tracks = cache.tracks ?? use(fetchData('/tracks')); + // Promise is settled and `use` is no longer called + const tracks = promiseStatus ? tracksPromise.value : use(tracksPromise); // Now the first `use(Promise)` call, so the Promise for the first - // position (`fetchData('/tracks')`) is passed to it + // position (`tracksPromise`) is passed to it const artist = use(fetchData('/artist')); } ```