Skip to content

Commit cfba52f

Browse files
committed
Include variant with subclasses
1 parent d74a592 commit cfba52f

File tree

1 file changed

+123
-9
lines changed
  • src/content/reference/react

1 file changed

+123
-9
lines changed

src/content/reference/react/use.md

Lines changed: 123 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ To use the Promise's <CodeStep step={1}>`catch`</CodeStep> method, call <CodeSte
440440
441441
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.
442442
443-
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)).
443+
React will read the `status` field on the Promise to synchronously read the value without having to wait 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)).
444444
445445
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.
446446
@@ -515,15 +515,116 @@ export default function App() {
515515
516516
```js src/App.js active
517517
import { Suspense, use, useState } from "react";
518+
import { flushSync } from "react-dom";
519+
520+
class PromiseWithStatus extends Promise {
521+
status = "pending";
522+
value = null;
523+
reason = null;
524+
525+
constructor(executor) {
526+
let resolve;
527+
let reject;
528+
super((_resolve, _reject) => {
529+
resolve = _resolve;
530+
reject = _reject;
531+
});
532+
// Setting the `status` field allows React to synchronously read
533+
// the value if the Promise is already settled by the time the Promise is
534+
// passed to `use`.
535+
executor(
536+
(value) => {
537+
this.status = "fulfilled";
538+
this.value = value;
539+
resolve(value);
540+
},
541+
(reason) => {
542+
this.status = "rejected";
543+
this.reason = reason;
544+
reject(reason);
545+
}
546+
);
547+
}
548+
}
518549

519550
function preloadUser(id) {
520551
// This is not a real implementation of getting the Promise for the user.
521552
// A real implementation would probably call `fetch` or another data fetching method.
522553
// The actual implementation should cache the Promise.
523-
const promise = Promise.resolve(`User #${id}`);
554+
// The important part is that we are using the PromiseWithStatus subclass here.
555+
// Check out the next step if you're not controlling the Promise creation
556+
// (e.g. when `fetch` is used).
557+
const promise = PromiseWithStatus.resolve(`User #${id}`);
558+
559+
return promise;
560+
}
561+
562+
function UserDetails({ userUsable }) {
563+
const user = use(userUsable);
564+
return <p>Hello, {user}!</p>;
565+
}
566+
567+
export default function App() {
568+
const [userId, setUserId] = useState(null);
569+
// The initial
570+
const [userUsable, setUser] = useState(null);
524571

525-
// Setting the `status` field allows React to synchronously read
526-
// the value if the Promise is already settled by the time the Promise is passed to `use`.
572+
return (
573+
<div>
574+
<button
575+
onClick={() => {
576+
// flushSync is only used for illustration purposes.
577+
// A real app would probably use startTransition instead.
578+
flushSync(() => {
579+
setUser(preloadUser(1));
580+
setUserId(1);
581+
});
582+
}}
583+
>
584+
Load User #1
585+
</button>
586+
<button
587+
onClick={() => {
588+
setUser(preloadUser(2));
589+
setUserId(2);
590+
}}
591+
>
592+
Load User #2
593+
</button>
594+
<Suspense key={userId} fallback={<p>Loading user...</p>}>
595+
{userUsable ? (
596+
<UserDetails userUsable={userUsable} />
597+
) : (
598+
<p>No user selected</p>
599+
)}
600+
</Suspense>
601+
</div>
602+
);
603+
}
604+
605+
```
606+
607+
</Sandpack>
608+
609+
<Solution />
610+
611+
#### Simplified implementation setting the `status` field {/*simplified-implementation-setting-the-status-field*/}
612+
613+
<Sandpack>
614+
615+
```js src/App.js active
616+
import { Suspense, use, useState } from "react";
617+
import { flushSync } from "react-dom";
618+
619+
function preloadUser(id) {
620+
const value = `User #${id}`;
621+
// This is not a real implementation of getting the Promise for the user.
622+
// A real implementation would probably call `fetch` or another data fetching method.
623+
// The actual implementation should cache the Promise.
624+
const promise = Promise.resolve(value);
625+
626+
// We don't need to create a custom subclass.
627+
// We can just set the necessary fields directly on the Promise.
527628
promise.status = "pending";
528629
promise.then(
529630
(value) => {
@@ -533,8 +634,15 @@ function preloadUser(id) {
533634
(error) => {
534635
promise.status = "rejected";
535636
promise.reason = error;
536-
},
637+
}
537638
);
639+
640+
// Setting the status in `.then` is too late if we want to create an already
641+
// settled Promise. We only included setting the fields in `.then` for
642+
// illustration purposes. Since our demo wants an already resolved Promise,
643+
// we set the necessary fields synchronously.
644+
promise.status = "fulfilled";
645+
promise.value = value;
538646
return promise;
539647
}
540648

@@ -552,16 +660,22 @@ export default function App() {
552660
<div>
553661
<button
554662
onClick={() => {
555-
setUser(preloadUser(1));
556-
setUserId(1);
663+
// flushSync is only used for illustration purposes.
664+
// A real app would probably use startTransition instead.
665+
flushSync(() => {
666+
setUser(preloadUser(1));
667+
setUserId(1);
668+
});
557669
}}
558670
>
559671
Load User #1
560672
</button>
561673
<button
562674
onClick={() => {
563-
setUser(preloadUser(2));
564-
setUserId(2);
675+
flushSync(() => {
676+
setUser(preloadUser(2));
677+
setUserId(2);
678+
});
565679
}}
566680
>
567681
Load User #2

0 commit comments

Comments
 (0)