-
Notifications
You must be signed in to change notification settings - Fork 46.4k
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
Bug: Nested SuspenseList may display fallbacks while the component is loaded on re-render #18661
Comments
Haven’t looked at the issue yet (and might not for a bit). But we need a lot stronger fuzz coverage for Suspense, SuspenseList, and priority scheduling. See the recent closed bugs with “concurrent mode” label for examples of how things go wrong and what catches them. If you’d like to contribute, coming up with a stronger fuzz and submitting failing tests would be much appreciated. |
For SuspenseList specifically I don’t remember what the semantics for updating are. There was something subtle there. So maybe this isn’t a bug per se. |
I agree with you this looks weird though. Can you submit an isolated failing case PR for now that’s manually written? And then add fuzz separately. |
I also opened #18669 for the broader fuzz question. |
Yes, I will open a specific PR only containing a failing unit test related to this potential bug.
And then open PRs to discuss fuzzing on SuspenseList. And link them to #18669. |
…nt is loaded on re-render Issue facebook#18661
Updates are indeed strange. Nesting is also strange. What happens here is that we have two rules that can yield counter intuitive results:
What's happening here is that the inner Suspense list is also a row. We follow the rule: "A row is not allowed to show partial content until the whole row is done." It gets extra confusing because A is still showing due to the rule "Never hide already shown content until it explicitly resuspends, to avoid content disappearing". So this behavior is "intentional" according to the rules. We might have the wrong heuristics but a real world use case would be helpful to inform how they should change. When you have two Suspense boundaries in a row that aren't wrapped in a SuspenseList there has to be kind of an implicit list there to decide how they should work with regard to each other. You wouldn't want the last one to pop in before the first one. So a good default is "A row is not allowed to show partial content until the whole row is done". It is as if the list was wrapped in SuspenseList revealOrder="together". However, I could imagine a rule like if all boundaries are in a single SuspenseList in a row, then that list decides. |
@sebmarkbage
There is still something I don't fully get - Why is the behaviour different if I directly render: render(
<SuspenseList key="1" revealOrder="forwards">
<SuspenseList key="1.1" revealOrder="forwards">
<Suspense key="1.1.a" fallback={<div>Loading A</div>}>
<A />
</Suspense>
<Suspense key="1.1.b" fallback={<div>Loading B</div>}>
<B />
</Suspense>
<Suspense key="1.1.c" fallback={<div>Loading C</div>}>
<C />
</Suspense>
</SuspenseList>
</SuspenseList>
) Case of the issue: https://codesandbox.io/s/mutable-rain-3ikor?file=/src/App.js (click on update) I mean according to your explanation I'd have expected to see (What is a |
I think a "row" is an immediate child of SuspenseList? |
@dubzzz In your example I see That's because we are not allowed to turn A back into "Loading A" because: We're not allowed to show "B" because: The solution btw is to wrap it in another Suspense boundary. Because that kind of "resets".
|
@sebmarkbage Thanks a lot for this very detailed explanation. I get a better idea of how updates are supposed to be working. Maybe one last question: Do these rules only apply on updates? (not first render) Indeed, they seem not to apply on first render. See the following example not behaving the same way?
In this second example I directly render: (with A, B already resolved BUT C still pending) render(
<SuspenseList key="1" revealOrder="forwards">
<SuspenseList key="1.1" revealOrder="forwards">
<Suspense key="1.1.a" fallback={<div>Loading A</div>}>
<A />
</Suspense>
<Suspense key="1.1.b" fallback={<div>Loading B</div>}>
<B />
</Suspense>
<Suspense key="1.1.c" fallback={<div>Loading C</div>}>
<C />
</Suspense>
</SuspenseList>
</SuspenseList>
) So given your explanation I expect to have Indeed neither A, nor B have already been displayed so "Never hide already shown content until it explicitly resuspends, to avoid content disappearing." does not apply (first render). Additionally as "A row is not allowed to show partial content until the whole row is done.", nothing from the row should be shown as C is still loading. As a consequence of those two rules I expect to have |
Yea that one is kind of surprising to me too. Regardless I came across a case where we might want to change the rules like I mentioned above. If you have a row that you want to consistently show in its loading state while popping in additional items one by one before it, then you need an inner list with tail=collapsed/hidden and an outer one which is not collapsed. But we force the last item not to show its fallback. However if you do that then the inner one isn’t allowed to partially suspend. So regardless I think we need to change this. |
What is the latest version of concurrent mode? |
React version: 0.0.0-experimental-e5d06e34b
Hoping this can be helpful for you. Here is what looks to be a bug with concurrent mode and nested SuspenseList.
Steps To Reproduce
In concurrent mode only
Let's suppose we have three components
<A />
(not lazy loaded),<B />
(not lazy loaded) and<C />
(lazy loaded).In other words:
A / Loading B / Loading C
. WhileB
has already been loaded (not lazy loaded). If I understand well the behaviour offorwards
I would have expect to haveA / B / Loading C
instead.Please note that the behaviour is not the same if I do not use nested
<SuspenseList />
.CodeSandbox: https://codesandbox.io/s/mutable-rain-3ikor
GitHub pages repro: https://dubzzz.github.io/react-suspenselist-bug/build/
GitHub pages source code: https://github.com/dubzzz/react-suspenselist-bug
The current behavior
Output is:
A / Loading B / Loading C
with nestedSuspenseList
The expected behavior
Output is:
A / B / Loading C
with nestedSuspenseList
How did I found this bug?
This potential bug has been discovered while I was trying to run property based tests based on fast-check against React library.
See dubzzz@e2cb477
The text was updated successfully, but these errors were encountered: