Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions src/content/reference/react/use.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)).
Copy link
Member

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?

const dataPromise = {
  then: () => {},
  status: 'fulfilled',
  value: `some data'
}

Copy link
Member Author

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.

Copy link
Contributor

@sebmarkbage sebmarkbage Nov 4, 2025

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:

class FulfilledPromise extends Promise {
  status = 'fulfilled';
  value = null;
  constructor(data) {
     super(resolve => resolve(data));
     this.value = data;
  }
}
const promise = new FulfilledPromise(data);

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:

const promise = Promise.resolve(data);
promise.status = 'fulfilled';
promise.value = data;

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).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is great content and very helpful!

It's a pretty advanced topic though so maybe point out the intended audience early? Maybe lead with:

If you are implementing a Suspense-enabled library, you can help React avoid unnecessarily suspending when you know the promise has already settled, by using status and value or reason fields.

I think "subclass" and "microtask" is likely language that will trip people up, but if it's clear this is an advanced topic I think that's fine.

Maybe be a bit more explicit about the rejected/reason case as well?

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.

I think this should be its own paragraph as it's a separate point.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

<Sandpack>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do something like this instead:

Screenshot 2025-11-04 at 9 31 49 AM

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 subclass

function getUser(id) {
  return {
    status: 'fulfilled',
    value: `User #${id}`,
    then: () => {}
  }
}


function UserDetails() {
  const user = use(getUser());
  return <p>Hello, {user}!</p>;
}

Without a Promise subclass

function getUser(id) {
  return Promise.resolve(`User #${id}`);
}


function UserDetails() {
  const user = use(getUser());
  return <p>Hello, {user}!</p>;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link

Choose a reason for hiding this comment

The 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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not critical, but I have a few suggestions on this bit:

  • the comment is not very clear. I suggest to either remove it or adapt it.
  • when reading through the JSX, I was looking for a setUserUsable call. I realized later that the state and the setter functions were not named consistently. I suggest we make them consistent.
  • I assume you are using the "Usable" suffix to emphasize the state is not a raw promise? Should the "Promise" suffix be discouraged when naming variables provided to use()?

Copy link

@debel27 debel27 Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the third point, I noticed the TypeScript/Flow definitions use usable as well:

export function use<T>(usable: Usable<T>): T;

What do you think about aligning the docs with that terminology (e.g. use(resource) -> use(usable))?


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*/}
Expand Down