-
Notifications
You must be signed in to change notification settings - Fork 7.8k
Docs for Promise subclasses #8125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -436,6 +436,88 @@ To use the Promise's <CodeStep step={1}>`catch`</CodeStep> method, call <CodeSte | |
| --- | ||
| ### Avoiding fallbacks by passing Promise subclasses {/*avoiding-fallbacks-by-passing-promise-subclasses*/} | ||
| React will read the `status` field on Promise subclasses to synchronously read the value without waiting for a microtask. If the Promise is already settled (resolved or rejected), React can read the value immediately without suspending and showing a fallback if the update was not part of a Transition (e.g. [`ReactDOM.flushSync()`](/reference/react-dom/flushSync)). | ||
| React will set the `status` field itself if the passed Promise does not have this field set. Suspense-enabled libraries should set the `status` field on the Promises they create to avoid unnecessary fallbacks. Calling `use` conditionally depending on whether a Promise is settled or not is discouraged. `use` should be called unconditionally so that React DevTools can show that the Component may suspend on data. | ||
|
||
| <Sandpack> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we do something like this instead:
And structure it so that the most relevant code (the promise creation) is what's visible first? Like this you should see this: With a Promise subclassfunction getUser(id) {
return {
status: 'fulfilled',
value: `User #${id}`,
then: () => {}
}
}
function UserDetails() {
const user = use(getUser());
return <p>Hello, {user}!</p>;
}Without a Promise subclassfunction getUser(id) {
return Promise.resolve(`User #${id}`);
}
function UserDetails() {
const user = use(getUser());
return <p>Hello, {user}!</p>;
}
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this! Many examples in the docs that compares approaches like this tend to point out what to look for, like "Note how loading the users shows a loading state", but I don't think that's necessary here. |
||
| ```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 <p>Hello, {user}!</p>; | ||
| } | ||
|
|
||
| export default function App() { | ||
| const [userId, setUserId] = useState(null); | ||
| // The initial | ||
| const [userUsable, setUser] = useState(null); | ||
|
Comment on lines
+475
to
+476
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not critical, but I have a few suggestions on this bit:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regarding the third point, I noticed the TypeScript/Flow definitions use export function use<T>(usable: Usable<T>): T;What do you think about aligning the docs with that terminology (e.g. |
||
|
|
||
| return ( | ||
| <div> | ||
| <p> | ||
| Passing the Promise without the <code>status</code> field will show the | ||
| fallback because the Promise resolves in the next microtask. | ||
| </p> | ||
| <button | ||
| onClick={() => { | ||
| setUser(Promise.resolve("User #2")); | ||
| setUserId(2); | ||
| }} | ||
| > | ||
| Load Promise not integrated with Suspense | ||
| </button> | ||
| <button | ||
| onClick={() => { | ||
| setUser(preload(1)); | ||
| setUserId(1); | ||
| }} | ||
| > | ||
| Load Promise integrated with Suspense | ||
| </button> | ||
| <Suspense key={userId} fallback={<p>Loading user...</p>}> | ||
| {userUsable ? ( | ||
| <UserDetails userUsable={userUsable} /> | ||
| ) : ( | ||
| <p>No user selected</p> | ||
| )} | ||
| </Suspense> | ||
| </div> | ||
| ); | ||
| } | ||
| ``` | ||
| ```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; | ||
| } | ||
| ``` | ||
| </Sandpack> | ||
| ## Troubleshooting {/*troubleshooting*/} | ||
| ### "Suspense Exception: This is not a real error!" {/*suspense-exception-error*/} | ||
|
|
||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add some code here for a high-level view of what this looks like?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want to show how to create a custom thenable.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea while you can use a custom Thenable, it's probably recommended to just use a native Promise for this.
You can make it a sub-class like such:
This makes it more palatable for sticklers that thinks adding expandos to native objects is bad. It shows that the pattern is encouraged by the platform. If it wasn't, then why can I sub-class natives? If can't add fields, what am I supposed to do with those sub-classes?
But in practice when you're not adding any methods to the prototype this is really just the same thing as:
Which is smaller code and faster, so why not do that? It also doesn't risk getting compiled which would break native subclassing.
So maybe document one stickler version and one faster version (and hint that thenables might be even faster).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done