浏览代码

update doc examples

Evan Almloff 2 年之前
父节点
当前提交
09cabe4e8b

+ 14 - 2
packages/router-macro/src/lib.rs

@@ -53,8 +53,20 @@ pub fn routable(input: TokenStream) -> TokenStream {
             dioxus_router::prelude::GenericLink(cx)
             dioxus_router::prelude::GenericLink(cx)
         }
         }
 
 
-        #vis fn use_router<R: dioxus_router::prelude::Routable + Clone>(cx: &dioxus::prelude::ScopeState) -> &dioxus_router::prelude::GenericRouterContext<R> {
-            dioxus_router::prelude::use_generic_router::<R>(cx)
+        #vis fn GoBackButton<'a>(cx: dioxus::prelude::Scope<'a, dioxus_router::prelude::GenericHistoryButtonProps<'a>>) -> dioxus::prelude::Element<'a> {
+            dioxus_router::prelude::GenericGoBackButton::<#name>(cx)
+        }
+
+        #vis fn GoForwardButton<'a>(cx: dioxus::prelude::Scope<'a, dioxus_router::prelude::GenericHistoryButtonProps<'a>>) -> dioxus::prelude::Element<'a> {
+            dioxus_router::prelude::GenericGoForwardButton::<#name>(cx)
+        }
+
+        #vis fn use_router(cx: &dioxus::prelude::ScopeState) -> &dioxus_router::prelude::GenericRouterContext<#name> {
+            dioxus_router::prelude::use_generic_router(cx)
+        }
+
+        #vis fn use_route(cx: &dioxus::prelude::ScopeState) -> Option<#name> {
+            dioxus_router::prelude::use_generic_route(cx)
         }
         }
 
 
         #error_type
         #error_type

+ 32 - 21
packages/router/README.md

@@ -7,13 +7,10 @@
 
 
 [crates-badge]: https://img.shields.io/crates/v/dioxus-router.svg
 [crates-badge]: https://img.shields.io/crates/v/dioxus-router.svg
 [crates-url]: https://crates.io/crates/dioxus-router
 [crates-url]: https://crates.io/crates/dioxus-router
