New use() docs for Promise subclasses
#3
rickhanlonii
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Overview
tl;dr: There's a new docs PR for how to create promises for
use(): reactjs/react.dev#8125When implementing suspense enabled data fetching, there's a few subtle details that haven't been documented yet around when and how to create the promises you suspend on. We're fixing that with a docs PR here: reactjs/react.dev#8125.
In this post, I want to provide some more context and open it up to any questions that might not be clear in the docs. Notes that I'm not covering how to cache the promises, but how and when to create the promise that you pass to
use()in your data fetching layer.Conditional suspending not recommended
First, you shouldn't conditionally call
usebased on whether you have the data or not:There are a number of reasons for this (taken from @sebmarkbage in facebook/react#34030):
use()keeps track of the order it was last called similar to other hooks. When a Promise is resolved without any updates, we can continue where we left off by rerendering the component and using the resolved value in eachuse()slot. If the second render picks a different path, even though no state or props have changed, then theuse()will line up in the wrong order and you can get data corruption by lateruse()observing the value of previoususe().setStateoruseSyncExternalStoreto cause a component to take a different path to unblock a Promise then you'll also cause other optimizations to deopt. For example, it'll force a dehydrated Suspense boundary to force hydration which can cause it to switch to client rendering or flip back to a loading state after having already shown the content. That's because React can tell that if you resolved a Promise then you're still in "hydration mode" given the original data. If you do a setState then React may need to throw away the SSR:ed state. At best you'll do multiple unnecessary rerender of the whole tree from the parent to the suspended point instead of using resuming where it left off.use(promise)after it resolves, then React DevTools can't track that as a component that might suspend. Because it can't differentiate between asetStatethat was a navigation to a different page and the new page isn't suspended by this value, vs. asetStatethat was used only to unblock this value.In the future we plan to warn for this to help catch these issues.
Note: Conditional
use(promise)is still fine in user codeNote that this doesn't mean that you "can't conditionally call
use(promise)" in user code.For example, this is fine:
The difference here is that in user code, the
condhere would be a state update, which is fine. The issue is specifically related to conditionally reading from a cache.Subclassing Promises
Because of the above, you will need a way to
use(promise)in a way that makes the promise data synchronously available if the promise has already resolved (such as for preloading), or the data is already available (such as in a cache).Common use cases for this are:
To support these cases, React already instruments fields on the promise so
use()knows the data is already resolved. So if you continue tousethe same promise, it just works.However, anytime you create a promise as a wrapper for some kind of cached data that is already available, that new promise will not have a chance for React to instrument it. Without this instrumentation,
use()will Suspend to at least the next tick so the thenable can instrument the values.In the past, we tried to just wait a tick before showing the fallback to see if the promise data is available, but this ends up delaying the fallback in the cases it's not available and has a bunch of other tricky issues.
Instead, we recommend subclassing the promise.
There are a few ways you can do this, by actually subclassing it (which feels more platformy):
Or by setting the fields:
Or you could create a thenable-like object:
The later is slightly faster, but I don't really want to get in debates about whether it's good or not - you can choose your level of strict spec adherence.
Docs
This is all just context for the docs @eps1lon @Ephem and @sebmarkbage have been iterating on in reactjs/react.dev#8125.
If you have a chance, please check out the new docs and feel free to ask questions here or on the PR.
Beta Was this translation helpful? Give feedback.
All reactions