From b252528722bdb53a187a8b4f7e33451bc3b312af Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 7 Jul 2025 13:43:32 -0500 Subject: [PATCH] Fix router state after client side navigation (#4383) * Fix router state after client side navigation * add client side navigation to the fullstack routing spec * fix playwright test --- packages/fullstack-hooks/src/history.rs | 8 ++--- packages/fullstack-protocol/src/lib.rs | 13 ++++++++ .../fullstack-routing.spec.js | 30 +++++++++++++++++++ .../fullstack-routing/src/main.rs | 7 +++++ 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/packages/fullstack-hooks/src/history.rs b/packages/fullstack-hooks/src/history.rs index 1feb17416..1eac5a62d 100644 --- a/packages/fullstack-hooks/src/history.rs +++ b/packages/fullstack-hooks/src/history.rs @@ -2,10 +2,8 @@ use std::cell::OnceCell; -use dioxus_core::{ - prelude::{generation, queue_effect}, - schedule_update, -}; +use dioxus_core::{prelude::queue_effect, schedule_update}; +use dioxus_fullstack_protocol::is_hydrating; use dioxus_history::History; // If we are currently in a scope and this is the first run then queue a rerender @@ -14,7 +12,7 @@ fn match_hydration( during_hydration: impl FnOnce() -> O, after_hydration: impl FnOnce() -> O, ) -> O { - if generation() == 0 { + if is_hydrating() { let update = schedule_update(); queue_effect(move || update()); during_hydration() diff --git a/packages/fullstack-protocol/src/lib.rs b/packages/fullstack-protocol/src/lib.rs index 4d170ea7c..9294957d4 100644 --- a/packages/fullstack-protocol/src/lib.rs +++ b/packages/fullstack-protocol/src/lib.rs @@ -144,6 +144,19 @@ impl SerializeContextEntry { } } +/// Check if the client is currently rendering a component for hydration. Always returns true on the server. +pub fn is_hydrating() -> bool { + #[cfg(feature = "web")] + { + // On the client, we can check if the context is set + CONTEXT.with(|context| context.borrow().is_some()) + } + #[cfg(not(feature = "web"))] + { + true + } +} + /// Get or insert the current serialize context. On the client, the hydration context this returns /// will always return `TakeDataError::DataNotAvailable` if hydration of the current chunk is finished. pub fn serialize_context() -> HydrationContext { diff --git a/packages/playwright-tests/fullstack-routing.spec.js b/packages/playwright-tests/fullstack-routing.spec.js index 3085bd557..5ae3043f1 100644 --- a/packages/playwright-tests/fullstack-routing.spec.js +++ b/packages/playwright-tests/fullstack-routing.spec.js @@ -51,3 +51,33 @@ test("unknown route", async ({ request }) => { expect(response.status()).toBe(404); }); + +// Clicking the link on the home page should navigate to the blog route +test("click blog link", async ({ page }) => { + await page.goto("http://localhost:8888"); + + // Click the link to the blog route + await page.locator('a').click(); + + // Wait for navigation to complete + await page.waitForURL("http://localhost:8888/blog/1/"); + + // Check that the blog page is displayed + const text = await page.textContent("body"); + expect(text).toContain("id: 1"); +}); + +// Clicking the link on the blog page should navigate back to the home route +test("click home link from blog", async ({ page }) => { + await page.goto("http://localhost:8888/blog/1"); + + // Click the link to the home route + await page.locator('a').click(); + + // Wait for navigation to complete + await page.waitForURL("http://localhost:8888"); + + // Check that the home page is displayed + const text = await page.textContent("body"); + expect(text).toContain("Home"); +}); diff --git a/packages/playwright-tests/fullstack-routing/src/main.rs b/packages/playwright-tests/fullstack-routing/src/main.rs index c0bba2175..12ae64068 100644 --- a/packages/playwright-tests/fullstack-routing/src/main.rs +++ b/packages/playwright-tests/fullstack-routing/src/main.rs @@ -36,6 +36,9 @@ enum Route { #[component] fn Blog(id: i32) -> Element { + let route: Route = use_route(); + assert_eq!(route, Route::Blog { id }); + rsx! { Link { to: Route::Home {}, "Go home" } "id: {id}" @@ -51,8 +54,12 @@ fn ThrowsError() -> Element { #[component] fn Home() -> Element { + let route: Route = use_route(); + assert_eq!(route, Route::Home); + rsx! { "Home" + Link { to: Route::Blog { id: 1 }, "Go to blog 1" } } }