Skip to content
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

Nested Suspense ignores SSR Mode and breaks Hydration #2561

Open
luxalpa opened this issue Apr 25, 2024 · 2 comments
Open

Nested Suspense ignores SSR Mode and breaks Hydration #2561

luxalpa opened this issue Apr 25, 2024 · 2 comments
Labels
bug Something isn't working

Comments

@luxalpa
Copy link
Contributor

luxalpa commented Apr 25, 2024

Describe the bug
When using a Suspense within another Suspense with PartiallyBlocked SSR mode and two blocking resources, it will only block for the outer one, but not wait for the inner one. Furthermore, a component (possibly others) that renders after the Suspense won't hydrate properly and instead create <DynChild> and <> comment nodes.

Leptos Dependencies

Please copy and paste the Leptos dependencies and features from your Cargo.toml.

For example:

leptos = { version = "0.6.11", features = ["nightly"] }
leptos_meta = { version = "0.6.11", features = ["nightly"] }
leptos_actix = { version = "0.6.11", optional = true }
leptos_router = { version = "0.6.11", features = ["nightly"] }
leptos_dom = "0.6.11"
leptos_reactive = { version = "0.6.11" }
leptos_macro = { version = "0.6.11" }
serde = "1.0.198"

To Reproduce

use leptos::*;
use leptos_meta::*;
use leptos_router::{Route, Router, Routes, SsrMode};
use std::time::Duration;

#[component]
pub fn App() -> impl IntoView {
    provide_meta_context();

    view! {
        <Router>
            <div>
                "Hello World"
                    <Routes>
                        <Route path="/" view=|| {
                            view! {<FrontPage />}
                        } ssr=SsrMode::PartiallyBlocked />
                    </Routes>
            </div>
            <For each=move || vec![1, 2, 3] key=|k| *k children=|i| {
                view! {
                    <div>
                        {i}
                    </div>
                }
            } />
        </Router>
    }
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct UserData {
    name: String,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct SiteData {
    contents: String,
}

#[server]
pub async fn get_user_data() -> Result<UserData, ServerFnError> {
    return Ok(UserData {
        name: "Luxalpa".to_string(),
    });
}

#[server]
pub async fn get_site_data() -> Result<SiteData, ServerFnError> {
    use actix_web::HttpRequest;
    use leptos_actix::extract;

    let req: HttpRequest = extract().await.unwrap();
    println!("Method: {:?}", req.method());

    return Ok(SiteData {
        contents: "Welcome to Leptos".to_string(),
    });
}

#[component]
pub fn FrontPage() -> impl IntoView {
    let res = create_blocking_resource(|| (), |_| async move { get_user_data().await.unwrap() });

    let contents = move || {
        res.with(|data| {
            let Some(data) = data.as_ref() else {
                return ().into_view();
            };

            let res2 =
                create_blocking_resource(|| (), |_| async move { get_site_data().await.unwrap() });

            // spawn_local(async move {
            //     let site_data = get_site_data().await.unwrap();  // <- this code is also broken, but I'll probably file a separate issue
            //     println!("Site Data: {:?}", site_data);
            // });

            let name = data.name.clone();

            (view! {
                <div>
                    <Suspense>
                        {move || res2.get().map(|data| data.contents)}
                    </Suspense>
                    {name}
                </div>
            })
            .into_view()
        })
    };

    view! {
        <div>
            <Suspense>
                {contents}
            </Suspense>
        </div>
    }
}

Expected behavior
Render the entire HTML first instead of rendering it later. Properly hydrate the created components

Additional context
My outer suspense / resource is loading UserData depending on whether the user is logged in or not. Then the inner suspense / resource is loading some application data that is only allowed to be loaded if the user is logged in. Unfortunately it seems that I cannot avoid nesting Suspense's here, as I can't load all possible userdata on the outer context, and if I load only the data necessary for the current page, then it won't work if the user starts on a different page but then navigates to this one. I have a large app and several pages have this issue, so any hacky workaround will also involve tons of work.

@gbj
Copy link
Collaborator

gbj commented Apr 25, 2024

Hm. This one does seem to be fixed by #2284, which I closed because I thought it broke other things. It may be worth testing your full example against that branch to see if it does break other things. I can revisit it.

The current Suspense system is very fragile and is one of the things that is definitely being improved in 0.7 wheni it comes. Sorry for the pain for now.

@luxalpa
Copy link
Contributor Author

luxalpa commented Apr 25, 2024

Yes, the change does resolve this issue. I'll see if it breaks any further things but it does look pretty good right now. Definitely excited for 0.7 but also a bit scared of all the things that it might be breaking :X

@gbj gbj added the bug Something isn't working label Apr 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants