Repository: DioxusLabs/dioxus (language: rust)

Issue:
On the 0.6.0-alpha.4 version, clicking Route:: Link to route does not refresh locally, but reloads the whole page
**On the 0.6.0-alpha.4 version, clicking Route:: Link to route does not refresh locally, but reloads the whole page**


Discussion / hints:
Would you be able to test if this occurs on `0.6.0-alpha.5`? I have not noticed this issue. Additionally, would you be able to share the RSX code where you're using the `Link`?
I am currently using version `0.6.0-alpha. 5`
I use the code from the dioxus/example/route.rs
Insert `println!("Rendering navbar");` into NavBar 
When clicking on Link, the rendering will reload every time, but it works very well in version 0.4.3

Run it using the following command：
```
dx serve
```

Cargo.toml:

```
[package]
authors = ["pojoin"]
edition = "2021"
name = "test-1"
version = "0.0.1"


[dependencies]
dioxus = { version = "0.6.0-alpha.5", features=["desktop", "router"]}
```

main.rs:

```
//! An advanced usage of the router with nested routes and redirects.
//!
//! Dioxus implements an enum-based router, which allows you to define your routes in a type-safe way.
//! However, since we need to bake quite a bit of logic into the enum, we have to add some extra syntax.
//!
//! Note that you don't need to use advanced features like nest, redirect, etc, since these can all be implemented
//! manually, but they are provided as a convenience.

use dioxus::prelude::*;

const STYLE: Asset = asset!("/assets/router.css");

fn main() {
    dioxus::launch(|| {
        rsx! {
            document::Link { rel: "stylesheet", href: STYLE }
            Router::<Route> {}
        }
    });
}

// Turn off rustfmt since we're doing layouts and routes in the same enum
#[derive(Routable, Clone, Debug, PartialEq)]
#[rustfmt::skip]
enum Route {
    // Wrap Home in a Navbar Layout
    #[layout(NavBar)]
        // The default route is always "/" unless otherwise specified
        #[route("/")]
        Home {},

        // Wrap the next routes in a layout and a nest
        #[nest("/blog")]
        #[layout(Blog)]
            // At "/blog", we want to show a list of blog posts
            #[route("/")]
            BlogList {},

            // At "/blog/:name", we want to show a specific blog post, using the name slug
            #[route("/:name")]
            BlogPost { name: String },

        // We need to end the blog layout and nest
        // Note we don't need either - we could've just done `/blog/` and `/blog/:name` without nesting,
        // but it's a bit cleaner this way
        #[end_layout]
        #[end_nest]

    // And the regular page layout
    #[end_layout]

    // Add some redirects for the `/myblog` route
    #[nest("/myblog")]
        #[redirect("/", || Route::BlogList {})]
        #[redirect("/:name", |name: String| Route::BlogPost { name })]
    #[end_nest]

    // Finally, we need to handle the 404 page
    #[route("/:..route")]
    PageNotFound {
        route: Vec<String>,
    },
}

#[component]
fn NavBar() -> Element {
    println!("Rendering navbar");
    rsx! {
        nav { id: "navbar",
            Link { to: Route::Home {}, "Home" }
            Link { to: Route::BlogList {}, "Blog" }
        }
        Outlet::<Route> {}
    }
}

#[component]
fn Home() -> Element {
    rsx! { h1 { "Welcome to the Dioxus Blog!" } }
}

#[component]
fn Blog() -> Element {
    rsx! {
        h1 { "Blog" }
        Outlet::<Route> {}
    }
}

#[component]
fn BlogList() -> Element {
    rsx! {
        h2 { "Choose a post" }
        div { id: "blog-list",
            Link { to: Route::BlogPost { name: "Blog post 1".into() },
                "Read the first blog post"
            }
            Link { to: Route::BlogPost { name: "Blog post 2".into() },
                "Read the second blog post"
            }
        }
    }
}

// We can use the `name` slug to show a specific blog post
// In theory we could read from the filesystem or a database here
#[component]
fn BlogPost(name: String) -> Element {
    let contents = match name.as_str() {
        "Blog post 1" => "This is the first blog post. It's not very interesting.",
        "Blog post 2" => "This is the second blog post. It's not very interesting either.",
        _ => "This blog post doesn't exist.",
    };

    rsx! {
        h2 { "{name}" }
        p { "{contents}" }
    }
}

#[component]
fn PageNotFound(route: Vec<String>) -> Element {
    rsx! {
        h1 { "Page not found" }
        p { "We are terribly sorry, but the page you requested doesn't exist." }
        pre { color: "red", "log:\nattemped to navigate to: {route:?}" }
    }
}

```

