fix: <Suspense/> hydration when no resources are read under it (#1046)

This commit is contained in:
Greg Johnston 2023-05-16 12:20:23 -04:00 committed by GitHub
parent f0a9940364
commit 85ad7b0f38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 41 additions and 4 deletions

View file

@ -101,13 +101,17 @@ where
use leptos_reactive::signal_prelude::*;
// run the child; we'll probably throw this away, but it will register resource reads
let child = orig_child(cx).into_view(cx);
let _child = orig_child(cx).into_view(cx);
let after_original_child = HydrationCtx::id();
let initial = {
// no resources were read under this, so just return the child
if context.pending_resources.get() == 0 {
child
let orig_child = Rc::clone(&orig_child);
HydrationCtx::continue_from(current_id.clone());
Fragment::lazy(Box::new(move || {
vec![DynChild::new(move || orig_child(cx)).into_view(cx)]
})).into_view(cx)
}
// show the fallback, but also prepare to stream HTML
else {

View file

@ -17,7 +17,7 @@ leptos_actix = { path = "../../../../integrations/actix", optional = true }
leptos_router = { path = "../../../../router", default-features = false }
log = "0.4"
simple_logger = "4"
wasm-bindgen = "0.2"
wasm-bindgen = "0.2.85"
serde = "1.0.159"
tokio = { version = "1.27.0", features = ["time"], optional = true }

View file

@ -56,6 +56,7 @@ pub fn App(cx: Scope) -> impl IntoView {
<Route path="single" view=|cx| view! { cx, <Single/> }/>
<Route path="parallel" view=|cx| view! { cx, <Parallel/> }/>
<Route path="inside-component" view=|cx| view! { cx, <InsideComponent/> }/>
<Route path="none" view=|cx| view! { cx, <None/> }/>
</Route>
// in-order
<Route
@ -71,6 +72,7 @@ pub fn App(cx: Scope) -> impl IntoView {
<Route path="single" view=|cx| view! { cx, <Single/> }/>
<Route path="parallel" view=|cx| view! { cx, <Parallel/> }/>
<Route path="inside-component" view=|cx| view! { cx, <InsideComponent/> }/>
<Route path="none" view=|cx| view! { cx, <None/> }/>
</Route>
// async
<Route
@ -86,6 +88,7 @@ pub fn App(cx: Scope) -> impl IntoView {
<Route path="single" view=|cx| view! { cx, <Single/> }/>
<Route path="parallel" view=|cx| view! { cx, <Parallel/> }/>
<Route path="inside-component" view=|cx| view! { cx, <InsideComponent/> }/>
<Route path="none" view=|cx| view! { cx, <None/> }/>
</Route>
</Routes>
</main>
@ -101,6 +104,7 @@ fn SecondaryNav(cx: Scope) -> impl IntoView {
<A href="single">"Single"</A>
<A href="parallel">"Parallel"</A>
<A href="inside-component">"Inside Component"</A>
<A href="none">"No Resources"</A>
</nav>
}
}
@ -217,3 +221,25 @@ fn InsideComponentChild(cx: Scope) -> impl IntoView {
</Suspense>
}
}
#[component]
fn None(cx: Scope) -> impl IntoView {
let (count, set_count) = create_signal(cx, 0);
view! { cx,
<div>
<Suspense fallback=|| "Loading 1...">
<div>"Children inside Suspense should hydrate properly."</div>
<button on:click=move |_| set_count.update(|n| *n += 1)>
{count}
</button>
</Suspense>
<p>"Children following " <code>"<Suspense/>"</code> " should hydrate properly."</p>
<div>
<button on:click=move |_| set_count.update(|n| *n += 1)>
{count}
</button>
</div>
</div>
}
}

View file

@ -240,13 +240,20 @@ impl View {
dont_escape_text: bool,
) {
match self {
View::Suspense(id, _) => {
View::Suspense(id, view) => {
let id = id.to_string();
if let Some(data) = cx.take_pending_fragment(&id) {
chunks.push_back(StreamChunk::Async {
chunks: data.in_order,
should_block: data.should_block,
});
} else {
// if not registered, means it was already resolved
View::CoreComponent(view).into_stream_chunks_helper(
cx,
chunks,
dont_escape_text,
);
}
}
View::Text(node) => {