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

Possible bug of Transition in sub view component in ssr/hydrate mode. The transition will always show fallback on all update #2279

Closed
shenjiangqiu opened this issue Feb 8, 2024 · 4 comments

Comments

@shenjiangqiu
Copy link

Describe the bug
here is a minimal reproduce code:

https://github.com/shenjiangqiu/leptos_bug

When the Transition in top level component, it will work correctly.
When the view is rendered conditionally, the Transition will always show fallback for every update.

For example: the first Transition works file, but the second not.

use std::time::Duration;

use leptos::*;

#[component]
pub fn App() -> impl IntoView {
    let (app_stat, set_app_stat) = create_signal(0);
    // the top level resource works as expected
    let top_level_resource = create_resource(
        move || app_stat.get(),
        move |v| async move {
            #[cfg(feature = "ssr")]
            {
                tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
            }
            #[cfg(feature = "hydrate")]
            {
                gloo_timers::future::sleep(Duration::from_secs(2)).await;
            }
            v
        },
    );

    let (show_sub_page, set_show_sub_page) = create_signal(false);

    view! {
        <div>
            <h1>"Hello, World!"</h1>
            <Transition
                fallback=move || view! { <p>"Loading..."</p> }
            >
                <div>
                    {
                        move || {
                            top_level_resource.get().map(|v| {
                                view! {
                                    <p>"this works fine! there will be no fallback after initial load"{v}</p>
                                    <button
                                        on:click=move |_| {
                                            set_app_stat.update(|v| *v += 1);
                                        }
                                    >
                                        "Increment"
                                    </button>
                                }

                            })
                        }
                    }
                </div>
            </Transition>
        </div>
        <div>
                    <button
                        on:click=move |_| {
                            set_show_sub_page.update(|v| *v= !*v);
                        }
                    >
                        "Toggle Sub Page"
                    </button>
                    <div>
                        {
                            move || {
                                if show_sub_page.get() {
                                    view! {
                                        <SubPage />
                                    }.into_view()
                                } else {
                                    view! {
                                        <p>"Sub Page is not shown"</p>
                                    }.into_view()
                                }
                            }
                        }
                    </div>
        </div>
    }
}

#[component]
fn SubPage() -> impl IntoView {
    let (app_stat, set_app_stat) = create_signal(0);
    // the top level resource works as expected
    let sub_page_resource = create_resource(
        move || app_stat.get(),
        move |v| async move {
            #[cfg(feature = "ssr")]
            {
                tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
            }
            #[cfg(feature = "hydrate")]
            {
                gloo_timers::future::sleep(Duration::from_secs(2)).await;
            }
            v
        },
    );
    view! {
        <div>
            <h1>"subpage!"</h1>
            <Transition
                fallback=move || view! { <p>"Loading..."</p> }
            >
                <div>
                    {
                        move || {
                            sub_page_resource.get().map(|v| {
                                view! {
                                    <p>"bug here!, every time updated, it will show loading!"{v}</p>
                                    <button
                                        on:click=move |_| {
                                            set_app_stat.update(|v| *v += 1);
                                        }
                                    >
                                        "Increment"
                                    </button>
                                }

                            })
                        }
                    }
                </div>
            </Transition>
        </div>
    }
}

Leptos Dependencies

[dependencies]
console_log = { version = "1.0", optional = true }
rand = { version = "0.8", features = ["min_const_gen"], optional = true }
console_error_panic_hook = { version = "0.1", optional = true }
futures = "0.3"
leptos = { version = "0.6", features = ["nightly"] }
leptos_meta = { version = "0.6", features = ["nightly"] }
leptos_axum = { version = "0.6", optional = true }
leptos_router = { version = "0.6", features = ["nightly"] }
serde = { version = "1.0", features = ["derive"] }
axum = { version = "0.7", optional = true, features = ["macros"] }
tower = { version = "0.4", optional = true }
tower-http = { version = "0.5", features = ["fs"], optional = true }
tokio = { version = "1", features = ["full"], optional = true }
http = { version = "1.0" }
sqlx = { version = "0.7.2", features = [
    "runtime-tokio-rustls",
    "sqlite",
], optional = true }
thiserror = "1.0"
wasm-bindgen = "0.2"
axum_session_auth = { version = "0.12.0", features = [
    "sqlite-rustls",
], optional = true }
axum_session = { version = "0.12.4", features = [
    "sqlite-rustls",
], optional = true }
bcrypt = { version = "0.15", optional = true }
async-trait = { version = "0.1", optional = true }

sea-orm = { version = "0.12.14", features = ["sqlx-sqlite"], optional = true }
dotenv = { version = "0.15.0", optional = true }
http-body = "1.0.0"
bytes = "1.5.0"
pin-project-lite = { version = "0.2.13", optional = true }
clap = { version = "4.4.18", features = ["derive"], optional = true }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = [
    "fmt",
    "env-filter",
], optional = true }
regex = "1.10.3"
eyre = "0.6.12"
anyhow = "1.0.79"
gloo-timers = { version = "0.3.0", features = ["futures"], optional = true }

To Reproduce
in my repo, run cargo leptos watch

Expected behavior
the second Transition shouldn't show fall back after the initial run.

Screenshots
image

@gbj
Copy link
Collaborator

gbj commented Feb 8, 2024

Thanks for the report. It looks like it works if the subpage is shown unconditionally, i.e., if the second Transition is actually shown on the page when it hydrates.

I'm sure this is just a bug in the Transition logic for hydration, which is very finicky.

@zoomiti
Copy link
Contributor

zoomiti commented Feb 8, 2024

In my own investigations this seems likely. Looking at the debug output the second transition is rendered just as a suspense

unconditional transition

<!-- <Suspense> -->
<!--suspense-open-0-1-0-0-->
<!-- <DynChild> -->
<!--leptos-view|<Transition/>-children|open-->
<!-- <> -->
<div data-hk="0-1-0-3">
    <!-- <DynChild> -->
    <!--leptos-view|src-app.rs-36|open-->
    <!-- <> --><p data-hk="0-1-0-6">this works fine! there will be no fallback after initial load0</p>
    <button data-hk="0-1-0-7">Increment</button>
    <!-- </> -->
    <!--leptos-view|src-app.rs-36|close-->
    <!-- </DynChild> -->
</div>
<!-- </> -->
<!--leptos-view|<Transition/>-children|close-->
<!-- </DynChild> -->
<!--suspense-close-0-1-0-0-->
<!-- </Suspense> -->

vs conditional

<!-- <Suspense> -->
<!-- <DynChild> -->
<!-- <> -->
<div>
    <!-- <DynChild> -->
    <!-- <> -->
    <p>bug here!, every time updated, it will show loading!0</p>
    <button>Increment</button>
    <!-- </> -->
    <!-- </DynChild> -->
</div>
<!-- </> -->
<!-- </DynChild> -->
<!-- </Suspense> -->

@gbj
Copy link
Collaborator

gbj commented Feb 16, 2024

Thanks, I've had some time to look at this and hopefully the attached fix is pretty straightforward. Please let me know if PR #2314 fixes the issue for you.

@shenjiangqiu
Copy link
Author

Yes! I tested it and it works. thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants