From 40ace0551470fcb86d4d3496515b9eac396e4a89 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Tue, 4 Nov 2025 15:04:29 +0100 Subject: [PATCH 1/5] Docs for Promise subclasses --- src/content/reference/react/use.md | 82 ++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md index dc43fa2289c..6878c152827 100644 --- a/src/content/reference/react/use.md +++ b/src/content/reference/react/use.md @@ -436,6 +436,88 @@ To use the Promise's `catch` method, call + +```js src/App.js active +import { Suspense, use, useState } from "react"; +import { preload } from "./data-fetching.js"; + +function UserDetails({ userUsable }) { + const user = use(userUsable); + return

Hello, {user}!

; +} + +export default function App() { + const [userId, setUserId] = useState(null); + // The initial + const [userUsable, setUser] = useState(null); + + return ( +
+

+ Passing the Promise without the status field will show the + fallback because the Promise resolves in the next microtask. +

+ + + Loading user...

}> + {userUsable ? ( + + ) : ( +

No user selected

+ )} +
+
+ ); +} +``` + + +```js src/data-fetching.js +export function preload(id) { + // This is not a real implementation of getting the Promise for the user. + // The actual implementation should cache the Promise + const promise = Promise.resolve(`User #${id}`); + + // Setting the `status` field allows React to synchronously read + // the value if the Promise is already settled by the time the Promise is passed to `use`. + promise.status = "pending"; + promise.then( + (value) => { + promise.status = "fulfilled"; + promise.value = value; + }, + (error) => { + promise.status = "rejected"; + promise.reason = error; + }, + ); + return promise; +} +``` + + + ## Troubleshooting {/*troubleshooting*/} ### "Suspense Exception: This is not a real error!" {/*suspense-exception-error*/} From 12aa604602532568489823a2589824a6e4fd8a7f Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Tue, 4 Nov 2025 19:50:05 +0100 Subject: [PATCH 2/5] Use recipes instead --- src/content/reference/react/use.md | 94 +++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 15 deletions(-) diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md index 6878c152827..4ef4f4d1290 100644 --- a/src/content/reference/react/use.md +++ b/src/content/reference/react/use.md @@ -440,13 +440,27 @@ To use the Promise's `catch` method, call + +#### Passing a basic Promise {/*passing-a-basic-promise*/} ```js src/App.js active import { Suspense, use, useState } from "react"; -import { preload } from "./data-fetching.js"; + +function preloadUser(id) { + // This is not a real implementation of getting the Promise for the user. + // A real implementation would probably call `fetch` or another data fetching method. + // The actual implementation should cache the Promise. + const promise = Promise.resolve(`User #${id}`); + + return promise; +} function UserDetails({ userUsable }) { const user = use(userUsable); @@ -460,25 +474,21 @@ export default function App() { return (
-

- Passing the Promise without the status field will show the - fallback because the Promise resolves in the next microtask. -

Loading user...

}> {userUsable ? ( @@ -492,11 +502,22 @@ export default function App() { } ``` + + + -```js src/data-fetching.js -export function preload(id) { +#### Passing the Promise with a `status` field {/*passing-the-promise-with-the-status-field*/} + + + + +```js src/App.js active +import { Suspense, use, useState } from "react"; + +function preloadUser(id) { // This is not a real implementation of getting the Promise for the user. - // The actual implementation should cache the Promise + // A real implementation would probably call `fetch` or another data fetching method. + // The actual implementation should cache the Promise. const promise = Promise.resolve(`User #${id}`); // Setting the `status` field allows React to synchronously read @@ -514,10 +535,53 @@ export function preload(id) { ); return promise; } + +function UserDetails({ userUsable }) { + const user = use(userUsable); + return

Hello, {user}!

; +} + +export default function App() { + const [userId, setUserId] = useState(null); + // The initial + const [userUsable, setUser] = useState(null); + + return ( +
+ + + Loading user...

}> + {userUsable ? ( + + ) : ( +

No user selected

+ )} +
+
+ ); +} ```
+ + + + ## Troubleshooting {/*troubleshooting*/} ### "Suspense Exception: This is not a real error!" {/*suspense-exception-error*/} From d74a5922231ee677be38d40a6ff25b2d98446445 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Tue, 4 Nov 2025 20:22:10 +0100 Subject: [PATCH 3/5] target audience --- src/content/reference/react/use.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md index 4ef4f4d1290..027f7442462 100644 --- a/src/content/reference/react/use.md +++ b/src/content/reference/react/use.md @@ -438,6 +438,8 @@ To use the Promise's `catch` method, call Date: Wed, 5 Nov 2025 09:34:09 +0100 Subject: [PATCH 4/5] Include variant with subclasses --- src/content/reference/react/use.md | 132 +++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 9 deletions(-) diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md index 027f7442462..ed59226e819 100644 --- a/src/content/reference/react/use.md +++ b/src/content/reference/react/use.md @@ -440,7 +440,7 @@ To use the Promise's `catch` method, call { + resolve = _resolve; + reject = _reject; + }); + // Setting the `status` field allows React to synchronously read + // the value if the Promise is already settled by the time the Promise is + // passed to `use`. + executor( + (value) => { + this.status = "fulfilled"; + this.value = value; + resolve(value); + }, + (reason) => { + this.status = "rejected"; + this.reason = reason; + reject(reason); + } + ); + } +} function preloadUser(id) { // This is not a real implementation of getting the Promise for the user. // A real implementation would probably call `fetch` or another data fetching method. // The actual implementation should cache the Promise. - const promise = Promise.resolve(`User #${id}`); + // The important part is that we are using the PromiseWithStatus subclass here. + // Check out the next step if you're not controlling the Promise creation + // (e.g. when `fetch` is used). + const promise = PromiseWithStatus.resolve(`User #${id}`); + + return promise; +} + +function UserDetails({ userUsable }) { + const user = use(userUsable); + return