![bug](https://github.com/user-attachments/assets/edb9b15b-e58c-4add-8d7c-d0c223edee00)


Rerendering that component isn't optimal, but it shouldn't cause behavior issues. The more problematic issue is the navbar is recreated every time the route changes. Creating the navbar every time will reset any state in the component. The log still triggers if you put it in a use hook:
```rust
#[component]
fn NavBar() -> Element {
    use_hook(|| println!("creating navbar"));
    rsx! {
        nav { id: "navbar",
            Link { to: Route::Home {}, "Home" }
            Link { to: Route::BlogList {}, "Blog" }
        }
        Outlet::<Route> {}
    }
}
```

Both of the logs go away when you run the code in release mode. It might be caused by the signal generated in the debug mode expansion of the `rsx!` macro

Fix patch:
diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs
index 060fd80dd6..bc50d9bc9c 100644
--- a/packages/core/src/nodes.rs
+++ b/packages/core/src/nodes.rs
@@ -376,19 +376,46 @@ pub struct Template {
     pub attr_paths: StaticPathArray,
 }
 
+// Are identical static items merged in the current build. Rust doesn't have a cfg(merge_statics) attribute
+// so we have to check this manually
+fn static_items_merged() -> bool {
+    fn a() {}
+    fn b() {}
+
+    a as fn() == b as fn()
+}
+
 impl std::hash::Hash for Template {
     fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        std::ptr::hash(self.roots as *const _, state);
-        std::ptr::hash(self.node_paths as *const _, state);
-        std::ptr::hash(self.attr_paths as *const _, state);
+        // If identical static items are merged, we can compare templates by pointer
+        if static_items_merged() {
+            std::ptr::hash(self.roots as *const _, state);
+            std::ptr::hash(self.node_paths as *const _, state);
+            std::ptr::hash(self.attr_paths as *const _, state);
+        }
+        // Otherwise, we hash by value
+        else {
+            self.roots.hash(state);
+            self.node_paths.hash(state);
+            self.attr_paths.hash(state);
+        }
     }
 }
 
 impl PartialEq for Template {
     fn eq(&self, other: &Self) -> bool {
-        std::ptr::eq(self.roots as *const _, other.roots as *const _)
-            && std::ptr::eq(self.node_paths as *const _, other.node_paths as *const _)
-            && std::ptr::eq(self.attr_paths as *const _, other.attr_paths as *const _)
+        // If identical static items are merged, we can compare templates by pointer
+        if static_items_merged() {
+            std::ptr::eq(self.roots as *const _, other.roots as *const _)
+                && std::ptr::eq(self.node_paths as *const _, other.node_paths as *const _)
+                && std::ptr::eq(self.attr_paths as *const _, other.attr_paths as *const _)
+        }
+        // Otherwise, we compare by value
+        else {
+            self.roots == other.roots
+                && self.node_paths == other.node_paths
+                && self.attr_paths == other.attr_paths
+        }
     }
 }
 


Test patch:
diff --git a/packages/router/tests/via_ssr/main.rs b/packages/router/tests/via_ssr/main.rs
index e187292c25..cd03f46220 100644
--- a/packages/router/tests/via_ssr/main.rs
+++ b/packages/router/tests/via_ssr/main.rs
@@ -1,4 +1,5 @@
 mod link;
+mod navigation;
 mod outlet;
 mod redirect;
 mod without_index;
diff --git a/packages/router/tests/via_ssr/navigation.rs b/packages/router/tests/via_ssr/navigation.rs
new file mode 100644
index 0000000000..1e0f4e6afd
--- /dev/null
+++ b/packages/router/tests/via_ssr/navigation.rs
@@ -0,0 +1,69 @@
+use dioxus::prelude::*;
+use dioxus_core::NoOpMutations;
+use std::sync::atomic::AtomicUsize;
+
+// Regression test for <https://github.com/DioxusLabs/dioxus/issues/3235>
+#[test]
+fn layout_retains_state_after_navigation() {
+    let mut vdom = VirtualDom::new(app);
+    vdom.rebuild_in_place();
+
+    vdom.render_immediate(&mut NoOpMutations);
+    let as_string = dioxus_ssr::render(&vdom);
+    assert_eq!(as_string, "Other");
+}
+
+fn app() -> Element {
+    rsx! {
+        Router::<Route> {}
+    }
+}
+
+// Turn off rustfmt since we're doing layouts and routes in the same enum
+#[derive(Routable, Clone, Debug, PartialEq)]
+#[rustfmt::skip]
+enum Route {
+    // Wrap Home in a Navbar Layout
+    #[layout(NavBar)]
+        // The default route is always "/" unless otherwise specified
+        #[route("/")]
+        Home {},
+            
+        #[route("/other")]
+        Other {},
+}
+
+#[component]
+fn NavBar() -> Element {
+    static NAVBARS_CREATED: AtomicUsize = AtomicUsize::new(0);
+    use_hook(|| {
+        let navbars_created = NAVBARS_CREATED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
+        println!("creating navbar #{navbars_created}");
+        if navbars_created > 0 {
+            panic!("layouts should not be recreated when switching between two routes under the nav bar");
+        }
+    });
+
+    // Queue an effect to navigate to the other route after rebuild_in_place
+    use_effect(|| {
+        router().push(Route::Other {});
+    });
+
+    rsx! {
+        Outlet::<Route> {}
+    }
+}
+
+#[component]
+fn Home() -> Element {
+    rsx! {
+        "Home!"
+    }
+}
+
+#[component]
+fn Other() -> Element {
+    rsx! {
+        "Other"
+    }
+}