Hydration mismatch with nested closures in <Transition> when resources resolve at different times #4430
-
|
I'm experiencing intermittent hydration errors when using with multiple resources that have different resolution times. The issue occurs when the Transition's outer closure only tracks one resource, while a nested closure accesses another resource that resolves later.
The <Transition fallback=move || view! { <p>"Loading..."</p> }>
{move || {
match fast_resource.get() {
Some(Ok(can_show)) => {
view! {
<div>
// Nested closure accessing slow_resource
{move || {
match slow_resource.get() {
None => view! { <p>"Still loading..."</p> },
Some(Ok(files)) => view! { <ul>...</ul> },
// ...
}
}}
</div>
}
}
None => view! { <div></div> }
}
}}
</Transition>How to Reproduce
The race condition doesn't always trigger - you may need to reload several times. Expected Behavior I would expect one of the following:
The workaround: access both resources in the outer closure: <Transition>
{move || {
match (fast_resource.get(), slow_resource.get()) {
(Some(Ok(can_show)), Some(Ok(files))) => {
// Both loaded, render without nested closure
}
_ => view! { <div></div> }
}
}}
</Transition>Questions
Thank you! |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
|
I don't see a race condition in the example provided -- it depends purely on whether the inner resource is already ready when the outer resource resolves, so in the example it is always broken. If I switch the timing of the two resources in the example it always works. I imagine the race condition comes in real code where you have two similarly-timed resources that finish in an arbitrary order any given time you call them. Otherwise, your analysis of the situation is correct:
This does mean that if you nest resource reads conditionally like this, it will not track the inner reads. So:
Yes and yes.
I'm sensing that you really mean "This pattern should produce a warning." In which case, a PR to add a warning or an issue adding this to the tracker would be welcome.
Yes. I can see a few ways to handle the situation you'e encountered here: Option 1: Nested As far as I can tell your example works as intended if you simply use nested Suspense to show the two different loading states. Note that you can remove the i.e., this <h4>"File List (from slow_resource):"</h4>
<Transition fallback=|| view! { <p>"Still loading files..."</p> }>
{move || {
let slow_val = slow_resource.get();Does exactly what you want, as far as I can tell. And I think this is actually better than the one-Transition approach -- if you actually want to show two separate loading placeholders, and have the fast data appear while the slow data is still loading, you need two separate Suspense/Transition components, one for each chunk. Option 2: Finer-grained resource reads Note that if you do just want one (You can catch the <Transition fallback=move || {
view! { <p>"Loading..."</p> }
}>
<div style="border: 3px solid blue; padding: 20px; margin: 20px 0;">
<h3>"Content Area (fast_resource loaded)"</h3>
<p>"fast_resource value: " {move || fast_resource.and_then(|n| n.to_string())}</p>
<div style="border: 2px solid orange; padding: 15px; margin-top: 15px;">
<h4>"File List (from slow_resource):"</h4>
<Show
when=move || matches!(&*slow_resource.read(), Some(Ok(files)) if files.is_empty())
fallback=move || view! { <p>"No files found."</p> }
>
<ul>
{move || slow_resource.and_then(|files| {
files
.iter()
.map(|file| {
let file = file.clone();
view! { <li>{file}</li> }
})
.collect_view()})}
</ul>
</Show>
</div>
</div>
</Transition> |
Beta Was this translation helpful? Give feedback.
-
|
I have made a PR (#4444) designed to address this category of Suspense issues. It does fix the issue in the example; I'd appreciate help testing it out on some larger examples as well. |
Beta Was this translation helpful? Give feedback.
I don't see a race condition in the example provided -- it depends purely on whether the inner resource is already ready when the outer resource resolves, so in the example it is always broken. If I switch the timing of the two resources in the example it always works. I imagine the race condition comes in real code where you have two similarly-timed resources that finish in an arbitrary order any given time you call them.
Otherwise, your analysis of the situation is correct:
Suspense/TransitionThis does mean that if you nest resou…