-
 [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
 [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
 [mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE
 [mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE
-
 [actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg
 [actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg
 [actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster
 [actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster
-
 [discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square
 [discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square
 [discord-url]: https://discord.gg/XgGxMSkvUM
 [discord-url]: https://discord.gg/XgGxMSkvUM
 
 
@@ -28,38 +25,49 @@ Dioxus Router is a first-party Router for all your Dioxus Apps. It provides an
 interface similar to React Router, but takes advantage of types for more
 interface similar to React Router, but takes advantage of types for more
 expressiveness.
 expressiveness.
 
 
-```rust ,no_run
+```rust, no_run
+#![allow(non_snake_case)]
+
 use dioxus::prelude::*;
 use dioxus::prelude::*;
 use dioxus_router::prelude::*;
 use dioxus_router::prelude::*;
+use serde::{Deserialize, Serialize};
+use std::str::FromStr;
+
+#[rustfmt::skip]
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Routable)]
+enum Route {
+    #[nest("/blog")]
+        #[layout(Blog)]
+            #[route("/")]
+            BlogList {},
+
+            #[route("/:blog_id")]
+            BlogPost { blog_id: usize },
+        #[end_layout]
+    #[end_nest]
+    #[route("/")]
+    Index {},
+}
 
 
-fn App(cx: Scope) -> Element {
-    use_router(
-        &cx,
-        &|| Default::default(),
-        &|| Segment::content(comp(Index)).fixed(
-            "blog",
-            Route::content(comp(Blog)).nested(
-                Segment::content(comp(BlogList))
-                    .catch_all((comp(BlogPost), BlogPost))
-            )
-        )
-    );
 
 
+fn App(cx: Scope) -> Element {
     render! {
     render! {
-        Outlet { }
+        Router { }
     }
     }
 }
 }
 
 
+#[inline_props]
 fn Index(cx: Scope) -> Element {
 fn Index(cx: Scope) -> Element {
     render! {
     render! {
         h1 { "Index" }
         h1 { "Index" }
         Link {
         Link {
-            target: "/blog",
+            target: Route::BlogList {},
             "Go to the blog"
             "Go to the blog"
         }
         }
     }
     }
 }
 }
 
 
+#[inline_props]
 fn Blog(cx: Scope) -> Element {
 fn Blog(cx: Scope) -> Element {
     render! {
     render! {
         h1 { "Blog" }
         h1 { "Blog" }
@@ -67,21 +75,23 @@ fn Blog(cx: Scope) -> Element {
     }
     }
 }
 }
 
 
+#[inline_props]
 fn BlogList(cx: Scope) -> Element {
 fn BlogList(cx: Scope) -> Element {
     render! {
     render! {
         h2 { "List of blog posts" }
         h2 { "List of blog posts" }
         Link {
         Link {
-            target: "/blog/1",
+            target: Route::BlogPost { blog_id: 0 },
             "Blog post 1"
             "Blog post 1"
         }
         }
         Link {
         Link {
-            target: "/blog/1",
+            target: Route::BlogPost { blog_id: 1 },
             "Blog post 2"
             "Blog post 2"
         }
         }
     }
     }
 }
 }
 
 
-fn BlogPost(cx: Scope) -> Element {
+#[inline_props]
+fn BlogPost(cx: Scope, blog_id: usize) -> Element {
     render! {
     render! {
         h2 { "Blog Post" }
         h2 { "Blog Post" }
     }
     }
@@ -96,6 +106,7 @@ You need to enable the right features for the platform you're targeting since th
 - Join the discord and ask questions!
 - Join the discord and ask questions!
 
 
 ## License
 ## License
+
 This project is licensed under the [MIT license].
 This project is licensed under the [MIT license].
 
 
 [mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
 [mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT

+ 9 - 4
packages/router/src/components/default_errors.rs

@@ -1,11 +1,12 @@
 use crate::{
 use crate::{
-    components::GenericLink, hooks::use_route, navigation::NavigationTarget, routable::Routable,
+    components::GenericLink, hooks::use_generic_route, navigation::NavigationTarget,
+    routable::Routable,
 };
 };
 use dioxus::prelude::*;
 use dioxus::prelude::*;
 
 
 #[allow(non_snake_case)]
 #[allow(non_snake_case)]
 pub fn FailureExternalNavigation<R: Routable + Clone>(cx: Scope) -> Element {
 pub fn FailureExternalNavigation<R: Routable + Clone>(cx: Scope) -> Element {
-    let href = use_route::<R>(cx).expect(
+    let href = use_generic_route::<R>(cx).expect(
         "`FailureExternalNavigation` can only be mounted by the router itself, \
         "`FailureExternalNavigation` can only be mounted by the router itself, \
             since it is not exposed",
             since it is not exposed",
     );
     );
@@ -38,7 +39,9 @@ pub fn FailureNamedNavigation<R: Routable + Clone>(cx: Scope) -> Element {
             "there is no guarantee."
             "there is no guarantee."
         }
         }
         GenericLink::<R> {
         GenericLink::<R> {
-            target: NavigationTarget::External("https://google.com".into()),
+            target: NavigationTarget::Internal(R::from_str("/").unwrap_or_else(|_| {
+                panic!("Failed to parse `/` as a Route")
+            })),
             "Click here to try to fix the failure."
             "Click here to try to fix the failure."
         }
         }
     }
     }
@@ -58,7 +61,9 @@ pub fn FailureRedirectionLimit<R: Routable + Clone>(cx: Scope) -> Element {
             "there is no guarantee."
             "there is no guarantee."
         }
         }
         GenericLink::<R> {
         GenericLink::<R> {
-            target: NavigationTarget::External("https://google.com".into()),
+            target: NavigationTarget::Internal(R::from_str("/").unwrap_or_else(|_| {
+                panic!("Failed to parse `/` as a Route")
+            })),
             "Click here to try to fix the failure."
             "Click here to try to fix the failure."
         }
         }
     }
     }

+ 40 - 30
packages/router/src/components/history_buttons.rs

@@ -1,41 +1,44 @@
 use dioxus::prelude::*;
 use dioxus::prelude::*;
 use log::error;
 use log::error;
 
 
-use crate::{routable::Routable, utils::use_router_internal::use_router_internal};
+use crate::{prelude::*, utils::use_router_internal::use_router_internal};
 
 
 /// The properties for a [`GoBackButton`] or a [`GoForwardButton`].
 /// The properties for a [`GoBackButton`] or a [`GoForwardButton`].
 #[derive(Debug, Props)]
 #[derive(Debug, Props)]
-pub struct HistoryButtonProps<'a> {
+pub struct GenericHistoryButtonProps<'a> {
     /// The children to render within the generated HTML button tag.
     /// The children to render within the generated HTML button tag.
     pub children: Element<'a>,
     pub children: Element<'a>,
 }
 }
 
 
 /// A button to go back through the navigation history. Similar to a browsers back button.
 /// A button to go back through the navigation history. Similar to a browsers back button.
 ///
 ///
-/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive.
+/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive.
 ///
 ///
 /// The button will disable itself if it is known that no prior history is available.
 /// The button will disable itself if it is known that no prior history is available.
 ///
 ///
-/// [`use_router`]: crate::hooks::use_router
-///
 /// # Panic
 /// # Panic
-/// - When the [`GoBackButton`] is not nested within another component calling the [`use_router`]
+/// - When the [`GoBackButton`] is not nested within a [`GenericRouter`] component
 ///   hook, but only in debug builds.
 ///   hook, but only in debug builds.
 ///
 ///
 /// # Example
 /// # Example
 /// ```rust
 /// ```rust
 /// # use dioxus::prelude::*;
 /// # use dioxus::prelude::*;
 /// # use dioxus_router::prelude::*;
 /// # use dioxus_router::prelude::*;
+/// # use serde::{Deserialize, Serialize};
+/// #[derive(Clone, Serialize, Deserialize, Routable)]
+/// enum Route {
+///     #[route("/")]
+///     Index {},
+/// }
+///
 /// fn App(cx: Scope) -> Element {
 /// fn App(cx: Scope) -> Element {
-///     use_router(
-///         &cx,
-///         &|| RouterConfiguration {
-///             synchronous: true, // asynchronicity not needed for doc test
-///             ..Default::default()
-///         },
-///         &|| Segment::empty()
-///     );
+///     render! {
+///         Router {}
+///     }
+/// }
 ///
 ///
+/// #[inline_props]
+/// fn Index(cx: Scope) -> Element {
 ///     render! {
 ///     render! {
 ///         GoBackButton {
 ///         GoBackButton {
 ///             "go back"
 ///             "go back"
@@ -51,8 +54,10 @@ pub struct HistoryButtonProps<'a> {
 /// # );
 /// # );
 /// ```
 /// ```
 #[allow(non_snake_case)]
 #[allow(non_snake_case)]
-pub fn GoBackButton<'a, R: Routable>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
-    let HistoryButtonProps { children } = cx.props;
+pub fn GenericGoBackButton<'a, R: Routable>(
+    cx: Scope<'a, GenericHistoryButtonProps<'a>>,
+) -> Element {
+    let GenericHistoryButtonProps { children } = cx.props;
 
 
     // hook up to router
     // hook up to router
     let router = match use_router_internal::<R>(cx) {
     let router = match use_router_internal::<R>(cx) {
@@ -81,30 +86,33 @@ pub fn GoBackButton<'a, R: Routable>(cx: Scope<'a, HistoryButtonProps<'a>>) -> E
 
 
 /// A button to go forward through the navigation history. Similar to a browsers forward button.
 /// A button to go forward through the navigation history. Similar to a browsers forward button.
 ///
 ///
-/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive.
+/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive.
 ///
 ///
 /// The button will disable itself if it is known that no later history is available.
 /// The button will disable itself if it is known that no later history is available.
 ///
 ///
-/// [`use_router`]: crate::hooks::use_router
-///
 /// # Panic
 /// # Panic
-/// - When the [`GoForwardButton`] is not nested within another component calling the [`use_router`]
+/// - When the [`GoForwardButton`] is not nested within a [`GenericRouter`] component
 ///   hook, but only in debug builds.
 ///   hook, but only in debug builds.
 ///
 ///
 /// # Example
 /// # Example
 /// ```rust
 /// ```rust
 /// # use dioxus::prelude::*;
 /// # use dioxus::prelude::*;
 /// # use dioxus_router::prelude::*;
 /// # use dioxus_router::prelude::*;
+/// # use serde::{Deserialize, Serialize};
+/// #[derive(Clone, Serialize, Deserialize, Routable)]
+/// enum Route {
+///     #[route("/")]
+///     Index {},
+/// }
+///
 /// fn App(cx: Scope) -> Element {
 /// fn App(cx: Scope) -> Element {
-///     use_router(
-///         &cx,
-///         &|| RouterConfiguration {
-///             synchronous: true, // asynchronicity not needed for doc test
-///             ..Default::default()
-///         },
-///         &|| Segment::empty()
-///     );
+///     render! {
+///         Router {}
+///     }
+/// }
 ///
 ///
+/// #[inline_props]
+/// fn Index(cx: Scope) -> Element {
 ///     render! {
 ///     render! {
 ///         GoForwardButton {
 ///         GoForwardButton {
 ///             "go forward"
 ///             "go forward"
@@ -120,8 +128,10 @@ pub fn GoBackButton<'a, R: Routable>(cx: Scope<'a, HistoryButtonProps<'a>>) -> E
 /// # );
 /// # );
 /// ```
 /// ```
 #[allow(non_snake_case)]
 #[allow(non_snake_case)]
-pub fn GoForwardButton<'a, R: Routable>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
-    let HistoryButtonProps { children } = cx.props;
+pub fn GenericGoForwardButton<'a, R: Routable>(
+    cx: Scope<'a, GenericHistoryButtonProps<'a>>,
+) -> Element {
+    let GenericHistoryButtonProps { children } = cx.props;
 
 
     // hook up to router
     // hook up to router
     let router = match use_router_internal::<R>(cx) {
     let router = match use_router_internal::<R>(cx) {

+ 32 - 34
packages/router/src/components/link.rs

@@ -4,7 +4,7 @@ use dioxus::prelude::*;
 use log::error;
 use log::error;
 
 
 use crate::navigation::NavigationTarget;
 use crate::navigation::NavigationTarget;
-use crate::routable::Routable;
+use crate::prelude::*;
 use crate::utils::use_router_internal::use_router_internal;
 use crate::utils::use_router_internal::use_router_internal;
 
 
 /// The properties for a [`Link`].
 /// The properties for a [`Link`].
@@ -63,56 +63,54 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
 
 
 /// A link to navigate to another route.
 /// A link to navigate to another route.
 ///
 ///
-/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive.
+/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive.
 ///
 ///
-/// Unlike a regular HTML anchor, a [`Link`] allows the router to handle the navigation and doesn't
+/// Unlike a regular HTML anchor, a [`GenericLink`] allows the router to handle the navigation and doesn't
 /// cause the browser to load a new page.
 /// cause the browser to load a new page.
 ///
 ///
-/// However, in the background a [`Link`] still generates an anchor, which you can use for styling
+/// However, in the background a [`GenericLink`] still generates an anchor, which you can use for styling
 /// as normal.
 /// as normal.
 ///
 ///
-/// [`use_router`]: crate::hooks::use_router
-///
 /// # External targets
 /// # External targets
-/// When the [`Link`]s target is an [`External`] target, that is used as the `href` directly. This
-/// means that a [`Link`] can always navigate to an [`External`] target.
-///
-/// This is different from a [`Navigator`], which can only navigate to external targets when the
-/// routers [`HistoryProvider`] supports it.
-///
-/// [`External`]: dioxus_router_core::navigation::NavigationTarget::External
-/// [`HistoryProvider`]: dioxus_router_core::history::HistoryProvider
-/// [`Navigator`]: dioxus_router_core::Navigator
+/// When the [`GenericLink`]s target is an [`NavigationTarget::External`] target, that is used as the `href` directly. This
+/// means that a [`GenericLink`] can always navigate to an [`NavigationTarget::External`] target, even if the [`HistoryProvider`] does not support it.
 ///
 ///
 /// # Panic
 /// # Panic
-/// - When the [`Link`] is not nested within another component calling the [`use_router`] hook, but
+/// - When the [`GenericLink`] is not nested within a [`GenericRouter`], but
 ///   only in debug builds.
 ///   only in debug builds.
 ///
 ///
 /// # Example
 /// # Example
 /// ```rust
 /// ```rust
 /// # use dioxus::prelude::*;
 /// # use dioxus::prelude::*;
 /// # use dioxus_router::prelude::*;
 /// # use dioxus_router::prelude::*;
-/// fn App(cx: Scope) -> Element {
-///     use_router(
-///         &cx,
-///         &|| RouterConfiguration {
-///             synchronous: true, // asynchronicity not needed for doc test
-///             ..Default::default()
-///         },
-///         &|| Segment::empty()
-///     );
+/// # use serde::{Deserialize, Serialize};
+///
+/// #[derive(Clone, Serialize, Deserialize, Routable)]
+/// enum Route {
+///     #[route("/")]
+///     Index {},
+/// }
 ///
 ///
+/// fn App(cx: Scope) -> Element {
 ///     render! {
 ///     render! {
-///         Link {
-///             active_class: "active",
-///             class: "link_class",
-///             exact: true,
-///             id: "link_id",
-///             new_tab: true,
-///             rel: "link_rel",
-///             target: "/",
+///         Router {}
+///     }
+/// }
 ///
 ///
-///             "A fully configured link"
+/// #[inline_props]
+/// fn Index(cx: Scope) -> Element {
+///     render! {
+///         render! {
+///             Link {
+///                 active_class: "active",
+///                 class: "link_class",
+///                 id: "link_id",
+///                 new_tab: true,
+///                 rel: "link_rel",
+///                 target: Route::Index {},
+///    
+///                 "A fully configured link"
+///             }
 ///         }
 ///         }
 ///     }
 ///     }
 /// }
 /// }

+ 43 - 16
packages/router/src/components/outlet.rs

@@ -3,13 +3,11 @@ use dioxus::prelude::*;
 
 
 /// An outlet for the current content.
 /// An outlet for the current content.
 ///
 ///
-/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive.
+/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive.
 ///
 ///
 /// The [`Outlet`] is aware of how many [`Outlet`]s it is nested within. It will render the content
 /// The [`Outlet`] is aware of how many [`Outlet`]s it is nested within. It will render the content
 /// of the active route that is __exactly as deep__.
 /// of the active route that is __exactly as deep__.
 ///
 ///
-/// [`use_router`]: crate::hooks::use_router
-///
 /// # Panic
 /// # Panic
 /// - When the [`Outlet`] is not nested within another component calling the [`use_router`] hook,
 /// - When the [`Outlet`] is not nested within another component calling the [`use_router`] hook,
 ///   but only in debug builds.
 ///   but only in debug builds.
@@ -17,32 +15,61 @@ use dioxus::prelude::*;
 /// # Example
 /// # Example
 /// ```rust
 /// ```rust
 /// # use dioxus::prelude::*;
 /// # use dioxus::prelude::*;
+/// # use serde::{Deserialize, Serialize};
 /// # use dioxus_router::prelude::*;
 /// # use dioxus_router::prelude::*;
-/// fn App(cx: Scope) -> Element {
-///     use_router(
-///         &cx,
-///         &|| RouterConfiguration {
-///             synchronous: true, // asynchronicity not needed for doc test
-///             ..Default::default()
-///         },
-///         &|| Segment::content(comp(Content))
-///     );
+/// #[derive(Clone, Serialize, Deserialize, Routable)]
+/// #[rustfmt::skip]
+/// enum Route {
+///     #[nest("/wrap")]
+///         #[layout(Wrapper)] // Every layout component must have one Outlet
+///             #[route("/")]
+///             Child {},
+///         #[end_layout]
+///     #[end_nest]
+///     #[route("/")]
+///     Index {},
+/// }
 ///
 ///
+/// #[inline_props]
+/// fn Index(cx: Scope) -> Element {
+///     render! {
+///         div {
+///             "Index"
+///         }
+///     }
+/// }
+///
+/// #[inline_props]
+/// fn Wrapper(cx: Scope) -> Element {
 ///     render! {
 ///     render! {
 ///         h1 { "App" }
 ///         h1 { "App" }
-///         Outlet { } // The content component will be rendered here
+///         Outlet {} // The content of child routes will be rendered here
 ///     }
 ///     }
 /// }
 /// }
 ///
 ///
-/// fn Content(cx: Scope) -> Element {
+/// #[inline_props]
+/// fn Child(cx: Scope) -> Element {
 ///     render! {
 ///     render! {
-///         p { "Content" }
+///         p {
+///             "Child"
+///         }
 ///     }
 ///     }
 /// }
 /// }
+///
+/// # fn App(cx: Scope) -> Element {
+/// #     render! {
+/// #         Router {
+/// #             config: RouterConfiguration {
+/// #                 history: Box::new(MemoryHistory::with_initial_path("/wrap").unwrap()),
+/// #                 ..Default::default()
+/// #             }
+/// #         }
+/// #     }
+/// # }
 /// #
 /// #
 /// # let mut vdom = VirtualDom::new(App);
 /// # let mut vdom = VirtualDom::new(App);
 /// # let _ = vdom.rebuild();
 /// # let _ = vdom.rebuild();
-/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><p>Content</p>");
+/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><p>Child</p>");
 /// ```
 /// ```
 pub fn GenericOutlet<R: Routable + Clone>(cx: Scope) -> Element {
 pub fn GenericOutlet<R: Routable + Clone>(cx: Scope) -> Element {
     OutletContext::render::<R>(cx)
     OutletContext::render::<R>(cx)

+ 7 - 4
packages/router/src/components/router.rs

@@ -1,5 +1,6 @@
 use dioxus::prelude::*;
 use dioxus::prelude::*;
 use log::error;
 use log::error;
+use serde::{de::DeserializeOwned, Serialize};
 use std::{cell::RefCell, str::FromStr};
 use std::{cell::RefCell, str::FromStr};
 
 
 use crate::{
 use crate::{
@@ -13,7 +14,7 @@ pub struct RouterCfg<R: Routable> {
     config: RefCell<Option<RouterConfiguration<R>>>,
     config: RefCell<Option<RouterConfiguration<R>>>,
 }
 }
 
 
-impl<R: Routable> Default for RouterCfg<R>
+impl<R: Routable + Serialize + DeserializeOwned> Default for RouterCfg<R>
 where
 where
     <R as FromStr>::Err: std::fmt::Display,
     <R as FromStr>::Err: std::fmt::Display,
 {
 {
@@ -34,7 +35,7 @@ impl<R: Routable> From<RouterConfiguration<R>> for RouterCfg<R> {
 
 
 /// The props for [`Router`].
 /// The props for [`Router`].
 #[derive(Props)]
 #[derive(Props)]
-pub struct GenericRouterProps<R: Routable>
+pub struct GenericRouterProps<R: Routable + Serialize + DeserializeOwned>
 where
 where
     <R as FromStr>::Err: std::fmt::Display,
     <R as FromStr>::Err: std::fmt::Display,
 {
 {
@@ -42,7 +43,7 @@ where
     config: RouterCfg<R>,
     config: RouterCfg<R>,
 }
 }
 
 
-impl<R: Routable> PartialEq for GenericRouterProps<R>
+impl<R: Routable + Serialize + DeserializeOwned> PartialEq for GenericRouterProps<R>
 where
 where
     <R as FromStr>::Err: std::fmt::Display,
     <R as FromStr>::Err: std::fmt::Display,
 {
 {
@@ -53,7 +54,9 @@ where
 }
 }
 
 
 /// A component that renders the current route.
 /// A component that renders the current route.
-pub fn GenericRouter<R: Routable + Clone>(cx: Scope<GenericRouterProps<R>>) -> Element
+pub fn GenericRouter<R: Routable + Clone + Serialize + DeserializeOwned>(
+    cx: Scope<GenericRouterProps<R>>,
+) -> Element
 where
 where
     <R as FromStr>::Err: std::fmt::Display,
     <R as FromStr>::Err: std::fmt::Display,
 {
 {

+ 2 - 2
packages/router/src/contexts/outlet.rs

@@ -1,6 +1,6 @@
 use dioxus::prelude::*;
 use dioxus::prelude::*;
 
 
-use crate::{hooks::use_route, routable::Routable};
+use crate::{hooks::use_generic_route, routable::Routable};
 
 
 #[derive(Clone)]
 #[derive(Clone)]
 pub(crate) struct OutletContext {
 pub(crate) struct OutletContext {
@@ -25,7 +25,7 @@ impl OutletContext {
             }
             }
         });
         });
 
 
-        use_route::<R>(cx)
+        use_generic_route::<R>(cx)
             .expect("Outlet must be inside of a router")
             .expect("Outlet must be inside of a router")
             .render(cx, current_level)
             .render(cx, current_level)
     }
     }

+ 6 - 4
packages/router/src/contexts/router.rs

@@ -149,7 +149,8 @@ where
     /// Push a new location.
     /// Push a new location.
     ///
     ///
     /// The previous location will be available to go back to.
     /// The previous location will be available to go back to.
-    pub fn push(&self, target: NavigationTarget<R>) -> Option<NavigationFailure<R>> {
+    pub fn push(&self, target: impl Into<NavigationTarget<R>>) -> Option<NavigationFailure<R>> {
+        let target = target.into();
         let mut state = self.state_mut();
         let mut state = self.state_mut();
         match target {
         match target {
             NavigationTarget::Internal(p) => state.history.push(p),
             NavigationTarget::Internal(p) => state.history.push(p),
@@ -163,7 +164,8 @@ where
     /// Replace the current location.
     /// Replace the current location.
     ///
     ///
     /// The previous location will **not** be available to go back to.
     /// The previous location will **not** be available to go back to.
-    pub fn replace(&self, target: NavigationTarget<R>) -> Option<NavigationFailure<R>> {
+    pub fn replace(&self, target: impl Into<NavigationTarget<R>>) -> Option<NavigationFailure<R>> {
+        let target = target.into();
         let mut state = self.state_mut();
         let mut state = self.state_mut();
         match target {
         match target {
             NavigationTarget::Internal(p) => state.history.replace(p),
             NavigationTarget::Internal(p) => state.history.replace(p),
@@ -264,7 +266,7 @@ where
 //         struct TestHistory {}
 //         struct TestHistory {}
 
 
 //         impl HistoryProvider for TestHistory {
 //         impl HistoryProvider for TestHistory {
-//             fn current_path(&self) -> String {
+//             fn current_route(&self) -> String {
 //                 todo!()
 //                 todo!()
 //             }
 //             }
 
 
@@ -758,7 +760,7 @@ where
 //         struct TestHistory {}
 //         struct TestHistory {}
 
 
 //         impl HistoryProvider for TestHistory {
 //         impl HistoryProvider for TestHistory {
-//             fn current_path(&self) -> String {
+//             fn current_route(&self) -> String {
 //                 String::from("/")
 //                 String::from("/")
 //             }
 //             }
 
 

+ 21 - 151
packages/router/src/history/memory.rs

@@ -18,16 +18,31 @@ where
     /// Create a [`MemoryHistory`] starting at `path`.
     /// Create a [`MemoryHistory`] starting at `path`.
     ///
     ///
     /// ```rust
     /// ```rust
-    /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
-    /// let mut history = MemoryHistory::with_initial_path("/some/path").unwrap();
-    /// assert_eq!(history.current_path(), "/some/path");
+    /// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
+    /// # use dioxus_router::prelude::*;
+    /// # use serde::{Deserialize, Serialize};
+    /// # use dioxus::prelude::*;
+    /// # #[inline_props]
+    /// # fn Index(cx: Scope) -> Element { todo!() }
+    /// # #[inline_props]
+    /// # fn OtherPage(cx: Scope) -> Element { todo!() }
+    /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)]
+    /// enum Route {
+    ///     #[route("/")]
+    ///     Index {},
+    ///     #[route("/some-other-page")]
+    ///     OtherPage {},
+    /// }
+    ///
+    /// let mut history = MemoryHistory::<Route>::with_initial_path("/").unwrap();
+    /// assert_eq!(history.current_route(), Route::Index {});
     /// assert_eq!(history.can_go_back(), false);
     /// assert_eq!(history.can_go_back(), false);
     /// ```
     /// ```
-    pub fn with_initial_path(path: impl Into<String>) -> Result<Self, <R as FromStr>::Err> {
-        let path = path.into();
+    pub fn with_initial_path(path: impl AsRef<str>) -> Result<Self, <R as FromStr>::Err> {
+        let path = path.as_ref();
 
 
         Ok(Self {
         Ok(Self {
-            current: R::from_str(&path)?,
+            current: R::from_str(path)?,
             ..Default::default()
             ..Default::default()
         })
         })
     }
     }
@@ -83,148 +98,3 @@ impl<R: Routable> HistoryProvider<R> for MemoryHistory<R> {
         self.current = path;
         self.current = path;
     }
     }
 }
 }
-
-// #[cfg(test)]
-// mod tests {
-//     use super::*;
-
-//     #[test]
-//     fn default() {
-//         let mem = MemoryHistory::default();
-//         assert_eq!(mem.current, Url::parse(INITIAL_URL).unwrap());
-//         assert_eq!(mem.history, Vec::<String>::new());
-//         assert_eq!(mem.future, Vec::<String>::new());
-//     }
-
-//     #[test]
-//     fn with_initial_path() {
-//         let mem = MemoryHistory::with_initial_path("something").unwrap();
-//         assert_eq!(
-//             mem.current,
-//             Url::parse(&format!("{INITIAL_URL}something")).unwrap()
-//         );
-//         assert_eq!(mem.history, Vec::<String>::new());
-//         assert_eq!(mem.future, Vec::<String>::new());
-//     }
-
-//     #[test]
-//     fn with_initial_path_with_leading_slash() {
-//         let mem = MemoryHistory::with_initial_path("/something").unwrap();
-//         assert_eq!(
-//             mem.current,
-//             Url::parse(&format!("{INITIAL_URL}something")).unwrap()
-//         );
-//         assert_eq!(mem.history, Vec::<String>::new());
-//         assert_eq!(mem.future, Vec::<String>::new());
-//     }
-
-//     #[test]
-//     fn can_go_back() {
-//         let mut mem = MemoryHistory::default();
-//         assert!(!mem.can_go_back());
-
-//         mem.push(String::from("/test"));
-//         assert!(mem.can_go_back());
-//     }
-
-//     #[test]
-//     fn go_back() {
-//         let mut mem = MemoryHistory::default();
-//         mem.push(String::from("/test"));
-//         mem.go_back();
-
-//         assert_eq!(mem.current, Url::parse(INITIAL_URL).unwrap());
-//         assert!(mem.history.is_empty());
-//         assert_eq!(mem.future, vec![format!("{INITIAL_URL}test")]);
-//     }
-
-//     #[test]
-//     fn can_go_forward() {
-//         let mut mem = MemoryHistory::default();
-//         assert!(!mem.can_go_forward());
-
-//         mem.push(String::from("/test"));
-//         mem.go_back();
-
-//         assert!(mem.can_go_forward());
-//     }
-
-//     #[test]
-//     fn go_forward() {
-//         let mut mem = MemoryHistory::default();
-//         mem.push(String::from("/test"));
-//         mem.go_back();
-//         mem.go_forward();
-
-//         assert_eq!(
-//             mem.current,
-//             Url::parse(&format!("{INITIAL_URL}test")).unwrap()
-//         );
-//         assert_eq!(mem.history, vec![INITIAL_URL.to_string()]);
-//         assert!(mem.future.is_empty());
-//     }
-
-//     #[test]
-//     fn push() {
-//         let mut mem = MemoryHistory::default();
-//         mem.push(String::from("/test"));
-
-//         assert_eq!(
-//             mem.current,
-//             Url::parse(&format!("{INITIAL_URL}test")).unwrap()
-//         );
-//         assert_eq!(mem.history, vec![INITIAL_URL.to_string()]);
-//         assert!(mem.future.is_empty());
-//     }
-
-//     #[test]
-//     #[should_panic = r#"cannot navigate to paths starting with "//": //test"#]
-//     #[cfg(debug_assertions)]
-//     fn push_debug() {
-//         let mut mem = MemoryHistory::default();
-//         mem.push(String::from("//test"));
-//     }
-
-//     #[test]
-//     #[cfg(not(debug_assertions))]
-//     fn push_release() {
-//         let mut mem = MemoryHistory::default();
-//         mem.push(String::from("//test"));
-
-//         assert_eq!(mem.current, Url::parse(INITIAL_URL).unwrap());
-//         assert!(mem.history.is_empty())
-//     }
-
-//     #[test]
-//     fn replace() {
-//         let mut mem = MemoryHistory::default();
-//         mem.push(String::from("/test"));
-//         mem.push(String::from("/other"));
-//         mem.go_back();
-//         mem.replace(String::from("/replace"));
-
-//         assert_eq!(
-//             mem.current,
-//             Url::parse(&format!("{INITIAL_URL}replace")).unwrap()
-//         );
-//         assert_eq!(mem.history, vec![INITIAL_URL.to_string()]);
-//         assert_eq!(mem.future, vec![format!("{INITIAL_URL}other")]);
-//     }
-
-//     #[test]
-//     #[should_panic = r#"cannot navigate to paths starting with "//": //test"#]
-//     #[cfg(debug_assertions)]
-//     fn replace_debug() {
-//         let mut mem = MemoryHistory::default();
-//         mem.replace(String::from("//test"));
-//     }
-
-//     #[test]
-//     #[cfg(not(debug_assertions))]
-//     fn replace_release() {
-//         let mut mem = MemoryHistory::default();
-//         mem.replace(String::from("//test"));
-
-//         assert_eq!(mem.current, Url::parse(INITIAL_URL).unwrap());
-//     }
-// }

+ 129 - 35
packages/router/src/history/mod.rs

@@ -31,7 +31,7 @@ pub(crate) mod web_scroll;
 /// An integration with some kind of navigation history.
 /// An integration with some kind of navigation history.
 ///
 ///
 /// Depending on your use case, your implementation may deviate from the described procedure. This
 /// Depending on your use case, your implementation may deviate from the described procedure. This
-/// is fine, as long as both `current_path` and `current_query` match the described format.
+/// is fine, as long as both `current_route` and `current_query` match the described format.
 ///
 ///
 /// However, you should document all deviations. Also, make sure the navigation is user-friendly.
 /// However, you should document all deviations. Also, make sure the navigation is user-friendly.
 /// The described behaviors are designed to mimic a web browser, which most users should already
 /// The described behaviors are designed to mimic a web browser, which most users should already
@@ -42,12 +42,26 @@ pub trait HistoryProvider<R: Routable> {
     /// **Must start** with `/`. **Must _not_ contain** the prefix.
     /// **Must start** with `/`. **Must _not_ contain** the prefix.
     ///
     ///
     /// ```rust
     /// ```rust
-    /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
-    /// let mut history = MemoryHistory::default();
-    /// assert_eq!(history.current_path(), "/");
+    /// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
+    /// # use dioxus_router::prelude::*;
+    /// # use serde::{Deserialize, Serialize};
+    /// # use dioxus::prelude::*;
+    /// # #[inline_props]
+    /// # fn Index(cx: Scope) -> Element { todo!() }
+    /// # #[inline_props]
+    /// # fn OtherPage(cx: Scope) -> Element { todo!() }
+    /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)]
+    /// enum Route {
+    ///     #[route("/")]
+    ///     Index {},
+    ///     #[route("/some-other-page")]
+    ///     OtherPage {},
+    /// }
+    /// let mut history = MemoryHistory::<Route>::default();
+    /// assert_eq!(history.current_route().to_string(), "/");
     ///
     ///
-    /// history.push(String::from("/path"));
-    /// assert_eq!(history.current_path(), "/path");
+    /// history.push(Route::OtherPage {});
+    /// assert_eq!(history.current_route().to_string(), "/some-other-page");
     /// ```
     /// ```
     #[must_use]
     #[must_use]
     fn current_route(&self) -> R;
     fn current_route(&self) -> R;
@@ -68,11 +82,21 @@ pub trait HistoryProvider<R: Routable> {
     /// If a [`HistoryProvider`] cannot know this, it should return [`true`].
     /// If a [`HistoryProvider`] cannot know this, it should return [`true`].
     ///
     ///
     /// ```rust
     /// ```rust
-    /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
-    /// let mut history = MemoryHistory::default();
+    /// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
+    /// # use dioxus_router::prelude::*;
+    /// # use serde::{Deserialize, Serialize};
+    /// # use dioxus::prelude::*;   
+    /// # #[inline_props]
+    /// # fn Index(cx: Scope) -> Element { todo!() }
+    /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)]
+    /// enum Route {
+    ///     #[route("/")]
+    ///     Index {},
+    /// }
+    /// let mut history = MemoryHistory::<Route>::default();
     /// assert_eq!(history.can_go_back(), false);
     /// assert_eq!(history.can_go_back(), false);
     ///
     ///
-    /// history.push(String::from("/some-other-page"));
+    /// history.push(Route::Index {});
     /// assert_eq!(history.can_go_back(), true);
     /// assert_eq!(history.can_go_back(), true);
     /// ```
     /// ```
     #[must_use]
     #[must_use]
@@ -86,18 +110,32 @@ pub trait HistoryProvider<R: Routable> {
     /// might be called, even if `can_go_back` returns [`false`].
     /// might be called, even if `can_go_back` returns [`false`].
     ///
     ///
     /// ```rust
     /// ```rust
-    /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
-    /// let mut history = MemoryHistory::default();
-    /// assert_eq!(history.current_path(), "/");
+    /// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
+    /// # use dioxus_router::prelude::*;
+    /// # use serde::{Deserialize, Serialize};
+    /// # use dioxus::prelude::*;
+    /// # #[inline_props]
+    /// # fn Index(cx: Scope) -> Element { todo!() }
+    /// # #[inline_props]
+    /// # fn OtherPage(cx: Scope) -> Element { todo!() }
+    /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)]
+    /// enum Route {
+    ///     #[route("/")]
+    ///     Index {},
+    ///     #[route("/some-other-page")]
+    ///     OtherPage {},
+    /// }
+    /// let mut history = MemoryHistory::<Route>::default();
+    /// assert_eq!(history.current_route().to_string(), "/");
     ///
     ///
     /// history.go_back();
     /// history.go_back();
-    /// assert_eq!(history.current_path(), "/");
+    /// assert_eq!(history.current_route().to_string(), "/");
     ///
     ///
-    /// history.push(String::from("/some-other-page"));
-    /// assert_eq!(history.current_path(), "/some-other-page");
+    /// history.push(Route::OtherPage {});
+    /// assert_eq!(history.current_route().to_string(), "/some-other-page");
     ///
     ///
     /// history.go_back();
     /// history.go_back();
-    /// assert_eq!(history.current_path(), "/");
+    /// assert_eq!(history.current_route().to_string(), "/");
     /// ```
     /// ```
     fn go_back(&mut self);
     fn go_back(&mut self);
 
 
@@ -106,11 +144,25 @@ pub trait HistoryProvider<R: Routable> {
     /// If a [`HistoryProvider`] cannot know this, it should return [`true`].
     /// If a [`HistoryProvider`] cannot know this, it should return [`true`].
     ///
     ///
     /// ```rust
     /// ```rust
-    /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
-    /// let mut history = MemoryHistory::default();
+    /// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
+    /// # use dioxus_router::prelude::*;
+    /// # use serde::{Deserialize, Serialize};
+    /// # use dioxus::prelude::*;
+    /// # #[inline_props]
+    /// # fn Index(cx: Scope) -> Element { todo!() }
+    /// # #[inline_props]
+    /// # fn OtherPage(cx: Scope) -> Element { todo!() }
+    /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)]
+    /// enum Route {
+    ///     #[route("/")]
+    ///     Index {},
+    ///     #[route("/some-other-page")]
+    ///     OtherPage {},
+    /// }
+    /// let mut history = MemoryHistory::<Route>::default();
     /// assert_eq!(history.can_go_forward(), false);
     /// assert_eq!(history.can_go_forward(), false);
     ///
     ///
-    /// history.push(String::from("/some-other-page"));
+    /// history.push(Route::OtherPage {});
     /// assert_eq!(history.can_go_forward(), false);
     /// assert_eq!(history.can_go_forward(), false);
     ///
     ///
     /// history.go_back();
     /// history.go_back();
@@ -127,16 +179,30 @@ pub trait HistoryProvider<R: Routable> {
     /// might be called, even if `can_go_forward` returns [`false`].
     /// might be called, even if `can_go_forward` returns [`false`].
     ///
     ///
     /// ```rust
     /// ```rust
-    /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
-    /// let mut history = MemoryHistory::default();
-    /// history.push(String::from("/some-other-page"));
-    /// assert_eq!(history.current_path(), "/some-other-page");
+    /// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
+    /// # use dioxus_router::prelude::*;
+    /// # use serde::{Deserialize, Serialize};
+    /// # use dioxus::prelude::*;
+    /// # #[inline_props]
+    /// # fn Index(cx: Scope) -> Element { todo!() }
+    /// # #[inline_props]
+    /// # fn OtherPage(cx: Scope) -> Element { todo!() }
+    /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)]
+    /// enum Route {
+    ///     #[route("/")]
+    ///     Index {},
+    ///     #[route("/some-other-page")]
+    ///     OtherPage {},
+    /// }
+    /// let mut history = MemoryHistory::<Route>::default();
+    /// history.push(Route::OtherPage {});
+    /// assert_eq!(history.current_route(), Route::OtherPage {});
     ///
     ///
     /// history.go_back();
     /// history.go_back();
-    /// assert_eq!(history.current_path(), "/");
+    /// assert_eq!(history.current_route(), Route::Index {});
     ///
     ///
     /// history.go_forward();
     /// history.go_forward();
-    /// assert_eq!(history.current_path(), "/some-other-page");
+    /// assert_eq!(history.current_route(), Route::OtherPage {});
     /// ```
     /// ```
     fn go_forward(&mut self);
     fn go_forward(&mut self);
 
 
@@ -148,12 +214,26 @@ pub trait HistoryProvider<R: Routable> {
     /// 3. Clear the navigation future.
     /// 3. Clear the navigation future.
     ///
     ///
     /// ```rust
     /// ```rust
-    /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
-    /// let mut history = MemoryHistory::default();
-    /// assert_eq!(history.current_path(), "/");
+    /// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
+    /// # use dioxus_router::prelude::*;
+    /// # use serde::{Deserialize, Serialize};
+    /// # use dioxus::prelude::*;
+    /// # #[inline_props]
+    /// # fn Index(cx: Scope) -> Element { todo!() }
+    /// # #[inline_props]
+    /// # fn OtherPage(cx: Scope) -> Element { todo!() }
+    /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)]
+    /// enum Route {
+    ///     #[route("/")]
+    ///     Index {},
+    ///     #[route("/some-other-page")]
+    ///     OtherPage {},
+    /// }
+    /// let mut history = MemoryHistory::<Route>::default();
+    /// assert_eq!(history.current_route(), Route::Index {});
     ///
     ///
-    /// history.push(String::from("/some-other-page"));
-    /// assert_eq!(history.current_path(), "/some-other-page");
+    /// history.push(Route::OtherPage {});
+    /// assert_eq!(history.current_route(), Route::OtherPage {});
     /// assert!(history.can_go_back());
     /// assert!(history.can_go_back());
     /// ```
     /// ```
     fn push(&mut self, route: R);
     fn push(&mut self, route: R);
@@ -165,12 +245,26 @@ pub trait HistoryProvider<R: Routable> {
     /// untouched.
     /// untouched.
     ///
     ///
     /// ```rust
     /// ```rust
-    /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
-    /// let mut history = MemoryHistory::default();
-    /// assert_eq!(history.current_path(), "/");
+    /// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
+    /// # use dioxus_router::prelude::*;
+    /// # use serde::{Deserialize, Serialize};
+    /// # use dioxus::prelude::*;
+    /// # #[inline_props]
+    /// # fn Index(cx: Scope) -> Element { todo!() }
+    /// # #[inline_props]
+    /// # fn OtherPage(cx: Scope) -> Element { todo!() }
+    /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)]
+    /// enum Route {
+    ///     #[route("/")]
+    ///     Index {},
+    ///     #[route("/some-other-page")]
+    ///     OtherPage {},
+    /// }
+    /// let mut history = MemoryHistory::<Route>::default();
+    /// assert_eq!(history.current_route(), Route::Index {});
     ///
     ///
-    /// history.replace(String::from("/some-other-page"));
-    /// assert_eq!(history.current_path(), "/some-other-page");
+    /// history.replace(Route::OtherPage {});
+    /// assert_eq!(history.current_route(), Route::OtherPage {});
     /// assert!(!history.can_go_back());
     /// assert!(!history.can_go_back());
     /// ```
     /// ```
     fn replace(&mut self, path: R);
     fn replace(&mut self, path: R);

+ 1 - 3
packages/router/src/history/web.rs

@@ -28,7 +28,7 @@ struct WebHistoryState<R> {
     scroll: ScrollPosition,
     scroll: ScrollPosition,
 }
 }
 
 
-/// A [`HistoryProvider`] that integrates with a browser via the [History API].
+/// A [`HistoryProvider`] that integrates with a browser via the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API).
 ///
 ///
 /// # Prefix
 /// # Prefix
 /// This [`HistoryProvider`] supports a prefix, which can be used for web apps that aren't located
 /// This [`HistoryProvider`] supports a prefix, which can be used for web apps that aren't located
@@ -40,8 +40,6 @@ struct WebHistoryState<R> {
 ///
 ///
 /// Application developers are responsible for not rendering the router if the prefix is not present
 /// Application developers are responsible for not rendering the router if the prefix is not present
 /// in the URL. Otherwise, if a router navigation is triggered, the prefix will be added.
 /// in the URL. Otherwise, if a router navigation is triggered, the prefix will be added.
-///
-/// [History API]: https://developer.mozilla.org/en-US/docs/Web/API/History_API
 pub struct WebHistory<R: Serialize + DeserializeOwned + Routable> {
 pub struct WebHistory<R: Serialize + DeserializeOwned + Routable> {
     do_scroll_restoration: bool,
     do_scroll_restoration: bool,
     history: History,
     history: History,

+ 1 - 3
packages/router/src/history/web_hash.rs

@@ -17,10 +17,8 @@ const INITIAL_URL: &str = "dioxus-router-core://initial_url.invalid/";
 ///
 ///
 /// Early web applications used the hash to store the current path because there was no other way
 /// Early web applications used the hash to store the current path because there was no other way
 /// for them to interact with the history without triggering a browser navigation, as the
 /// for them to interact with the history without triggering a browser navigation, as the
-/// [History API] did not yet exist. While this implementation could have been written that way, it
+/// [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) did not yet exist. While this implementation could have been written that way, it
 /// was not, because no browser supports WebAssembly without the [History API].
 /// was not, because no browser supports WebAssembly without the [History API].
-///
-/// [History API]: https://developer.mozilla.org/en-US/docs/Web/API/History_API
 pub struct WebHashHistory<R: Serialize + DeserializeOwned> {
 pub struct WebHashHistory<R: Serialize + DeserializeOwned> {
     do_scroll_restoration: bool,
     do_scroll_restoration: bool,
     history: History,
     history: History,

+ 18 - 27
packages/router/src/hooks/use_route.rs

@@ -1,17 +1,13 @@
 use dioxus::prelude::ScopeState;
 use dioxus::prelude::ScopeState;
 
 
-use crate::{routable::Routable, utils::use_router_internal::use_router_internal};
+use crate::prelude::*;
+use crate::utils::use_router_internal::use_router_internal;
 
 
 /// A hook that provides access to information about the current routing location.
 /// A hook that provides access to information about the current routing location.
 ///
 ///
 /// # Return values
 /// # Return values
-/// - [`RouterError::NotInsideRouter`], when the calling component is not nested within another
-///   component calling the [`use_router`] hook.
-/// - Otherwise [`Ok`].
-///
-/// # Important usage information
-/// Make sure to [`drop`] the returned [`RwLockReadGuard`] when done rendering. Otherwise the router
-/// will be frozen.
+/// - None, when not called inside a [`GenericRouter`] component.
+/// - Otherwise the current route.
 ///
 ///
 /// # Panic
 /// # Panic
 /// - When the calling component is not nested within another component calling the [`use_router`]
 /// - When the calling component is not nested within another component calling the [`use_router`]
@@ -20,28 +16,25 @@ use crate::{routable::Routable, utils::use_router_internal::use_router_internal}
 /// # Example
 /// # Example
 /// ```rust
 /// ```rust
 /// # use dioxus::prelude::*;
 /// # use dioxus::prelude::*;
+/// # use serde::{Deserialize, Serialize};
 /// # use dioxus_router::{history::*, prelude::*};
 /// # use dioxus_router::{history::*, prelude::*};
-/// fn App(cx: Scope) -> Element {
-///     use_router(
-///         &cx,
-///         &|| RouterConfiguration {
-///             synchronous: true, // asynchronicity not needed for doc test
-///             history: Box::new(MemoryHistory::with_initial_path("/some/path").unwrap()),
-///             ..Default::default()
-///         },
-///         &|| Segment::empty()
-///     );
 ///
 ///
+/// #[derive(Clone, Serialize, Deserialize, Routable)]
+/// enum Route {
+///     #[route("/")]
+///     Index {},
+/// }
+///
+/// fn App(cx: Scope) -> Element {
 ///     render! {
 ///     render! {
 ///         h1 { "App" }
 ///         h1 { "App" }
-///         Content { }
+///         Router {}
 ///     }
 ///     }
 /// }
 /// }
 ///
 ///
-/// fn Content(cx: Scope) -> Element {
-///     let state = use_route(&cx)?;
-///     let path = state.path.clone();
-///
+/// #[inline_props]
+/// fn Index(cx: Scope) -> Element {
+///     let path = use_route(&cx).unwrap();
 ///     render! {
 ///     render! {
 ///         h2 { "Current Path" }
 ///         h2 { "Current Path" }
 ///         p { "{path}" }
 ///         p { "{path}" }
@@ -50,11 +43,9 @@ use crate::{routable::Routable, utils::use_router_internal::use_router_internal}
 /// #
 /// #
 /// # let mut vdom = VirtualDom::new(App);
 /// # let mut vdom = VirtualDom::new(App);
 /// # let _ = vdom.rebuild();
 /// # let _ = vdom.rebuild();
-/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><h2>Current Path</h2><p>/some/path</p>")
+/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><h2>Current Path</h2><p>/</p>")
 /// ```
 /// ```
-///
-/// [`use_router`]: crate::hooks::use_router
-pub fn use_route<R: Routable + Clone>(cx: &ScopeState) -> Option<R> {
+pub fn use_generic_route<R: Routable + Clone>(cx: &ScopeState) -> Option<R> {
     match use_router_internal(cx) {
     match use_router_internal(cx) {
         Some(r) => Some(r.current()),
         Some(r) => Some(r.current()),
         None => {
         None => {

+ 44 - 1
packages/router/src/hooks/use_router.rs

@@ -5,7 +5,50 @@ use crate::{
     utils::use_router_internal::use_router_internal,
     utils::use_router_internal::use_router_internal,
 };
 };
 
 
-/// A hook that provides access to information about the router
+/// A hook that provides access to information about the router. The Router will define a version of this hook with an explicit type.
+///
+/// ```rust
+/// # use dioxus::prelude::*;
+/// # use dioxus_router::{history::*, prelude::*};
+/// # use serde::{Deserialize, Serialize};
+/// #[derive(Clone, Serialize, Deserialize, Routable)]
+/// enum Route {
+///     #[route("/")]
+///     Index {},
+///     #[route("/:id")]
+///     Dynamic { id: usize },
+/// }
+///
+/// fn App(cx: Scope) -> Element {
+///     render! {
+///         Router {}
+///     }
+/// }
+///
+/// #[inline_props]
+/// fn Index(cx: Scope) -> Element {
+///     let router = use_router(&cx);
+///
+///     render! {
+///         button {
+///             onclick: move |_| { router.push(Route::Dynamic { id: 1234 }); },
+///             "Go to /1234"
+///         }
+///     }
+/// }
+///
+/// #[inline_props]
+/// fn Dynamic(cx: Scope, id: usize) -> Element {
+///     render! {
+///         p {
+///             "Current ID: {id}"
+///         }
+///     }
+/// }
+///
+/// # let mut vdom = VirtualDom::new(App);
+/// # let _ = vdom.rebuild();
+/// ```
 pub fn use_generic_router<R: Routable + Clone>(cx: &ScopeState) -> &GenericRouterContext<R> {
 pub fn use_generic_router<R: Routable + Clone>(cx: &ScopeState) -> &GenericRouterContext<R> {
     use_router_internal(cx)
     use_router_internal(cx)
         .as_ref()
         .as_ref()

+ 30 - 6
packages/router/src/navigation.rs

@@ -12,18 +12,42 @@ pub enum NavigationTarget<R: Routable> {
     /// An internal path that the router can navigate to by itself.
     /// An internal path that the router can navigate to by itself.
     ///
     ///
     /// ```rust
     /// ```rust
-    /// # use dioxus_router_core::navigation::NavigationTarget;
-    /// let explicit = NavigationTarget::Internal(String::from("/internal"));
-    /// let implicit: NavigationTarget = "/internal".into();
+    /// # use dioxus::prelude::*;
+    /// # use dioxus_router::prelude::*;
+    /// # use dioxus_router::navigation::NavigationTarget;
+    /// # use serde::{Deserialize, Serialize};
+    /// # #[inline_props]
+    /// # fn Index(cx: Scope) -> Element {
+    /// #     todo!()
+    /// # }
+    /// #[derive(Clone, Serialize, Deserialize, Routable, PartialEq, Debug)]
+    /// enum Route {
+    ///     #[route("/")]
+    ///     Index {},
+    /// }
+    /// let explicit = NavigationTarget::Internal(Route::Index {});
+    /// let implicit: NavigationTarget::<Route> = "/".into();
     /// assert_eq!(explicit, implicit);
     /// assert_eq!(explicit, implicit);
     /// ```
     /// ```
     Internal(R),
     Internal(R),
     /// An external target that the router doesn't control.
     /// An external target that the router doesn't control.
     ///
     ///
     /// ```rust
     /// ```rust
-    /// # use dioxus_router_core::navigation::NavigationTarget;
-    /// let explicit = NavigationTarget::External(String::from("https://dioxuslabs.com/"));
-    /// let implicit: NavigationTarget = "https://dioxuslabs.com/".into();
+    /// # use dioxus::prelude::*;
+    /// # use dioxus_router::prelude::*;
+    /// # use dioxus_router::navigation::NavigationTarget;
+    /// # use serde::{Deserialize, Serialize};
+    /// # #[inline_props]
+    /// # fn Index(cx: Scope) -> Element {
+    /// #     todo!()
+    /// # }
+    /// #[derive(Clone, Serialize, Deserialize, Routable, PartialEq, Debug)]
+    /// enum Route {
+    ///     #[route("/")]
+    ///     Index {},
+    /// }
+    /// let explicit = NavigationTarget::<Route>::External(String::from("https://dioxuslabs.com/"));
+    /// let implicit: NavigationTarget::<Route> = "https://dioxuslabs.com/".into();
     /// assert_eq!(explicit, implicit);
     /// assert_eq!(explicit, implicit);
     /// ```
     /// ```
     External(String),
     External(String),

+ 6 - 1
packages/router/src/routable.rs

@@ -93,7 +93,12 @@ impl<I: std::iter::FromIterator<String>> FromRouteSegments for I {
     }
     }
 }
 }
 
 
-/// Something that can be routed to
+/// Something that can be:
+/// 1) Converted from a route
+/// 2) Converted to a route
+/// 3) Rendered as a component
+///
+/// This trait can be derived using the `#[derive(Routable)]` macro
 pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static {
 pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static {
     /// The error that can occur when parsing a route
     /// The error that can occur when parsing a route
     const SITE_MAP: &'static [SiteMapSegment];
     const SITE_MAP: &'static [SiteMapSegment];

+ 21 - 10
packages/router/src/router_cfg.rs

@@ -1,7 +1,9 @@
 use crate::contexts::router::RoutingCallback;
 use crate::contexts::router::RoutingCallback;
-use crate::history::{HistoryProvider, MemoryHistory};
+use crate::history::HistoryProvider;
+use crate::prelude::*;
 use crate::routable::Routable;
 use crate::routable::Routable;
 use dioxus::prelude::*;
 use dioxus::prelude::*;
+use serde::{de::DeserializeOwned, Serialize};
 
 
 use crate::prelude::default_errors::{
 use crate::prelude::default_errors::{
     FailureExternalNavigation, FailureNamedNavigation, FailureRedirectionLimit,
     FailureExternalNavigation, FailureNamedNavigation, FailureRedirectionLimit,
@@ -11,9 +13,20 @@ use crate::prelude::default_errors::{
 ///
 ///
 /// This implements [`Default`], so you can use it like this:
 /// This implements [`Default`], so you can use it like this:
 /// ```rust,no_run
 /// ```rust,no_run
-/// # use dioxus_router::prelude::RouterConfiguration;
+/// # use dioxus_router::prelude::*;
+/// # use serde::{Deserialize, Serialize};
+/// # use dioxus::prelude::*;
+/// # #[inline_props]
+/// # fn Index(cx: Scope) -> Element {
+/// #     todo!()
+/// # }
+/// #[derive(Clone, Serialize, Deserialize, Routable)]
+/// enum Route {
+///     #[route("/")]
+///     Index {},
+/// }
 /// let cfg = RouterConfiguration {
 /// let cfg = RouterConfiguration {
-///     synchronous: false,
+///     history: Box::<WebHistory<Route>>::default(),
 ///     ..Default::default()
 ///     ..Default::default()
 /// };
 /// };
 /// ```
 /// ```
@@ -22,19 +35,19 @@ pub struct RouterConfiguration<R: Routable> {
     ///
     ///
     /// Defaults to a router-internal component called `FailureExternalNavigation`. It is not part
     /// Defaults to a router-internal component called `FailureExternalNavigation`. It is not part
     /// of the public API. Do not confuse it with
     /// of the public API. Do not confuse it with
-    /// [`dioxus_router_core::prelude::FailureExternalNavigation`].
+    /// [`dioxus_router::prelude::FailureExternalNavigation`].
     pub failure_external_navigation: fn(Scope) -> Element,
     pub failure_external_navigation: fn(Scope) -> Element,
     /// A component to render when a named navigation fails.
     /// A component to render when a named navigation fails.
     ///
     ///
     /// Defaults to a router-internal component called `FailureNamedNavigation`. It is not part of
     /// Defaults to a router-internal component called `FailureNamedNavigation`. It is not part of
     /// the public API. Do not confuse it with
     /// the public API. Do not confuse it with
-    /// [`dioxus_router_core::prelude::FailureNamedNavigation`].
+    /// [`dioxus_router::prelude::FailureNamedNavigation`].
     pub failure_named_navigation: fn(Scope) -> Element,
     pub failure_named_navigation: fn(Scope) -> Element,
     /// A component to render when the redirect limit is reached.
     /// A component to render when the redirect limit is reached.
     ///
     ///
     /// Defaults to a router-internal component called `FailureRedirectionLimit`. It is not part of
     /// Defaults to a router-internal component called `FailureRedirectionLimit`. It is not part of
     /// the public API. Do not confuse it with
     /// the public API. Do not confuse it with
-    /// [`dioxus_router_core::prelude::FailureRedirectionLimit`].
+    /// [`dioxus_router::prelude::FailureRedirectionLimit`].
     pub failure_redirection_limit: fn(Scope) -> Element,
     pub failure_redirection_limit: fn(Scope) -> Element,
     /// The [`HistoryProvider`] the router should use.
     /// The [`HistoryProvider`] the router should use.
     ///
     ///
@@ -45,7 +58,7 @@ pub struct RouterConfiguration<R: Routable> {
     /// The callback is invoked after the routing is updated, but before components and hooks are
     /// The callback is invoked after the routing is updated, but before components and hooks are
     /// updated.
     /// updated.
     ///
     ///
-    /// If the callback returns a [`NavigationTarget`] the router will replace the current location
+    /// If the callback returns a [`dioxus_router::navigation::NavigationTarget`] the router will replace the current location
     /// with it. If no navigation failure was triggered, the router will then updated dependent
     /// with it. If no navigation failure was triggered, the router will then updated dependent
     /// components and hooks.
     /// components and hooks.
     ///
     ///
@@ -53,12 +66,10 @@ pub struct RouterConfiguration<R: Routable> {
     /// navigation failure occurs.
     /// navigation failure occurs.
     ///
     ///
     /// Defaults to [`None`].
     /// Defaults to [`None`].
-    ///
-    /// [`NavigationTarget`]: dioxus_router_core::navigation::NavigationTarget
     pub on_update: Option<RoutingCallback<R>>,
     pub on_update: Option<RoutingCallback<R>>,
 }
 }
 
 
-impl<R: Routable + Clone> Default for RouterConfiguration<R>
+impl<R: Routable + Clone + Serialize + DeserializeOwned> Default for RouterConfiguration<R>
 where
 where
     <R as std::str::FromStr>::Err: std::fmt::Display,
     <R as std::str::FromStr>::Err: std::fmt::Display,
 {
 {

+ 0 - 145
packages/router/src/utils/sitemap.rs

@@ -1,145 +0,0 @@
-use std::collections::BTreeMap;
-
-use urlencoding::encode;
-
-pub fn gen_sitemap<T: Clone>(seg: &Segment<T>, current: &str, map: &mut Vec<String>) {
-    for (p, r) in &seg.fixed {
-        let current = format!("{current}/{p}");
-        map.push(current.clone());
-        if let Some(n) = &r.nested {
-            gen_sitemap(n, &current, map);
-        }
-    }
-
-    for (_, r) in &seg.matching {
-        let current = format!("{current}/\\{}", r.key);
-        map.push(current.clone());
-        if let Some(n) = &r.nested {
-            gen_sitemap(n, &current, map);
-        }
-    }
-
-    if let Some(r) = &seg.catch_all {
-        let current = format!("{current}/\\{}", r.key);
-        map.push(current.clone());
-        if let Some(n) = &r.nested {
-            gen_sitemap(n, &current, map)
-        }
-    }
-}
-
-pub fn gen_parameter_sitemap<T: Clone>(
-    seg: &Segment<T>,
-    parameters: &BTreeMap<Name, Vec<String>>,
-    current: &str,
-    map: &mut Vec<String>,
-) {
-    for (p, r) in &seg.fixed {
-        let current = format!("{current}/{p}");
-        map.push(current.clone());
-        if let Some(n) = &r.nested {
-            gen_parameter_sitemap(n, parameters, &current, map);
-        }
-    }
-
-    for (m, r) in &seg.matching {
-        if let Some(rp) = parameters.get(&r.key) {
-            for p in rp {
-                if m.matches(p) {
-                    let current = format!("{current}/{}", encode(p).into_owned());
-                    map.push(current.clone());
-                    if let Some(n) = &r.nested {
-                        gen_parameter_sitemap(n, parameters, &current, map);
-                    }
-                }
-            }
-        }
-    }
-
-    if let Some(r) = &seg.catch_all {
-        if let Some(rp) = parameters.get(&r.key) {
-            for p in rp {
-                let current = format!("{current}/{}", encode(p).into_owned());
-                map.push(current.clone());
-                if let Some(n) = &r.nested {
-                    gen_parameter_sitemap(n, parameters, &current, map);
-                }
-            }
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::routes::{ParameterRoute, Route};
-
-    use super::*;
-
-    fn test_segment() -> Segment<&'static str> {
-        Segment::empty()
-            .fixed(
-                "fixed",
-                Route::empty().nested(Segment::empty().fixed("nested", Route::empty())),
-            )
-            .matching(
-                String::from("m1"),
-                ParameterRoute::empty::<u8>().nested(
-                    Segment::empty().matching(String::from("n2"), ParameterRoute::empty::<u16>()),
-                ),
-            )
-            .matching(String::from("no match"), ParameterRoute::empty::<u32>())
-            .matching(String::from("no parameter"), ParameterRoute::empty::<u64>())
-            .catch_all(
-                ParameterRoute::empty::<u32>()
-                    .nested(Segment::empty().catch_all(ParameterRoute::empty::<u16>())),
-            )
-    }
-
-    #[test]
-    fn sitemap() {
-        let mut result = Vec::new();
-        result.push(String::from("/"));
-        gen_sitemap(&test_segment(), "", &mut result);
-
-        assert_eq!(
-            result,
-            vec![
-                "/",
-                "/fixed",
-                "/fixed/nested",
-                "/\\u8",
-                "/\\u8/\\u16",
-                "/\\u32",
-                "/\\u64",
-                "/\\u32",
-                "/\\u32/\\u16"
-            ]
-        );
-    }
-
-    #[test]
-    fn sitemap_with_parameters() {
-        let mut parameters = BTreeMap::new();
-        parameters.insert(Name::of::<u8>(), vec!["m1".to_string(), "m2".to_string()]);
-        parameters.insert(Name::of::<u16>(), vec!["n1".to_string(), "n2".to_string()]);
-        parameters.insert(Name::of::<u32>(), vec!["catch all".to_string()]);
-
-        let mut result = Vec::new();
-        result.push(String::from("/"));
-        gen_parameter_sitemap(&test_segment(), &parameters, "", &mut result);
-
-        assert_eq!(
-            result,
-            vec![
-                "/",
-                "/fixed",
-                "/fixed/nested",
-                "/m1",
-                "/m1/n2",
-                "/catch%20all",
-                "/catch%20all/n1",
-                "/catch%20all/n2"
-            ]
-        );
-    }
-}

+ 2 - 4
packages/router/src/utils/use_router_internal.rs

@@ -1,6 +1,6 @@
 use dioxus::prelude::{ScopeId, ScopeState};
 use dioxus::prelude::{ScopeId, ScopeState};
 
 
-use crate::{contexts::router::GenericRouterContext, routable::Routable};
+use crate::{contexts::router::GenericRouterContext, prelude::*};
 
 
 /// A private hook to subscribe to the router.
 /// A private hook to subscribe to the router.
 ///
 ///
@@ -8,10 +8,8 @@ use crate::{contexts::router::GenericRouterContext, routable::Routable};
 /// single component, but not recommended. Multiple subscriptions will be discarded.
 /// single component, but not recommended. Multiple subscriptions will be discarded.
 ///
 ///
 /// # Return values
 /// # Return values
-/// - [`None`], when the current component isn't a descendant of a [`use_router`] component.
+/// - [`None`], when the current component isn't a descendant of a [`GenericRouter`] component.
 /// - Otherwise [`Some`].
 /// - Otherwise [`Some`].
-///
-/// [`use_router`]: crate::hooks::use_router
 pub(crate) fn use_router_internal<R: Routable>(
 pub(crate) fn use_router_internal<R: Routable>(
     cx: &ScopeState,
     cx: &ScopeState,
 ) -> &Option<GenericRouterContext<R>> {
 ) -> &Option<GenericRouterContext<R>> {

+ 12 - 12
packages/router/tests/via_ssr/link.rs

@@ -1,10 +1,10 @@
 #![allow(non_snake_case)]
 #![allow(non_snake_case)]
-
 use dioxus::prelude::*;
 use dioxus::prelude::*;
 use dioxus_router::prelude::*;
 use dioxus_router::prelude::*;
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use std::str::FromStr;
 use std::str::FromStr;
 
 
-fn prepare<R: Routable>() -> String
+fn prepare<R: Routable + Serialize + DeserializeOwned>() -> String
 where
 where
     <R as FromStr>::Err: std::fmt::Display,
     <R as FromStr>::Err: std::fmt::Display,
 {
 {
@@ -28,7 +28,7 @@ where
         }
         }
     }
     }
 
 
-    fn App<R: Routable>(cx: Scope<AppProps<R>>) -> Element
+    fn App<R: Routable + Serialize + DeserializeOwned>(cx: Scope<AppProps<R>>) -> Element
     where
     where
         <R as FromStr>::Err: std::fmt::Display,
         <R as FromStr>::Err: std::fmt::Display,
     {
     {
@@ -46,7 +46,7 @@ where
 
 
 #[test]
 #[test]
 fn href_internal() {
 fn href_internal() {
-    #[derive(Routable, Clone)]
+    #[derive(Routable, Clone, Serialize, Deserialize)]
     enum Route {
     enum Route {
         #[route("/")]
         #[route("/")]
         Root {},
         Root {},
@@ -84,7 +84,7 @@ fn href_internal() {
 
 
 #[test]
 #[test]
 fn href_external() {
 fn href_external() {
-    #[derive(Routable, Clone)]
+    #[derive(Routable, Clone, Serialize, Deserialize)]
     enum Route {
     enum Route {
         #[route("/")]
         #[route("/")]
         Root {},
         Root {},
@@ -122,7 +122,7 @@ fn href_external() {
 
 
 #[test]
 #[test]
 fn with_class() {
 fn with_class() {
-    #[derive(Routable, Clone)]
+    #[derive(Routable, Clone, Serialize, Deserialize)]
     enum Route {
     enum Route {
         #[route("/")]
         #[route("/")]
         Root {},
         Root {},
@@ -161,7 +161,7 @@ fn with_class() {
 
 
 #[test]
 #[test]
 fn with_active_class_active() {
 fn with_active_class_active() {
-    #[derive(Routable, Clone)]
+    #[derive(Routable, Clone, Serialize, Deserialize)]
     enum Route {
     enum Route {
         #[route("/")]
         #[route("/")]
         Root {},
         Root {},
@@ -194,7 +194,7 @@ fn with_active_class_active() {
 
 
 #[test]
 #[test]
 fn with_active_class_inactive() {
 fn with_active_class_inactive() {
-    #[derive(Routable, Clone)]
+    #[derive(Routable, Clone, Serialize, Deserialize)]
     enum Route {
     enum Route {
         #[route("/")]
         #[route("/")]
         Root {},
         Root {},
@@ -234,7 +234,7 @@ fn with_active_class_inactive() {
 
 
 #[test]
 #[test]
 fn with_id() {
 fn with_id() {
-    #[derive(Routable, Clone)]
+    #[derive(Routable, Clone, Serialize, Deserialize)]
     enum Route {
     enum Route {
         #[route("/")]
         #[route("/")]
         Root {},
         Root {},
@@ -273,7 +273,7 @@ fn with_id() {
 
 
 #[test]
 #[test]
 fn with_new_tab() {
 fn with_new_tab() {
-    #[derive(Routable, Clone)]
+    #[derive(Routable, Clone, Serialize, Deserialize)]
     enum Route {
     enum Route {
         #[route("/")]
         #[route("/")]
         Root {},
         Root {},
@@ -312,7 +312,7 @@ fn with_new_tab() {
 
 
 #[test]
 #[test]
 fn with_new_tab_external() {
 fn with_new_tab_external() {
-    #[derive(Routable, Clone)]
+    #[derive(Routable, Clone, Serialize, Deserialize)]
     enum Route {
     enum Route {
         #[route("/")]
         #[route("/")]
         Root {},
         Root {},
@@ -344,7 +344,7 @@ fn with_new_tab_external() {
 
 
 #[test]
 #[test]
 fn with_rel() {
 fn with_rel() {
-    #[derive(Routable, Clone)]
+    #[derive(Routable, Clone, Serialize, Deserialize)]
     enum Route {
     enum Route {
         #[route("/")]
         #[route("/")]
         Root {},
         Root {},

+ 2 - 1
packages/router/tests/via_ssr/outlet.rs

@@ -2,13 +2,14 @@
 
 
 use dioxus::prelude::*;
 use dioxus::prelude::*;
 use dioxus_router::{history::MemoryHistory, prelude::*};
 use dioxus_router::{history::MemoryHistory, prelude::*};
+use serde::{Deserialize, Serialize};
 
 
 fn prepare(path: impl Into<String>) -> VirtualDom {
 fn prepare(path: impl Into<String>) -> VirtualDom {
     let mut vdom = VirtualDom::new_with_props(App, AppProps { path: path.into() });
     let mut vdom = VirtualDom::new_with_props(App, AppProps { path: path.into() });
     let _ = vdom.rebuild();
     let _ = vdom.rebuild();
     return vdom;
     return vdom;
 
 
-    #[derive(Routable, Clone)]
+    #[derive(Routable, Clone, Serialize, Deserialize)]
     #[rustfmt::skip]
     #[rustfmt::skip]
     enum Route {
     enum Route {
         #[route("/")]
         #[route("/")]