Forráskód Böngészése

update doc examples

Evan Almloff 2 éve
szülő
commit
09cabe4e8b

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

@@ -53,8 +53,20 @@ pub fn routable(input: TokenStream) -> TokenStream {
             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

+ 32 - 21
packages/router/README.md

@@ -7,13 +7,10 @@
 
 [crates-badge]: https://img.shields.io/crates/v/dioxus-router.svg
 [crates-url]: https://crates.io/crates/dioxus-router
-
 [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
 [mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE
-
 [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
-
 [discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square
 [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
 expressiveness.
 
-```rust ,no_run
+```rust, no_run
+#![allow(non_snake_case)]
+
 use dioxus::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! {
-        Outlet { }
+        Router { }
     }
 }
 
+#[inline_props]
 fn Index(cx: Scope) -> Element {
     render! {
         h1 { "Index" }
         Link {
-            target: "/blog",
+            target: Route::BlogList {},
             "Go to the blog"
         }
     }
 }
 
+#[inline_props]
 fn Blog(cx: Scope) -> Element {
     render! {
         h1 { "Blog" }
@@ -67,21 +75,23 @@ fn Blog(cx: Scope) -> Element {
     }
 }
 
+#[inline_props]
 fn BlogList(cx: Scope) -> Element {
     render! {
         h2 { "List of blog posts" }
         Link {
-            target: "/blog/1",
+            target: Route::BlogPost { blog_id: 0 },
             "Blog post 1"
         }
         Link {
-            target: "/blog/1",
+            target: Route::BlogPost { blog_id: 1 },
             "Blog post 2"
         }
     }
 }
 
-fn BlogPost(cx: Scope) -> Element {
+#[inline_props]
+fn BlogPost(cx: Scope, blog_id: usize) -> Element {
     render! {
         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!
 
 ## License
+
 This project is licensed under the [MIT license].
 
 [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::{
-    components::GenericLink, hooks::use_route, navigation::NavigationTarget, routable::Routable,
+    components::GenericLink, hooks::use_generic_route, navigation::NavigationTarget,
+    routable::Routable,
 };
 use dioxus::prelude::*;
 
 #[allow(non_snake_case)]
 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, \
             since it is not exposed",
     );
@@ -38,7 +39,9 @@ pub fn FailureNamedNavigation<R: Routable + Clone>(cx: Scope) -> Element {
             "there is no guarantee."
         }
         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."
         }
     }
@@ -58,7 +61,9 @@ pub fn FailureRedirectionLimit<R: Routable + Clone>(cx: Scope) -> Element {
             "there is no guarantee."
         }
         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."
         }
     }

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

@@ -1,41 +1,44 @@
 use dioxus::prelude::*;
 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`].
 #[derive(Debug, Props)]
-pub struct HistoryButtonProps<'a> {
+pub struct GenericHistoryButtonProps<'a> {
     /// The children to render within the generated HTML button tag.
     pub children: Element<'a>,
 }
 
 /// 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.
 ///
-/// [`use_router`]: crate::hooks::use_router
-///
 /// # 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.
 ///
 /// # Example
 /// ```rust
 /// # use dioxus::prelude::*;
 /// # use dioxus_router::prelude::*;
+/// # use serde::{Deserialize, Serialize};
+/// #[derive(Clone, Serialize, Deserialize, Routable)]
+/// enum Route {
+///     #[route("/")]
+///     Index {},
+/// }
+///
 /// 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! {
 ///         GoBackButton {
 ///             "go back"
@@ -51,8 +54,10 @@ pub struct HistoryButtonProps<'a> {
 /// # );
 /// ```
 #[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
     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.
 ///
-/// 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.
 ///
-/// [`use_router`]: crate::hooks::use_router
-///
 /// # 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.
 ///
 /// # Example
 /// ```rust
 /// # use dioxus::prelude::*;
 /// # use dioxus_router::prelude::*;
+/// # use serde::{Deserialize, Serialize};
+/// #[derive(Clone, Serialize, Deserialize, Routable)]
+/// enum Route {
+///     #[route("/")]
+///     Index {},
+/// }
+///
 /// 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! {
 ///         GoForwardButton {
 ///             "go forward"
@@ -120,8 +128,10 @@ pub fn GoBackButton<'a, R: Routable>(cx: Scope<'a, HistoryButtonProps<'a>>) -> E
 /// # );
 /// ```
 #[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
     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 crate::navigation::NavigationTarget;
-use crate::routable::Routable;
+use crate::prelude::*;
 use crate::utils::use_router_internal::use_router_internal;
 
 /// The properties for a [`Link`].
@@ -63,56 +63,54 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
 
 /// 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.
 ///
-/// 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.
 ///
-/// [`use_router`]: crate::hooks::use_router
-///
 /// # 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
-/// - 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.
 ///
 /// # Example
 /// ```rust
 /// # use dioxus::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! {
-///         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.
 ///
-/// 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
 /// of the active route that is __exactly as deep__.
 ///
-/// [`use_router`]: crate::hooks::use_router
-///
 /// # Panic
 /// - When the [`Outlet`] is not nested within another component calling the [`use_router`] hook,
 ///   but only in debug builds.
@@ -17,32 +15,61 @@ use dioxus::prelude::*;
 /// # Example
 /// ```rust
 /// # use dioxus::prelude::*;
+/// # use serde::{Deserialize, Serialize};
 /// # 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! {
 ///         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! {
-///         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 _ = 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 {
     OutletContext::render::<R>(cx)

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

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

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

@@ -1,6 +1,6 @@
 use dioxus::prelude::*;
 
-use crate::{hooks::use_route, routable::Routable};
+use crate::{hooks::use_generic_route, routable::Routable};
 
 #[derive(Clone)]
 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")
             .render(cx, current_level)
     }

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

@@ -149,7 +149,8 @@ where
     /// Push a new location.
     ///
     /// 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();
         match target {
             NavigationTarget::Internal(p) => state.history.push(p),
@@ -163,7 +164,8 @@ where
     /// Replace the current location.
     ///
     /// 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();
         match target {
             NavigationTarget::Internal(p) => state.history.replace(p),
@@ -264,7 +266,7 @@ where
 //         struct TestHistory {}
 
 //         impl HistoryProvider for TestHistory {
-//             fn current_path(&self) -> String {
+//             fn current_route(&self) -> String {
 //                 todo!()
 //             }
 
@@ -758,7 +760,7 @@ where
 //         struct TestHistory {}
 
 //         impl HistoryProvider for TestHistory {
-//             fn current_path(&self) -> String {
+//             fn current_route(&self) -> String {
 //                 String::from("/")
 //             }
 

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

@@ -18,16 +18,31 @@ where
     /// Create a [`MemoryHistory`] starting at `path`.
     ///
     /// ```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);
     /// ```
-    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 {
-            current: R::from_str(&path)?,
+            current: R::from_str(path)?,
             ..Default::default()
         })
     }
@@ -83,148 +98,3 @@ impl<R: Routable> HistoryProvider<R> for MemoryHistory<R> {
         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.
 ///
 /// 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.
 /// 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.
     ///
     /// ```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]
     fn current_route(&self) -> R;
@@ -68,11 +82,21 @@ pub trait HistoryProvider<R: Routable> {
     /// If a [`HistoryProvider`] cannot know this, it should return [`true`].
     ///
     /// ```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);
     ///
-    /// history.push(String::from("/some-other-page"));
+    /// history.push(Route::Index {});
     /// assert_eq!(history.can_go_back(), true);
     /// ```
     #[must_use]
@@ -86,18 +110,32 @@ pub trait HistoryProvider<R: Routable> {
     /// might be called, even if `can_go_back` returns [`false`].
     ///
     /// ```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();
-    /// 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();
-    /// assert_eq!(history.current_path(), "/");
+    /// assert_eq!(history.current_route().to_string(), "/");
     /// ```
     fn go_back(&mut self);
 
@@ -106,11 +144,25 @@ pub trait HistoryProvider<R: Routable> {
     /// If a [`HistoryProvider`] cannot know this, it should return [`true`].
     ///
     /// ```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);
     ///
-    /// history.push(String::from("/some-other-page"));
+    /// history.push(Route::OtherPage {});
     /// assert_eq!(history.can_go_forward(), false);
     ///
     /// history.go_back();
@@ -127,16 +179,30 @@ pub trait HistoryProvider<R: Routable> {
     /// might be called, even if `can_go_forward` returns [`false`].
     ///
     /// ```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();
-    /// assert_eq!(history.current_path(), "/");
+    /// assert_eq!(history.current_route(), Route::Index {});
     ///
     /// history.go_forward();
-    /// assert_eq!(history.current_path(), "/some-other-page");
+    /// assert_eq!(history.current_route(), Route::OtherPage {});
     /// ```
     fn go_forward(&mut self);
 
@@ -148,12 +214,26 @@ pub trait HistoryProvider<R: Routable> {
     /// 3. Clear the navigation future.
     ///
     /// ```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());
     /// ```
     fn push(&mut self, route: R);
@@ -165,12 +245,26 @@ pub trait HistoryProvider<R: Routable> {
     /// untouched.
     ///
     /// ```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());
     /// ```
     fn replace(&mut self, path: R);

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

@@ -28,7 +28,7 @@ struct WebHistoryState<R> {
     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
 /// 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
 /// 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> {
     do_scroll_restoration: bool,
     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
 /// 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].
-///
-/// [History API]: https://developer.mozilla.org/en-US/docs/Web/API/History_API
 pub struct WebHashHistory<R: Serialize + DeserializeOwned> {
     do_scroll_restoration: bool,
     history: History,

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

@@ -1,17 +1,13 @@
 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.
 ///
 /// # 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
 /// - 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
 /// ```rust
 /// # use dioxus::prelude::*;
+/// # use serde::{Deserialize, Serialize};
 /// # 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! {
 ///         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! {
 ///         h2 { "Current 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 _ = 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) {
         Some(r) => Some(r.current()),
         None => {

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

@@ -5,7 +5,50 @@ use crate::{
     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> {
     use_router_internal(cx)
         .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.
     ///
     /// ```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);
     /// ```
     Internal(R),
     /// An external target that the router doesn't control.
     ///
     /// ```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);
     /// ```
     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 {
     /// The error that can occur when parsing a route
     const SITE_MAP: &'static [SiteMapSegment];

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

@@ -1,7 +1,9 @@
 use crate::contexts::router::RoutingCallback;
-use crate::history::{HistoryProvider, MemoryHistory};
+use crate::history::HistoryProvider;
+use crate::prelude::*;
 use crate::routable::Routable;
 use dioxus::prelude::*;
+use serde::{de::DeserializeOwned, Serialize};
 
 use crate::prelude::default_errors::{
     FailureExternalNavigation, FailureNamedNavigation, FailureRedirectionLimit,
@@ -11,9 +13,20 @@ use crate::prelude::default_errors::{
 ///
 /// This implements [`Default`], so you can use it like this:
 /// ```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 {
-///     synchronous: false,
+///     history: Box::<WebHistory<Route>>::default(),
 ///     ..Default::default()
 /// };
 /// ```
@@ -22,19 +35,19 @@ pub struct RouterConfiguration<R: Routable> {
     ///
     /// Defaults to a router-internal component called `FailureExternalNavigation`. It is not part
     /// 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,
     /// A component to render when a named navigation fails.
     ///
     /// Defaults to a router-internal component called `FailureNamedNavigation`. It is not part of
     /// the public API. Do not confuse it with
-    /// [`dioxus_router_core::prelude::FailureNamedNavigation`].
+    /// [`dioxus_router::prelude::FailureNamedNavigation`].
     pub failure_named_navigation: fn(Scope) -> Element,
     /// A component to render when the redirect limit is reached.
     ///
     /// Defaults to a router-internal component called `FailureRedirectionLimit`. It is not part of
     /// the public API. Do not confuse it with
-    /// [`dioxus_router_core::prelude::FailureRedirectionLimit`].
+    /// [`dioxus_router::prelude::FailureRedirectionLimit`].
     pub failure_redirection_limit: fn(Scope) -> Element,
     /// 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
     /// 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
     /// components and hooks.
     ///
@@ -53,12 +66,10 @@ pub struct RouterConfiguration<R: Routable> {
     /// navigation failure occurs.
     ///
     /// Defaults to [`None`].
-    ///
-    /// [`NavigationTarget`]: dioxus_router_core::navigation::NavigationTarget
     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
     <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 crate::{contexts::router::GenericRouterContext, routable::Routable};
+use crate::{contexts::router::GenericRouterContext, prelude::*};
 
 /// 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.
 ///
 /// # 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`].
-///
-/// [`use_router`]: crate::hooks::use_router
 pub(crate) fn use_router_internal<R: Routable>(
     cx: &ScopeState,
 ) -> &Option<GenericRouterContext<R>> {

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

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

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

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