Hello, {user}!

; +} + +export default function App() { + const [userId, setUserId] = useState(null); + // The initial + const [userUsable, setUser] = useState(null); - // Setting the `status` field allows React to synchronously read - // the value if the Promise is already settled by the time the Promise is passed to `use`. + return ( +
+ + + Loading user...

}> + {userUsable ? ( + + ) : ( +

No user selected

+ )} +
+
+ ); +} + +``` + + + + + +#### Simplified implementation setting the `status` field {/*simplified-implementation-setting-the-status-field*/} + + + +```js src/App.js active +import { Suspense, use, useState } from "react"; +import { flushSync } from "react-dom"; + +function preloadUser(id) { + const value = `User #${id}`; + // This is not a real implementation of getting the Promise for the user. + // A real implementation would probably call `fetch` or another data fetching method. + // The actual implementation should cache the Promise. + const promise = Promise.resolve(value); + + // We don't need to create a custom subclass. + // We can just set the necessary fields directly on the Promise. promise.status = "pending"; promise.then( (value) => { @@ -533,8 +634,15 @@ function preloadUser(id) { (error) => { promise.status = "rejected"; promise.reason = error; - }, + } ); + + // Setting the status in `.then` is too late if we want to create an already + // settled Promise. We only included setting the fields in `.then` for + // illustration purposes. Since our demo wants an already resolved Promise, + // we set the necessary fields synchronously. + promise.status = "fulfilled"; + promise.value = value; return promise; } @@ -552,16 +660,22 @@ export default function App() {
- Loading user...

}> + Loading

}> {userUsable ? ( ) : ( @@ -529,8 +530,9 @@ class PromiseWithStatus extends Promise { resolve = _resolve; reject = _reject; }); - // Setting the `status` field allows React to synchronously read - // the value if the Promise is already settled by the time the Promise is + // Setting the `status` field allows React to + // synchronously read the value if the Promise + // is already settled by the time the Promise is // passed to `use`. executor( (value) => { @@ -548,11 +550,13 @@ class PromiseWithStatus extends Promise { } function preloadUser(id) { - // This is not a real implementation of getting the Promise for the user. - // A real implementation would probably call `fetch` or another data fetching method. + // This is not a real implementation of getting the + // Promise for the user. A real implementation would + // probably call `fetch` or another data fetching method. // The actual implementation should cache the Promise. - // The important part is that we are using the PromiseWithStatus subclass here. - // Check out the next step if you're not controlling the Promise creation + // The important part is that we are using the + // PromiseWithStatus subclass here. Check out the next + // step if you're not controlling the Promise creation // (e.g. when `fetch` is used). const promise = PromiseWithStatus.resolve(`User #${id}`); @@ -573,8 +577,9 @@ export default function App() {
- Loading user...

}> + Loading

}> {userUsable ? ( ) : ( @@ -618,13 +623,15 @@ import { flushSync } from "react-dom"; function preloadUser(id) { const value = `User #${id}`; - // This is not a real implementation of getting the Promise for the user. - // A real implementation would probably call `fetch` or another data fetching method. + // This is not a real implementation of getting the + // Promise for the user. A real implementation would + // probably call `fetch` or another data fetching method. // The actual implementation should cache the Promise. const promise = Promise.resolve(value); // We don't need to create a custom subclass. - // We can just set the necessary fields directly on the Promise. + // We can just set the necessary fields directly on the + // Promise. promise.status = "pending"; promise.then( (value) => { @@ -637,10 +644,11 @@ function preloadUser(id) { } ); - // Setting the status in `.then` is too late if we want to create an already - // settled Promise. We only included setting the fields in `.then` for - // illustration purposes. Since our demo wants an already resolved Promise, - // we set the necessary fields synchronously. + // Setting the status in `.then` is too late if we want + // to create an already settled Promise. We only included + // setting the fields in `.then` for illustration + // purposes. Since our demo wants an already resolved + // Promise, we set the necessary fields synchronously. promise.status = "fulfilled"; promise.value = value; return promise; @@ -660,8 +668,9 @@ export default function App() {
- Loading user...

}> + Loading

}> {userUsable ? ( ) : (