-
Notifications
You must be signed in to change notification settings - Fork 559
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
RFC: Suspense in React 18 #213
Conversation
One issue that I saw with Next.js is that some folks are already using Suspense on the client, and such APIs might not work well with server rendering without additional changes. Would it make sense to officially recommend something like having existing client-only suspense APIs start throwing an error when rendered on the server so that they fallback to client-rendering, until the API can be updated to properly support server-side Suspense? |
Co-authored-by: Sebastian Silbermann <silbermann.sebastian@gmail.com>
Co-authored-by: Sebastian Silbermann <silbermann.sebastian@gmail.com>
Re: #213 (comment)
Is it the case where people do conditional rendering like If it's the scenario I'm thinking of, the fix is usually to do rendering in two passes: const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
<div>
{mounted && <Suspense>...</Suspense>}
</div> It's not great but would have the same behavior.
I'm not sure I understand what you're proposing. Is it to make |
Co-authored-by: Steven Liao <steven.liaouey@gmail.com>
@gaearon The case is not something that happens by just upgrading to newer React. It's when you first upgrade to newer React and then later start using existing Suspense-y APIs on the server and So there's no conditional Suspense. IMO, it's correct that an API that tries to be suspensey but doesn't have a way to transfer data to the client should throw an error if used on the server so that it deopts to client rendering and you get the correct client side warning. Typically such an API has some way to provide initial data from getServerSideProps/getInitialProps. So it can throw only if that wasn't provided |
Do you mean third-party libs that throw Promises today? |
OK yeah got it. I thought this was about using |
text/0000-suspense-in-react-18.md
Outdated
|
||
The reason we didn't go with this approach in React 16.6 was for backwards compatibility reasons. At the time, most React code used classes, and many classes contained the `componentWillMount` method. This is why we renamed it to `UNSAFE_componentWillMount` in 2018 and [described](https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html) strategies ot migrate away from it. The problem with `UNSAFE_componentWillMount` is that it fires during rendering (so, before we know whether child components suspended or not). If we throw away an incomplete tree after `UNSAFE_componentWillMount` has already fired, it will not receive a matching `componentDidMount` or `componentWillUnmount` call. (During a retry, there will be another `UNSAFE_componentWillMount` call because we need to render the same tree again.) So code that relies on `UNSAFE_componentWillMount` and `componentWillUnmount` being called the same number of times might cause mistakes or memory leaks. | ||
|
||
We don't think this concern is relevant anymore for several reasons. `UNSAFE_componentWillMount` was marked as "unsafe" in 2018, and most popular open source libraries have long migrated away from it. This issue also only affects components "between" the `<Suspense>` node and the component that actually suspends. So it is very local in scope, and is easy to fix. Finally, Hooks have become a popular alternative to classes, and don't have the same issue. On the other hand, the current behavior [has been causing issues](https://github.com/facebook/react/issues/14536) for using popular component libraries with Suspense. This is why we think now is a good idea to make that change. |
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.
Is there a timeline for when these UNSAFE_
methods would go away? This seems to be the first concrete reason to move away from these APIs.
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.
FYI there’s a typo on line 123, after the link for [described]:
ot
should be to
strategies ot migrate -> strategies to migrate
Love all this btw!
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.
For what it’s worth, componentWillMount potentially firing multiple times was the original reason that it was renamed to UNSAFE in 2018. So I wouldn’t quite say that it’s “the first concrete reason” now. It’s what the reason has been along from the beginning. We didn’t anticipate that the whole journey to actually taking advantage of this would take so long, but this reason was exactly why we suggested to move away from it back then.
There is currently no plan to “remove” the UNSAFE methods. They’ll keep firing. It’s just that if you use Suspense, UNSAFE_componentWillMount may potentially execute multiple times without corresponding DidMount/WillUnmount if it’s in a component between Suspense and the part that suspends. Since many old componentWillMount code is not resilient to that, “UNSAFE” helps highlight that this code can be a problem.
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.
Gotcha, this to me just feels like the first step towards a world without these UNSAFE methods being available at all (with the language of its been 4 years and libraries have moved away etc) so I was curious if there was a timeline for that.
For context at my company we have > 500 instances of just UNSAFE_componentWillMount
and are currently undergoing a migration to move away from all UNSAFE methods. But overall its always easier to prioritize such work when they are being removed and a hard blocker for the upgrade vs potentially subtle bugs / opt into R17 behavior.
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 see. Our internal policy was to forbid them in any new code (or any code imported into new code) by using Strict Mode which complains about them. And then for old code (of which there is plenty), the policy is to exercise caution before using new features like Suspense. So it could still be added there but there are possible new memory leaks etc.
Are the layout effects to re-run when content reappears effectively only those defined using |
Re: #213 (comment)
Yes, as well as ref callbacks.
Edit: I discussed with the team a bit. |
This sounds trivial, but: What are the current scenarios that allow a component to suspend in general? The whole RFC gives explanations for what happens when a component suspends, but how do I, as a developer, enable a component to suspend? With that I mean I saw "The exact protocol a component should use to tell React that it's suspended." at the "Unresolved questions" - maybe I'm lost in translation, but I read this as "How a component lets React know while it is suspending" (=> "loading state"). |
Re: #213 (comment)
Indeed, that's the "unresolved question" you're mentioning. The pragmatic answer is that today it works by throwing a Promise. However that's not an "official" or "final" API, and it's also not a complete description of the contract. You can't just throw arbitrary promises and expect it to work. Rather, it has to be backed by some cache outside of your components (rather than create during rendering), you need to throw the same Promise while it's pending (rather than creating a new one every time), you have to store its result outside of your components, you can't mutate the received data, and so on. These requirements are somewhat difficult to satisfy on your own, which is why we don't consider Suspense for data fetching officially supported yet. reactwg/react-18#25 is part of the work to make this simpler. |
I have a question, with this new behavior, if <Panel>
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
</Panel> Because with the previous behavior However, I am more in favor of the new approach because, in addition to its simplicity and all the reasons you have mentioned, I think it also simplifies react-devtools code by removing many of the Suspense edge cases, thanks to the fact that Suspense now behaves the same as regular fibers from the devtools perspective, right? |
Re: #213 (comment)
Re-rendering always starts from the
I'm not familiar enough with React DevTools internals to answer that. |
I recently had proplems with Suspense during hydration. I had a text mismatch warning and then that immediatelly failed the Suspense root and switched to client side rendering. If it's "just" a warning, I wouldn't expect the whole thing to break. If it's an error, why is it only a warning? :) I tested with |
Re: #213 (comment) Yes, we’re treating hydration mismatches as errors now. They cause a fully client render at the granularity of the closest Suspense boundary above the mismatch. I will post more details about this later today. The correct fix is usually to fix the mismatch but you’re right that for things like timestamps, the “suppress warning” prop now actually has production behavior too. This is unfortunate and we might rename it in a future release. |
We've effectively introduced a new form of Error that's kind of between a Warning and an Error... "Recoverable Error". It just means it can recover by client rendering so the user won't see it but it's an error so worth fixing. |
More on that: #215 |
* RFC: Intent to ship React 18 This RFC describes the changes we intend to ship in React 18. * Add link to RFC #213 * Move the note up * Add RFC link * Update text/0000-react-18.md Co-authored-by: Sebastian Silbermann <silbermann.sebastian@gmail.com> * Add useDeferredValue * Update and rename 0000-react-18.md to 0212-react-18.md Co-authored-by: dan <dan.abramov@gmail.com> Co-authored-by: Sebastian Silbermann <silbermann.sebastian@gmail.com>
In this RFC, we describe a few changes to Suspense that we intend to ship in React 18.
View the formatted RFC.