Browse Source

it almost lives?

Evan Almloff 1 year ago
parent
commit
5f0dd3af3e
43 changed files with 502 additions and 240 deletions
  1. 3 3
      docs/guide/src/en/interactivity/router.md
  2. 3 3
      docs/guide/src/pt-br/interactivity/router.md
  3. 1 1
      docs/posts/release-0-2-0.md
  4. 1 1
      docs/router/examples/catch_all.rs
  5. 3 3
      docs/router/examples/dynamic_route.rs
  6. 1 1
      docs/router/examples/external_link.rs
  7. 1 1
      docs/router/examples/first_route.rs
  8. 3 3
      docs/router/examples/full_example.rs
  9. 1 1
      docs/router/examples/history_provider.rs
  10. 2 2
      docs/router/examples/links.rs
  11. 1 1
      docs/router/examples/navigator.rs
  12. 2 2
      docs/router/examples/nested_routes.rs
  13. 2 2
      docs/router/examples/outlet.rs
  14. 1 1
      docs/router/examples/router_cfg.rs
  15. 1 1
      docs/router/examples/routing_update.rs
  16. 1 1
      examples/crm.rs
  17. 2 2
      examples/flat_router.rs
  18. 2 2
      examples/link.rs
  19. 3 3
      examples/router.rs
  20. 2 2
      examples/simple_desktop.rs
  21. 3 3
      packages/fullstack/src/router.rs
  22. 0 33
      packages/router-macro/src/lib.rs
  23. 2 2
      packages/router/README.md
  24. 1 1
      packages/router/benches/incremental.rs
  25. 3 3
      packages/router/examples/simple_routes.rs
  26. 3 3
      packages/router/src/components/default_errors.rs
  27. 15 19
      packages/router/src/components/history_buttons.rs
  28. 78 22
      packages/router/src/components/link.rs
  29. 6 6
      packages/router/src/components/outlet.rs
  30. 17 21
      packages/router/src/components/router.rs
  31. 5 5
      packages/router/src/contexts/navigator.rs
  32. 2 2
      packages/router/src/contexts/outlet.rs
  33. 153 42
      packages/router/src/contexts/router.rs
  34. 141 1
      packages/router/src/history/mod.rs
  35. 4 7
      packages/router/src/hooks/use_navigator.rs
  36. 4 4
      packages/router/src/hooks/use_route.rs
  37. 2 5
      packages/router/src/hooks/use_router.rs
  38. 1 1
      packages/router/src/incremental.rs
  39. 1 1
      packages/router/src/navigation.rs
  40. 12 8
      packages/router/src/router_cfg.rs
  41. 7 9
      packages/router/src/utils/use_router_internal.rs
  42. 3 3
      packages/router/tests/via_ssr/link.rs
  43. 3 3
      packages/router/tests/via_ssr/outlet.rs

+ 3 - 3
docs/guide/src/en/interactivity/router.md

@@ -26,7 +26,7 @@ Unlike other routers in the Rust ecosystem, our router is built declaratively. T
 ```rust, no_run
 rsx!{
     // All of our routes will be rendered inside this Router component
-    Router {
+    Router::<Route> {
         // if the current location is "/home", render the Home component
         Route { to: "/home", Home {} }
         // if the current location is "/blog", render the Blog component
@@ -43,7 +43,7 @@ We can fix this one of two ways:
 
 ```rust, no_run
 rsx!{
-    Router {
+    Router::<Route> {
         Route { to: "/home", Home {} }
         Route { to: "/blog", Blog {} }
         //  if the current location doesn't match any of the above routes, render the NotFound component
@@ -56,7 +56,7 @@ rsx!{
 
 ```rust, no_run
 rsx!{
-    Router {
+    Router::<Route> {
         Route { to: "/home", Home {} }
         Route { to: "/blog", Blog {} }
         //  if the current location doesn't match any of the above routes, redirect to "/home"

+ 3 - 3
docs/guide/src/pt-br/interactivity/router.md

@@ -28,7 +28,7 @@ Ao contrário de outros roteadores no ecossistema Rust, nosso roteador é constr
 
 ```rust, no_run
 rsx!{
-    Router {
+    Router::<Route> {
         Route { to: "/home", Home {} }
         Route { to: "/blog", Blog {} }
     }
@@ -43,7 +43,7 @@ Podemos corrigir isso de duas maneiras:
 
 ```rust, no_run
 rsx!{
-    Router {
+    Router::<Route> {
         Route { to: "/home", Home {} }
         Route { to: "/blog", Blog {} }
         Route { to: "", NotFound {} }
@@ -55,7 +55,7 @@ rsx!{
 
 ```rust, no_run
 rsx!{
-    Router {
+    Router::<Route> {
         Route { to: "/home", Home {} }
         Route { to: "/blog", Blog {} }
         Redirect { from: "", to: "/home" }

+ 1 - 1
docs/posts/release-0-2-0.md

@@ -72,7 +72,7 @@ Apps with routers are _really_ simple now. It's easy to compose the "Router", a
 ```rust, no_run
 fn app(cx: Scope) -> Element {
     cx.render(rsx! {
-        Router {
+        Router::<Route> {
             onchange: move |_| log::info!("Route changed!"),
             ul {
                 Link { to: "/",  li { "Go home!" } }

+ 1 - 1
docs/router/examples/catch_all.rs

@@ -17,7 +17,7 @@ enum Route {
 #[inline_props]
 fn App(cx: Scope) -> Element {
     render! {
-        Router {}
+        Router::<Route> {}
     }
 }
 // ANCHOR_END: app

+ 3 - 3
docs/router/examples/dynamic_route.rs

@@ -35,7 +35,7 @@ enum Route {
 
 fn App(cx: Scope) -> Element {
     render! {
-        Router {}
+        Router::<Route> {}
     }
 }
 
@@ -48,7 +48,7 @@ fn NavBar(cx: Scope) -> Element {
                 li { Link { to: Route::BlogList {}, "Blog" } }
             }
         }
-        Outlet {}
+        Outlet::<Route> {}
     }
 }
 
@@ -64,7 +64,7 @@ fn Home(cx: Scope) -> Element {
 fn Blog(cx: Scope) -> Element {
     render! {
         h1 { "Blog" }
-        Outlet {}
+        Outlet::<Route> {}
     }
 }
 // ANCHOR_END: blog

+ 1 - 1
docs/router/examples/external_link.rs

@@ -20,7 +20,7 @@ fn main() {}
 fn GoToDioxus(cx: Scope) -> Element {
     render! {
         Link {
-            to: NavigationTarget::External("https://dioxuslabs.com".into()),
+            to: "https://dioxuslabs.com",
             "ExternalTarget target"
         }
     }

+ 1 - 1
docs/router/examples/first_route.rs

@@ -19,7 +19,7 @@ enum Route {
 #[inline_props]
 fn App(cx: Scope) -> Element {
     render! {
-        Router {}
+        Router::<Route> {}
     }
 }
 // ANCHOR_END: app

+ 3 - 3
docs/router/examples/full_example.rs

@@ -39,7 +39,7 @@ enum Route {
 
 fn App(cx: Scope) -> Element {
     render! {
-        Router {}
+        Router::<Route> {}
     }
 }
 
@@ -52,7 +52,7 @@ fn NavBar(cx: Scope) -> Element {
                 li { Link { to: Route::BlogList {}, "Blog" } }
             }
         }
-        Outlet {}
+        Outlet::<Route> {}
     }
 }
 
@@ -67,7 +67,7 @@ fn Home(cx: Scope) -> Element {
 fn Blog(cx: Scope) -> Element {
     render! {
         h1 { "Blog" }
-        Outlet {}
+        Outlet::<Route> {}
     }
 }
 

+ 1 - 1
docs/router/examples/history_provider.rs

@@ -12,7 +12,7 @@ enum Route {
 #[inline_props]
 fn App(cx: Scope) -> Element {
     render! {
-        Router {
+        Router::<Route> {
             config: || RouterConfig::default().history(WebHistory::default())
         }
     }

+ 2 - 2
docs/router/examples/links.rs

@@ -32,7 +32,7 @@ fn NavBar(cx: Scope) -> Element {
                 }
             }
         }
-        Outlet {}
+        Outlet::<Route> {}
     }
 }
 // ANCHOR_END: nav
@@ -41,7 +41,7 @@ fn NavBar(cx: Scope) -> Element {
 #[inline_props]
 fn App(cx: Scope) -> Element {
     render! {
-        Router {}
+        Router::<Route> {}
     }
 }
 // ANCHOR_END: app

+ 1 - 1
docs/router/examples/navigator.rs

@@ -14,7 +14,7 @@ enum Route {
 #[inline_props]
 fn App(cx: Scope) -> Element {
     render! {
-        Router {}
+        Router::<Route> {}
     }
 }
 

+ 2 - 2
docs/router/examples/nested_routes.rs

@@ -26,7 +26,7 @@ fn NavBar(cx: Scope) -> Element {
             }
         }
         // The Outlet component will render child routes (In this case just the Home component) inside the Outlet component
-        Outlet {}
+        Outlet::<Route> {}
     }
 }
 // ANCHOR_END: nav
@@ -35,7 +35,7 @@ fn NavBar(cx: Scope) -> Element {
 #[inline_props]
 fn App(cx: Scope) -> Element {
     render! {
-        Router {}
+        Router::<Route> {}
     }
 }
 // ANCHOR_END: app

+ 2 - 2
docs/router/examples/outlet.rs

@@ -16,7 +16,7 @@ fn Wrapper(cx: Scope) -> Element {
     render! {
         header { "header" }
         // The index route will be rendered here
-        Outlet { }
+        Outlet::<Route> { }
         footer { "footer" }
     }
 }
@@ -31,7 +31,7 @@ fn Index(cx: Scope) -> Element {
 
 fn App(cx: Scope) -> Element {
     render! {
-        Router {}
+        Router::<Route> {}
     }
 }
 

+ 1 - 1
docs/router/examples/router_cfg.rs

@@ -18,7 +18,7 @@ enum Route {
 #[inline_props]
 fn App(cx: Scope) -> Element {
     render! {
-        Router {
+        Router::<Route> {
             config: || RouterConfig::default().history(WebHistory::default())
         }
     }

+ 1 - 1
docs/router/examples/routing_update.rs

@@ -27,7 +27,7 @@ fn Index(cx: Scope) -> Element {
 
 fn app(cx: Scope) -> Element {
     render! {
-        Router {
+        Router::<Route> {
             config: || RouterConfig::default().on_update(|state|{
                 (state.current() == Route::Index {}).then_some(
                     NavigationTarget::Internal(Route::Home {})

+ 1 - 1
examples/crm.rs

@@ -46,7 +46,7 @@ fn App(cx: Scope) -> Element {
 
         h1 { "Dioxus CRM Example" }
 
-        Router {}
+        Router::<Route> {}
     }
 }
 

+ 2 - 2
examples/flat_router.rs

@@ -18,7 +18,7 @@ fn main() {
 
 fn app(cx: Scope) -> Element {
     render! {
-        Router {}
+        Router::<Route> {}
     }
 }
 
@@ -40,7 +40,7 @@ enum Route {
 fn Footer(cx: Scope) -> Element {
     render! {
         div {
-            Outlet { }
+            Outlet::<Route> { }
 
             p {
                 "----"

+ 2 - 2
examples/link.rs

@@ -23,7 +23,7 @@ fn app(cx: Scope) -> Element {
             }
         }
         div {
-            Router {}
+            Router::<Route> {}
         }
     ))
 }
@@ -46,7 +46,7 @@ fn Header(cx: Scope) -> Element {
             li { Link { to: Route::Home {}, "home" } }
             li { Link { to: Route::Settings {}, "settings" } }
         }
-        Outlet {}
+        Outlet::<Route> {}
     }
 }
 

+ 3 - 3
examples/router.rs

@@ -39,7 +39,7 @@ enum Route {
 
 fn App(cx: Scope) -> Element {
     render! {
-        Router {}
+        Router::<Route> {}
     }
 }
 
@@ -52,7 +52,7 @@ fn NavBar(cx: Scope) -> Element {
                 li { Link { to: Route::BlogList {}, "Blog" } }
             }
         }
-        Outlet {}
+        Outlet::<Route> {}
     }
 }
 
@@ -67,7 +67,7 @@ fn Home(cx: Scope) -> Element {
 fn Blog(cx: Scope) -> Element {
     render! {
         h1 { "Blog" }
-        Outlet {}
+        Outlet::<Route> {}
     }
 }
 

+ 2 - 2
examples/simple_desktop.rs

@@ -14,7 +14,7 @@ fn main() {
 
 fn app(cx: Scope) -> Element {
     render! {
-        Router {}
+        Router::<Route> {}
     }
 }
 
@@ -47,7 +47,7 @@ fn NavBar(cx: Scope) -> Element {
             li { Link { to: Route::BlogPost { post: "bill".into() }, "bills' blog" } }
             li { Link { to: Route::BlogPost { post: "james".into() }, "james amazing' blog" } }
         }
-        Outlet {}
+        Outlet::<Route> {}
     }
 }
 

+ 3 - 3
packages/fullstack/src/router.rs

@@ -16,7 +16,7 @@ where
 
     let cfg = *cx.props;
     render! {
-        dioxus_router::prelude::GenericRouter::<R> {
+        dioxus_router::prelude::Router::<R> {
             config: move || {
                 RouterConfig::default()
                     .failure_external_navigation(cfg.failure_external_navigation)
@@ -54,7 +54,7 @@ where
     R: dioxus_router::prelude::Routable,
     <R as std::str::FromStr>::Err: std::fmt::Display,
 {
-    dioxus_router::prelude::FailureExternalNavigation::<R>
+    dioxus_router::prelude::FailureExternalNavigation
 }
 
 /// The configeration for the router
@@ -96,7 +96,7 @@ where
 {
     fn default() -> Self {
         Self {
-            failure_external_navigation: dioxus_router::prelude::FailureExternalNavigation::<R>,
+            failure_external_navigation: dioxus_router::prelude::FailureExternalNavigation,
             scroll_restoration: true,
             phantom: std::marker::PhantomData,
         }

+ 0 - 33
packages/router-macro/src/lib.rs

@@ -211,38 +211,8 @@ pub fn routable(input: TokenStream) -> TokenStream {
     let parse_impl = route_enum.parse_impl();
     let display_impl = route_enum.impl_display();
     let routable_impl = route_enum.routable_impl();
-    let name = &route_enum.name;
-    let vis = &route_enum.vis;
 
     quote! {
-        #vis fn Outlet(cx: dioxus::prelude::Scope) -> dioxus::prelude::Element {
-            dioxus_router::prelude::GenericOutlet::<#name>(cx)
-        }
-
-        #vis fn Router(cx: dioxus::prelude::Scope<dioxus_router::prelude::GenericRouterProps<#name>>) -> dioxus::prelude::Element {
-            dioxus_router::prelude::GenericRouter(cx)
-        }
-
-        #vis fn Link<'a>(cx: dioxus::prelude::Scope<'a, dioxus_router::prelude::GenericLinkProps<'a, #name>>) -> dioxus::prelude::Element<'a> {
-            dioxus_router::prelude::GenericLink(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_route(cx: &dioxus::prelude::ScopeState) -> Option<#name> {
-            dioxus_router::prelude::use_generic_route(cx)
-        }
-
-        #vis fn use_navigator(cx: &dioxus::prelude::ScopeState) -> &dioxus_router::prelude::GenericNavigator<#name> {
-            dioxus_router::prelude::use_generic_navigator(cx)
-        }
-
         #error_type
 
         #display_impl
@@ -255,7 +225,6 @@ pub fn routable(input: TokenStream) -> TokenStream {
 }
 
 struct RouteEnum {
-    vis: syn::Visibility,
     name: Ident,
     redirects: Vec<Redirect>,
     routes: Vec<Route>,
@@ -267,7 +236,6 @@ struct RouteEnum {
 impl RouteEnum {
     fn parse(data: syn::ItemEnum) -> syn::Result<Self> {
         let name = &data.ident;
-        let vis = &data.vis;
 
         let mut site_map = Vec::new();
         let mut site_map_stack: Vec<Vec<SiteMapSegment>> = Vec::new();
@@ -457,7 +425,6 @@ impl RouteEnum {
         }
 
         let myself = Self {
-            vis: vis.clone(),
             name: name.clone(),
             routes,
             redirects,

+ 2 - 2
packages/router/README.md

@@ -51,7 +51,7 @@ enum Route {
 
 fn App(cx: Scope) -> Element {
     render! {
-        Router { }
+        Router::<Route> { }
     }
 }
 
@@ -70,7 +70,7 @@ fn Index(cx: Scope) -> Element {
 fn Blog(cx: Scope) -> Element {
     render! {
         h1 { "Blog" }
-        Outlet { }
+        Outlet::<Route> { }
     }
 }
 

+ 1 - 1
packages/router/benches/incremental.rs

@@ -193,7 +193,7 @@ enum Route {
 fn RenderPath(cx: Scope, path: Route) -> Element {
     let path = path.clone();
     render! {
-        Router {
+        Router::<Route> {
             config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
         }
     }

+ 3 - 3
packages/router/examples/simple_routes.rs

@@ -14,7 +14,7 @@ fn main() {
 
 fn root(cx: Scope) -> Element {
     render! {
-        Router {}
+        Router::<Route> {}
     }
 }
 
@@ -27,7 +27,7 @@ fn UserFrame(cx: Scope, user_id: usize) -> Element {
         div {
             background_color: "rgba(0,0,0,50%)",
             "children:"
-            Outlet {}
+            Outlet::<Route> {}
         }
     }
 }
@@ -90,7 +90,7 @@ fn Route3(cx: Scope, dynamic: String) -> Element {
             "hello world link"
         }
         button {
-            onclick: move |_| { navigator.push(NavigationTarget::External("https://www.google.com".to_string())); },
+            onclick: move |_| { navigator.push(NavigationTarget::<Route>::External("https://www.google.com".to_string())); },
             "google link"
         }
         p { "Site Map" }

+ 3 - 3
packages/router/src/components/default_errors.rs

@@ -1,10 +1,10 @@
-use crate::{hooks::use_generic_router, routable::Routable};
+use crate::hooks::use_router;
 use dioxus::prelude::*;
 
 /// The default component to render when an external navigation fails.
 #[allow(non_snake_case)]
-pub fn FailureExternalNavigation<R: Routable + Clone>(cx: Scope) -> Element {
-    let router = use_generic_router::<R>(cx);
+pub fn FailureExternalNavigation(cx: Scope) -> Element {
+    let router = use_router(cx);
 
     render! {
         h1 { "External Navigation Failure!" }

+ 15 - 19
packages/router/src/components/history_buttons.rs

@@ -1,23 +1,23 @@
 use dioxus::prelude::*;
 use log::error;
 
-use crate::{prelude::*, utils::use_router_internal::use_router_internal};
+use crate::utils::use_router_internal::use_router_internal;
 
-/// The properties for a [`GenericGoBackButton`] or a [`GenericGoForwardButton`].
+/// The properties for a [`GoBackButton`] or a [`GoForwardButton`].
 #[derive(Debug, Props)]
-pub struct GenericHistoryButtonProps<'a> {
+pub struct HistoryButtonProps<'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 [`GenericRouter`] component, otherwise it will be inactive.
+/// Only works as descendant of a [`Link`] component, otherwise it will be inactive.
 ///
 /// The button will disable itself if it is known that no prior history is available.
 ///
 /// # Panic
-/// - When the [`GenericGoBackButton`] is not nested within a [`GenericRouter`] component
+/// - When the [`GoBackButton`] is not nested within a [`Link`] component
 ///   hook, but only in debug builds.
 ///
 /// # Example
@@ -32,7 +32,7 @@ pub struct GenericHistoryButtonProps<'a> {
 ///
 /// fn App(cx: Scope) -> Element {
 ///     render! {
-///         Router {}
+///         Router::<Route> {}
 ///     }
 /// }
 ///
@@ -53,13 +53,11 @@ pub struct GenericHistoryButtonProps<'a> {
 /// # );
 /// ```
 #[allow(non_snake_case)]
-pub fn GenericGoBackButton<'a, R: Routable>(
-    cx: Scope<'a, GenericHistoryButtonProps<'a>>,
-) -> Element {
-    let GenericHistoryButtonProps { children } = cx.props;
+pub fn GoBackButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
+    let HistoryButtonProps { children } = cx.props;
 
     // hook up to router
-    let router = match use_router_internal::<R>(cx) {
+    let router = match use_router_internal(cx) {
         Some(r) => r,
         #[allow(unreachable_code)]
         None => {
@@ -85,12 +83,12 @@ pub fn GenericGoBackButton<'a, R: Routable>(
 
 /// A button to go forward through the navigation history. Similar to a browsers forward button.
 ///
-/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive.
+/// Only works as descendant of a [`Link`] component, otherwise it will be inactive.
 ///
 /// The button will disable itself if it is known that no later history is available.
 ///
 /// # Panic
-/// - When the [`GenericGoForwardButton`] is not nested within a [`GenericRouter`] component
+/// - When the [`GoForwardButton`] is not nested within a [`Link`] component
 ///   hook, but only in debug builds.
 ///
 /// # Example
@@ -105,7 +103,7 @@ pub fn GenericGoBackButton<'a, R: Routable>(
 ///
 /// fn App(cx: Scope) -> Element {
 ///     render! {
-///         Router {}
+///         Router::<Route> {}
 ///     }
 /// }
 ///
@@ -126,13 +124,11 @@ pub fn GenericGoBackButton<'a, R: Routable>(
 /// # );
 /// ```
 #[allow(non_snake_case)]
-pub fn GenericGoForwardButton<'a, R: Routable>(
-    cx: Scope<'a, GenericHistoryButtonProps<'a>>,
-) -> Element {
-    let GenericHistoryButtonProps { children } = cx.props;
+pub fn GoForwardButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
+    let HistoryButtonProps { children } = cx.props;
 
     // hook up to router
-    let router = match use_router_internal::<R>(cx) {
+    let router = match use_router_internal(cx) {
         Some(r) => r,
         #[allow(unreachable_code)]
         None => {

+ 78 - 22
packages/router/src/components/link.rs

@@ -1,15 +1,57 @@
+use std::any::Any;
 use std::fmt::Debug;
 
 use dioxus::prelude::*;
 use log::error;
 
 use crate::navigation::NavigationTarget;
-use crate::prelude::*;
+use crate::prelude::{AnyDisplay, Routable};
 use crate::utils::use_router_internal::use_router_internal;
 
-/// The properties for a [`GenericLink`].
+/// Something that can be converted into a [`NavigationTarget`].
+pub enum IntoRoutable {
+    /// A raw string target.
+    FromStr(String),
+    /// A internal target.
+    Route(Box<dyn AnyDisplay>),
+}
+
+impl<R: Routable> From<R> for IntoRoutable {
+    fn from(value: R) -> Self {
+        IntoRoutable::Route(Box::new(value))
+    }
+}
+
+impl<R: Routable> From<NavigationTarget<R>> for IntoRoutable {
+    fn from(value: NavigationTarget<R>) -> Self {
+        match value {
+            NavigationTarget::Internal(route) => IntoRoutable::Route(Box::new(route)),
+            NavigationTarget::External(url) => IntoRoutable::FromStr(url),
+        }
+    }
+}
+
+impl From<String> for IntoRoutable {
+    fn from(value: String) -> Self {
+        IntoRoutable::FromStr(value)
+    }
+}
+
+impl From<&String> for IntoRoutable {
+    fn from(value: &String) -> Self {
+        IntoRoutable::FromStr(value.to_string())
+    }
+}
+
+impl From<&str> for IntoRoutable {
+    fn from(value: &str) -> Self {
+        IntoRoutable::FromStr(value.to_string())
+    }
+}
+
+/// The properties for a [`Link`].
 #[derive(Props)]
-pub struct GenericLinkProps<'a, R: Routable> {
+pub struct LinkProps<'a> {
     /// A class to apply to the generate HTML anchor tag if the `target` route is active.
     pub active_class: Option<&'a str>,
     /// The children to render within the generated HTML anchor tag.
@@ -23,7 +65,7 @@ pub struct GenericLinkProps<'a, R: Routable> {
     pub id: Option<&'a str>,
     /// When [`true`], the `target` route will be opened in a new tab.
     ///
-    /// This does not change whether the [`GenericLink`] is active or not.
+    /// This does not change whether the [`Link`] is active or not.
     #[props(default)]
     pub new_tab: bool,
     /// The onclick event handler.
@@ -42,10 +84,10 @@ pub struct GenericLinkProps<'a, R: Routable> {
     pub rel: Option<&'a str>,
     /// The navigation target. Roughly equivalent to the href attribute of an HTML anchor tag.
     #[props(into)]
-    pub to: NavigationTarget<R>,
+    pub to: IntoRoutable,
 }
 
-impl<R: Routable> Debug for GenericLinkProps<'_, R> {
+impl Debug for LinkProps<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("LinkProps")
             .field("active_class", &self.active_class)
@@ -56,27 +98,26 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
             .field("onclick", &self.onclick.as_ref().map(|_| "onclick is set"))
             .field("onclick_only", &self.onclick_only)
             .field("rel", &self.rel)
-            .field("to", &self.to.to_string())
             .finish()
     }
 }
 
 /// A link to navigate to another route.
 ///
-/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive.
+/// Only works as descendant of a [`Router`] component, otherwise it will be inactive.
 ///
-/// Unlike a regular HTML anchor, a [`GenericLink`] allows the router to handle the navigation and doesn't
+/// Unlike a regular HTML anchor, a [`Link`] allows the router to handle the navigation and doesn't
 /// cause the browser to load a new page.
 ///
-/// However, in the background a [`GenericLink`] still generates an anchor, which you can use for styling
+/// However, in the background a [`Link`] still generates an anchor, which you can use for styling
 /// as normal.
 ///
 /// # External targets
-/// 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.
+/// When the [`Link`]s target is an [`NavigationTarget::External`] target, that is used as the `href` directly. This
+/// means that a [`Link`] can always navigate to an [`NavigationTarget::External`] target, even if the [`HistoryProvider`] does not support it.
 ///
 /// # Panic
-/// - When the [`GenericLink`] is not nested within a [`GenericRouter`], but
+/// - When the [`Link`] is not nested within a [`Router`], but
 ///   only in debug builds.
 ///
 /// # Example
@@ -92,7 +133,7 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
 ///
 /// fn App(cx: Scope) -> Element {
 ///     render! {
-///         Router {}
+///         Router::<Route> {}
 ///     }
 /// }
 ///
@@ -122,8 +163,8 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
 /// # );
 /// ```
 #[allow(non_snake_case)]
-pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R>>) -> Element {
-    let GenericLinkProps {
+pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
+    let LinkProps {
         active_class,
         children,
         class,
@@ -133,10 +174,11 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
         onclick_only,
         rel,
         to,
+        ..
     } = cx.props;
 
     // hook up to router
-    let router = match use_router_internal::<R>(cx) {
+    let router = match use_router_internal(cx) {
         Some(r) => r,
         #[allow(unreachable_code)]
         None => {
@@ -148,9 +190,15 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
         }
     };
 
-    let current_route = router.current();
-    let current_url = current_route.to_string();
-    let href = to.to_string();
+    let current_url = router.current_route_string();
+    let href = match to {
+        IntoRoutable::FromStr(url) => url.to_string(),
+        IntoRoutable::Route(route) => route.to_string(),
+    };
+    let parsed_route: NavigationTarget<Box<dyn Any>> = match router.route_from_str(&href) {
+        Ok(route) => NavigationTarget::Internal(route.into()),
+        Err(err) => NavigationTarget::External(err),
+    };
     let ac = active_class
         .and_then(|active_class| (href == current_url).then(|| format!(" {active_class}")))
         .unwrap_or_default();
@@ -159,7 +207,7 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
     let class = format!("{}{ac}", class.unwrap_or_default());
     let tag_target = new_tab.then_some("_blank").unwrap_or_default();
 
-    let is_external = matches!(to, NavigationTarget::External(_));
+    let is_external = matches!(parsed_route, NavigationTarget::External(_));
     let is_router_nav = !is_external && !new_tab;
     let prevent_default = is_router_nav.then_some("onclick").unwrap_or_default();
     let rel = rel
@@ -169,7 +217,15 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
     let do_default = onclick.is_none() || !onclick_only;
     let action = move |event| {
         if do_default && is_router_nav {
-            router.push(to.clone());
+            let href = match to {
+                IntoRoutable::FromStr(url) => url.to_string(),
+                IntoRoutable::Route(route) => route.to_string(),
+            };
+            let parsed_route: NavigationTarget<Box<dyn Any>> = match router.route_from_str(&href) {
+                Ok(route) => NavigationTarget::Internal(route.into()),
+                Err(err) => NavigationTarget::External(err),
+            };
+            router.push_any(parsed_route);
         }
 
         if let Some(handler) = onclick {

+ 6 - 6
packages/router/src/components/outlet.rs

@@ -3,13 +3,13 @@ use dioxus::prelude::*;
 
 /// An outlet for the current content.
 ///
-/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive.
+/// Only works as descendant of a [`Link`] component, otherwise it will be inactive.
 ///
-/// The [`GenericOutlet`] is aware of how many [`GenericOutlet`]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__.
 ///
 /// # Panic
-/// - When the [`GenericOutlet`] is not nested a [`GenericRouter`] component,
+/// - When the [`Outlet`] is not nested a [`Link`] component,
 ///   but only in debug builds.
 ///
 /// # Example
@@ -42,7 +42,7 @@ use dioxus::prelude::*;
 /// fn Wrapper(cx: Scope) -> Element {
 ///     render! {
 ///         h1 { "App" }
-///         Outlet {} // The content of child routes will be rendered here
+///         Outlet::<Route> {} // The content of child routes will be rendered here
 ///     }
 /// }
 ///
@@ -57,7 +57,7 @@ use dioxus::prelude::*;
 ///
 /// # fn App(cx: Scope) -> Element {
 /// #     render! {
-/// #         Router {
+/// #         Router::<Route> {
 /// #             config: || RouterConfig::default().history(MemoryHistory::with_initial_path(Route::Child {}))
 /// #         }
 /// #     }
@@ -67,6 +67,6 @@ use dioxus::prelude::*;
 /// # let _ = vdom.rebuild();
 /// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><p>Child</p>");
 /// ```
-pub fn GenericOutlet<R: Routable + Clone>(cx: Scope) -> Element {
+pub fn Outlet<R: Routable + Clone>(cx: Scope) -> Element {
     OutletContext::<R>::render(cx)
 }

+ 17 - 21
packages/router/src/components/router.rs

@@ -1,13 +1,9 @@
 use dioxus::prelude::*;
 use std::{cell::RefCell, str::FromStr};
 
-use crate::{
-    prelude::{GenericOutlet, GenericRouterContext},
-    routable::Routable,
-    router_cfg::RouterConfig,
-};
+use crate::{prelude::Outlet, routable::Routable, router_cfg::RouterConfig};
 
-/// The config for [`GenericRouter`].
+/// The config for [`Router`].
 pub struct RouterConfigFactory<R: Routable> {
     #[allow(clippy::type_complexity)]
     config: RefCell<Option<Box<dyn FnOnce() -> RouterConfig<R>>>>,
@@ -43,9 +39,9 @@ impl<R: Routable, F: FnOnce() -> RouterConfig<R> + 'static> From<F> for RouterCo
 }
 
 #[cfg(feature = "serde")]
-/// The props for [`GenericRouter`].
+/// The props for [`Router`].
 #[derive(Props)]
-pub struct GenericRouterProps<R: Routable>
+pub struct RouterProps<R: Routable>
 where
     <R as FromStr>::Err: std::fmt::Display,
     R: serde::Serialize + serde::de::DeserializeOwned,
@@ -55,9 +51,9 @@ where
 }
 
 #[cfg(not(feature = "serde"))]
-/// The props for [`GenericRouter`].
+/// The props for [`Router`].
 #[derive(Props)]
-pub struct GenericRouterProps<R: Routable>
+pub struct RouterProps<R: Routable>
 where
     <R as FromStr>::Err: std::fmt::Display,
 {
@@ -66,7 +62,7 @@ where
 }
 
 #[cfg(not(feature = "serde"))]
-impl<R: Routable> Default for GenericRouterProps<R>
+impl<R: Routable> Default for RouterProps<R>
 where
     <R as FromStr>::Err: std::fmt::Display,
 {
@@ -78,7 +74,7 @@ where
 }
 
 #[cfg(feature = "serde")]
-impl<R: Routable> Default for GenericRouterProps<R>
+impl<R: Routable> Default for RouterProps<R>
 where
     <R as FromStr>::Err: std::fmt::Display,
     R: serde::Serialize + serde::de::DeserializeOwned,
@@ -91,7 +87,7 @@ where
 }
 
 #[cfg(not(feature = "serde"))]
-impl<R: Routable> PartialEq for GenericRouterProps<R>
+impl<R: Routable> PartialEq for RouterProps<R>
 where
     <R as FromStr>::Err: std::fmt::Display,
 {
@@ -102,7 +98,7 @@ where
 }
 
 #[cfg(feature = "serde")]
-impl<R: Routable> PartialEq for GenericRouterProps<R>
+impl<R: Routable> PartialEq for RouterProps<R>
 where
     <R as FromStr>::Err: std::fmt::Display,
     R: serde::Serialize + serde::de::DeserializeOwned,
@@ -115,14 +111,14 @@ where
 
 #[cfg(not(feature = "serde"))]
 /// A component that renders the current route.
-pub fn GenericRouter<R: Routable + Clone>(cx: Scope<GenericRouterProps<R>>) -> Element
+pub fn Router<R: Routable + Clone>(cx: Scope<RouterProps<R>>) -> Element
 where
     <R as FromStr>::Err: std::fmt::Display,
 {
-    use crate::prelude::outlet::OutletContext;
+    use crate::prelude::{outlet::OutletContext, RouterContext};
 
     use_context_provider(cx, || {
-        GenericRouterContext::new(
+        RouterContext::new(
             (cx.props
                 .config
                 .config
@@ -137,19 +133,19 @@ where
     });
 
     render! {
-        GenericOutlet::<R> {}
+        Outlet::<R> {}
     }
 }
 
 #[cfg(feature = "serde")]
 /// A component that renders the current route.
-pub fn GenericRouter<R: Routable + Clone>(cx: Scope<GenericRouterProps<R>>) -> Element
+pub fn Router<R: Routable + Clone>(cx: Scope<RouterProps<R>>) -> Element
 where
     <R as FromStr>::Err: std::fmt::Display,
     R: serde::Serialize + serde::de::DeserializeOwned,
 {
     use_context_provider(cx, || {
-        GenericRouterContext::new(
+        RouterContext::new(
             (cx.props
                 .config
                 .config
@@ -164,6 +160,6 @@ where
     });
 
     render! {
-        GenericOutlet::<R> {}
+        Outlet::<R> {}
     }
 }

+ 5 - 5
packages/router/src/contexts/navigator.rs

@@ -1,10 +1,10 @@
-use crate::prelude::{ExternalNavigationFailure, GenericRouterContext, NavigationTarget, Routable};
+use crate::prelude::{ExternalNavigationFailure, NavigationTarget, Routable, RouterContext};
 
 /// A view into the navigation state of a router.
 #[derive(Clone)]
-pub struct GenericNavigator<R: Routable>(pub(crate) GenericRouterContext<R>);
+pub struct GenericNavigator(pub(crate) RouterContext);
 
-impl<R: Routable> GenericNavigator<R> {
+impl GenericNavigator {
     /// Check whether there is a previous page to navigate back to.
     #[must_use]
     pub fn can_go_back(&self) -> bool {
@@ -34,7 +34,7 @@ impl<R: Routable> GenericNavigator<R> {
     /// Push a new location.
     ///
     /// The previous location will be available to go back to.
-    pub fn push(
+    pub fn push<R: Routable>(
         &self,
         target: impl Into<NavigationTarget<R>>,
     ) -> Option<ExternalNavigationFailure> {
@@ -44,7 +44,7 @@ impl<R: Routable> GenericNavigator<R> {
     /// Replace the current location.
     ///
     /// The previous location will **not** be available to go back to.
-    pub fn replace(
+    pub fn replace<R: Routable>(
         &self,
         target: impl Into<NavigationTarget<R>>,
     ) -> Option<ExternalNavigationFailure> {

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

@@ -31,7 +31,7 @@ impl<R> OutletContext<R> {
     where
         R: Routable + Clone,
     {
-        let router = use_router_internal::<R>(cx)
+        let router = use_router_internal(cx)
             .as_ref()
             .expect("Outlet must be inside of a router");
         let outlet: &OutletContext<R> = use_outlet_context(cx);
@@ -51,6 +51,6 @@ impl<R> OutletContext<R> {
             }
         }
 
-        router.current().render(cx, current_level)
+        router.current::<R>().render(cx, current_level)
     }
 }

+ 153 - 42
packages/router/src/contexts/router.rs

@@ -1,4 +1,5 @@
 use std::{
+    any::Any,
     collections::HashSet,
     sync::{Arc, RwLock, RwLockWriteGuard},
 };
@@ -6,7 +7,7 @@ use std::{
 use dioxus::prelude::*;
 
 use crate::{
-    history::HistoryProvider, navigation::NavigationTarget, routable::Routable,
+    navigation::NavigationTarget, prelude::AnyHistoryProvider, routable::Routable,
     router_cfg::RouterConfig,
 };
 
@@ -15,52 +16,31 @@ use crate::{
 pub struct ExternalNavigationFailure(String);
 
 /// A function the router will call after every routing update.
-pub(crate) type RoutingCallback<R> =
-    Arc<dyn Fn(GenericRouterContext<R>) -> Option<NavigationTarget<R>>>;
+pub(crate) type RoutingCallback<R> = Arc<dyn Fn(LinkContext<R>) -> Option<NavigationTarget<R>>>;
 
-struct MutableRouterState<R>
-where
-    R: Routable,
-{
+struct MutableRouterState {
     /// The current prefix.
     prefix: Option<String>,
 
-    history: Box<dyn HistoryProvider<R>>,
+    history: Box<dyn AnyHistoryProvider>,
 
     unresolved_error: Option<ExternalNavigationFailure>,
 }
 
 /// A collection of router data that manages all routing functionality.
-pub struct GenericRouterContext<R>
-where
-    R: Routable,
-{
-    state: Arc<RwLock<MutableRouterState<R>>>,
+#[derive(Clone)]
+pub struct RouterContext {
+    state: Arc<RwLock<MutableRouterState>>,
 
     subscribers: Arc<RwLock<HashSet<ScopeId>>>,
     subscriber_update: Arc<dyn Fn(ScopeId)>,
-    routing_callback: Option<RoutingCallback<R>>,
+    routing_callback: Option<Arc<dyn Fn(RouterContext) -> Option<NavigationTarget<Box<dyn Any>>>>>,
 
     failure_external_navigation: fn(Scope) -> Element,
 }
 
-impl<R: Routable> Clone for GenericRouterContext<R> {
-    fn clone(&self) -> Self {
-        Self {
-            state: self.state.clone(),
-            subscribers: self.subscribers.clone(),
-            subscriber_update: self.subscriber_update.clone(),
-            routing_callback: self.routing_callback.clone(),
-            failure_external_navigation: self.failure_external_navigation,
-        }
-    }
-}
-
-impl<R> GenericRouterContext<R>
-where
-    R: Routable,
-{
-    pub(crate) fn new(
+impl RouterContext {
+    pub(crate) fn new<R: Routable + 'static>(
         mut cfg: RouterConfig<R>,
         mark_dirty: Arc<dyn Fn(ScopeId) + Sync + Send>,
     ) -> Self
@@ -82,7 +62,21 @@ where
             subscribers: subscribers.clone(),
             subscriber_update,
 
-            routing_callback: cfg.on_update,
+            routing_callback: cfg.on_update.map(|update| {
+                Arc::new(move |ctx| {
+                    let ctx = LinkContext {
+                        inner: ctx,
+                        _marker: std::marker::PhantomData,
+                    };
+                    update(ctx).map(|t| match t {
+                        NavigationTarget::Internal(r) => {
+                            NavigationTarget::Internal(Box::new(r) as Box<dyn Any>)
+                        }
+                        NavigationTarget::External(s) => NavigationTarget::External(s),
+                    })
+                })
+                    as Arc<dyn Fn(RouterContext) -> Option<NavigationTarget<Box<dyn Any>>>>
+            }),
 
             failure_external_navigation: cfg.failure_external_navigation,
         };
@@ -100,6 +94,11 @@ where
         myself
     }
 
+    pub(crate) fn route_from_str(&self, route: &str) -> Result<Box<dyn Any>, String> {
+        let state = self.state.read().unwrap();
+        state.history.parse_route(route)
+    }
+
     /// Check whether there is a previous page to navigate back to.
     #[must_use]
     pub fn can_go_back(&self) -> bool {
@@ -134,10 +133,25 @@ where
         self.change_route();
     }
 
+    pub(crate) fn push_any(
+        &self,
+        target: NavigationTarget<Box<dyn Any>>,
+    ) -> Option<ExternalNavigationFailure> {
+        match target {
+            NavigationTarget::Internal(p) => {
+                let mut state = self.state_mut();
+                state.history.push(p)
+            }
+            NavigationTarget::External(e) => return self.external(e),
+        }
+
+        self.change_route()
+    }
+
     /// Push a new location.
     ///
     /// The previous location will be available to go back to.
-    pub fn push(
+    pub fn push<R: Routable>(
         &self,
         target: impl Into<NavigationTarget<R>>,
     ) -> Option<ExternalNavigationFailure> {
@@ -145,7 +159,7 @@ where
         match target {
             NavigationTarget::Internal(p) => {
                 let mut state = self.state_mut();
-                state.history.push(p)
+                state.history.push(Box::new(p))
             }
             NavigationTarget::External(e) => return self.external(e),
         }
@@ -156,7 +170,7 @@ where
     /// Replace the current location.
     ///
     /// The previous location will **not** be available to go back to.
-    pub fn replace(
+    pub fn replace<R: Routable>(
         &self,
         target: impl Into<NavigationTarget<R>>,
     ) -> Option<ExternalNavigationFailure> {
@@ -165,7 +179,7 @@ where
         {
             let mut state = self.state_mut();
             match target {
-                NavigationTarget::Internal(p) => state.history.replace(p),
+                NavigationTarget::Internal(p) => state.history.replace(Box::new(p)),
                 NavigationTarget::External(e) => return self.external(e),
             }
         }
@@ -174,11 +188,24 @@ where
     }
 
     /// The route that is currently active.
-    pub fn current(&self) -> R
-    where
-        R: Clone,
-    {
-        self.state.read().unwrap().history.current_route()
+    pub fn current<R: Routable>(&self) -> R {
+        self.state
+            .read()
+            .unwrap()
+            .history
+            .current_route()
+            .downcast::<R>()
+            .unwrap()
+    }
+
+    /// The route that is currently active.
+    pub fn current_route_string(&self) -> String {
+        self.state
+            .read()
+            .unwrap()
+            .history
+            .current_route()
+            .to_string()
     }
 
     /// The prefix that is currently active.
@@ -201,7 +228,7 @@ where
         }
     }
 
-    fn state_mut(&self) -> RwLockWriteGuard<MutableRouterState<R>> {
+    fn state_mut(&self) -> RwLockWriteGuard<MutableRouterState> {
         self.state.write().unwrap()
     }
 
@@ -254,3 +281,87 @@ where
         None
     }
 }
+
+pub struct LinkContext<R> {
+    inner: RouterContext,
+    _marker: std::marker::PhantomData<R>,
+}
+
+impl<R> LinkContext<R>
+where
+    R: Routable,
+{
+    /// Check whether there is a previous page to navigate back to.
+    #[must_use]
+    pub fn can_go_back(&self) -> bool {
+        self.inner.can_go_back()
+    }
+
+    /// Check whether there is a future page to navigate forward to.
+    #[must_use]
+    pub fn can_go_forward(&self) -> bool {
+        self.inner.can_go_forward()
+    }
+
+    /// Go back to the previous location.
+    ///
+    /// Will fail silently if there is no previous location to go to.
+    pub fn go_back(&self) {
+        self.inner.go_back();
+    }
+
+    /// Go back to the next location.
+    ///
+    /// Will fail silently if there is no next location to go to.
+    pub fn go_forward(&self) {
+        self.inner.go_forward();
+    }
+
+    /// Push a new location.
+    ///
+    /// The previous location will be available to go back to.
+    pub fn push(
+        &self,
+        target: impl Into<NavigationTarget<R>>,
+    ) -> Option<ExternalNavigationFailure> {
+        self.inner.push(target)
+    }
+
+    /// Replace the current location.
+    ///
+    /// The previous location will **not** be available to go back to.
+    pub fn replace(
+        &self,
+        target: impl Into<NavigationTarget<R>>,
+    ) -> Option<ExternalNavigationFailure> {
+        self.inner.replace(target)
+    }
+
+    /// The route that is currently active.
+    pub fn current(&self) -> R
+    where
+        R: Clone,
+    {
+        self.inner.current()
+    }
+
+    /// The prefix that is currently active.
+    pub fn prefix(&self) -> Option<String> {
+        self.inner.prefix()
+    }
+
+    /// Manually subscribe to the current route
+    pub fn subscribe(&self, id: ScopeId) {
+        self.inner.subscribe(id)
+    }
+
+    /// Manually unsubscribe from the current route
+    pub fn unsubscribe(&self, id: ScopeId) {
+        self.inner.unsubscribe(id)
+    }
+
+    /// Clear any unresolved errors
+    pub fn clear_error(&self) {
+        self.inner.clear_error()
+    }
+}

+ 141 - 1
packages/router/src/history/mod.rs

@@ -10,7 +10,7 @@
 //! 1) [`MemoryHistory`] for desktop/mobile/ssr platforms
 //! 2) [`WebHistory`] for web platforms
 
-use std::sync::Arc;
+use std::{any::Any, fmt::Display, sync::Arc};
 
 mod memory;
 pub use memory::*;
@@ -277,3 +277,143 @@ pub trait HistoryProvider<R: Routable> {
     #[allow(unused_variables)]
     fn updater(&mut self, callback: Arc<dyn Fn() + Send + Sync>) {}
 }
+
+/// Something that can be displayed and is also an [`Any`]
+pub trait AnyDisplay: Display + Any {
+    /// Get a reference to the inner [`Any`] object.
+    fn as_any(&self) -> &dyn Any;
+}
+
+impl dyn AnyDisplay {
+    /// Try to downcast the inner [`Any`] object to a concrete type.
+    pub fn downcast<T: Any + Clone>(self: Box<dyn AnyDisplay>) -> Option<T> {
+        self.as_any().downcast_ref::<T>().cloned()
+    }
+}
+
+impl<T: Display + Any> AnyDisplay for T {
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+}
+
+pub(crate) trait AnyHistoryProvider {
+    #[must_use]
+    fn parse_route(&self, route: &str) -> Result<Box<dyn Any>, String>;
+
+    #[must_use]
+    fn accepts_type_id(&self, type_id: &std::any::TypeId) -> bool;
+
+    #[must_use]
+    fn current_route(&self) -> Box<dyn AnyDisplay>;
+
+    #[must_use]
+    fn current_prefix(&self) -> Option<String> {
+        None
+    }
+
+    #[must_use]
+    fn can_go_back(&self) -> bool {
+        true
+    }
+
+    fn go_back(&mut self);
+
+    #[must_use]
+    fn can_go_forward(&self) -> bool {
+        true
+    }
+
+    fn go_forward(&mut self);
+
+    fn push(&mut self, route: Box<dyn Any>);
+
+    fn replace(&mut self, path: Box<dyn Any>);
+
+    #[allow(unused_variables)]
+    fn external(&mut self, url: String) -> bool {
+        false
+    }
+
+    #[allow(unused_variables)]
+    fn updater(&mut self, callback: Arc<dyn Fn() + Send + Sync>) {}
+}
+
+pub(crate) struct AnyHistoryProviderImplWrapper<R, H> {
+    inner: H,
+    _marker: std::marker::PhantomData<R>,
+}
+
+impl<R, H> AnyHistoryProviderImplWrapper<R, H> {
+    pub fn new(inner: H) -> Self {
+        Self {
+            inner,
+            _marker: std::marker::PhantomData,
+        }
+    }
+}
+
+impl<R, H: Default> Default for AnyHistoryProviderImplWrapper<R, H> {
+    fn default() -> Self {
+        Self::new(H::default())
+    }
+}
+
+impl<R, H> AnyHistoryProvider for AnyHistoryProviderImplWrapper<R, H>
+where
+    R: Routable,
+    <R as std::str::FromStr>::Err: std::fmt::Display,
+    H: HistoryProvider<R>,
+{
+    fn parse_route(&self, route: &str) -> Result<Box<dyn Any>, String> {
+        R::from_str(route)
+            .map_err(|err| err.to_string())
+            .map(|route| Box::new(route) as Box<dyn Any>)
+    }
+
+    fn accepts_type_id(&self, type_id: &std::any::TypeId) -> bool {
+        type_id == &std::any::TypeId::of::<R>()
+    }
+
+    fn current_route(&self) -> Box<dyn AnyDisplay> {
+        let route = self.inner.current_route();
+        println!("current_route {route}");
+        Box::new(route)
+    }
+
+    fn current_prefix(&self) -> Option<String> {
+        self.inner.current_prefix()
+    }
+
+    fn can_go_back(&self) -> bool {
+        self.inner.can_go_back()
+    }
+
+    fn go_back(&mut self) {
+        self.inner.go_back()
+    }
+
+    fn can_go_forward(&self) -> bool {
+        self.inner.can_go_forward()
+    }
+
+    fn go_forward(&mut self) {
+        self.inner.go_forward()
+    }
+
+    fn push(&mut self, route: Box<dyn Any>) {
+        self.inner.push(*route.downcast().unwrap())
+    }
+
+    fn replace(&mut self, path: Box<dyn Any>) {
+        self.inner.replace(*path.downcast().unwrap())
+    }
+
+    fn external(&mut self, url: String) -> bool {
+        self.inner.external(url)
+    }
+
+    fn updater(&mut self, callback: Arc<dyn Fn() + Send + Sync>) {
+        self.inner.updater(callback)
+    }
+}

+ 4 - 7
packages/router/src/hooks/use_navigator.rs

@@ -1,9 +1,6 @@
 use dioxus::prelude::ScopeState;
 
-use crate::{
-    prelude::{GenericNavigator, GenericRouterContext},
-    routable::Routable,
-};
+use crate::prelude::{GenericNavigator, RouterContext};
 
 /// A hook that provides access to the navigator to change the router history. Unlike [`use_router`], this hook will not cause a rerender when the current route changes
 ///
@@ -22,7 +19,7 @@ use crate::{
 ///
 /// fn App(cx: Scope) -> Element {
 ///     render! {
-///         Router {}
+///         Router::<Route> {}
 ///     }
 /// }
 ///
@@ -50,10 +47,10 @@ use crate::{
 /// # let mut vdom = VirtualDom::new(App);
 /// # let _ = vdom.rebuild();
 /// ```
-pub fn use_generic_navigator<R: Routable + Clone>(cx: &ScopeState) -> &GenericNavigator<R> {
+pub fn use_navigator(cx: &ScopeState) -> &GenericNavigator {
     &*cx.use_hook(|| {
         let router = cx
-            .consume_context::<GenericRouterContext<R>>()
+            .consume_context::<RouterContext>()
             .expect("Must be called in a descendant of a Router component");
 
         GenericNavigator(router)

+ 4 - 4
packages/router/src/hooks/use_route.rs

@@ -8,11 +8,11 @@ use crate::utils::use_router_internal::use_router_internal;
 /// > The Routable macro will define a version of this hook with an explicit type.
 ///
 /// # Return values
-/// - None, when not called inside a [`GenericRouter`] component.
+/// - None, when not called inside a [`Link`] component.
 /// - Otherwise the current route.
 ///
 /// # Panic
-/// - When the calling component is not nested within a [`GenericRouter`] component durring a debug build.
+/// - When the calling component is not nested within a [`Link`] component durring a debug build.
 ///
 /// # Example
 /// ```rust
@@ -28,7 +28,7 @@ use crate::utils::use_router_internal::use_router_internal;
 /// fn App(cx: Scope) -> Element {
 ///     render! {
 ///         h1 { "App" }
-///         Router {}
+///         Router::<Route> {}
 ///     }
 /// }
 ///
@@ -45,7 +45,7 @@ use crate::utils::use_router_internal::use_router_internal;
 /// # let _ = vdom.rebuild();
 /// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><h2>Current Path</h2><p>/</p>")
 /// ```
-pub fn use_generic_route<R: Routable + Clone>(cx: &ScopeState) -> Option<R> {
+pub fn use_route<R: Routable + Clone>(cx: &ScopeState) -> Option<R> {
     match use_router_internal(cx) {
         Some(r) => Some(r.current()),
         None => {

+ 2 - 5
packages/router/src/hooks/use_router.rs

@@ -1,12 +1,9 @@
 use dioxus::prelude::ScopeState;
 
-use crate::{
-    prelude::GenericRouterContext, routable::Routable,
-    utils::use_router_internal::use_router_internal,
-};
+use crate::{prelude::RouterContext, utils::use_router_internal::use_router_internal};
 
 /// A hook that provides access to information about the router.
-pub fn use_generic_router<R: Routable + Clone>(cx: &ScopeState) -> &GenericRouterContext<R> {
+pub fn use_router(cx: &ScopeState) -> &RouterContext {
     use_router_internal(cx)
         .as_ref()
         .expect("use_route must have access to a router")

+ 1 - 1
packages/router/src/incremental.rs

@@ -95,7 +95,7 @@ where
     {
         let path = path.clone();
         render! {
-            GenericRouter::<R> {
+            Link::<R> {
                 config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
             }
         }

+ 1 - 1
packages/router/src/navigation.rs

@@ -11,7 +11,7 @@ use crate::routable::Routable;
 
 /// A target for the router to navigate to.
 #[derive(Clone, PartialEq, Eq, Debug)]
-pub enum NavigationTarget<R: Routable> {
+pub enum NavigationTarget<R> {
     /// An internal path that the router can navigate to by itself.
     ///
     /// ```rust

+ 12 - 8
packages/router/src/router_cfg.rs

@@ -26,7 +26,7 @@ use crate::prelude::*;
 /// ```
 pub struct RouterConfig<R: Routable> {
     pub(crate) failure_external_navigation: fn(Scope) -> Element,
-    pub(crate) history: Option<Box<dyn HistoryProvider<R>>>,
+    pub(crate) history: Option<Box<dyn AnyHistoryProvider>>,
     pub(crate) on_update: Option<RoutingCallback<R>>,
 }
 
@@ -69,7 +69,7 @@ where
 {
     fn default() -> Self {
         Self {
-            failure_external_navigation: FailureExternalNavigation::<R>,
+            failure_external_navigation: FailureExternalNavigation,
             history: None,
             on_update: None,
         }
@@ -81,18 +81,22 @@ impl<R: Routable + Clone> RouterConfig<R>
 where
     <R as std::str::FromStr>::Err: std::fmt::Display,
 {
-    pub(crate) fn take_history(&mut self) -> Box<dyn HistoryProvider<R>> {
+    pub(crate) fn take_history(&mut self) -> Box<dyn AnyHistoryProvider> {
         self.history.take().unwrap_or_else(|| {
             #[cfg(all(target_arch = "wasm32", feature = "web"))]
-            let history = Box::<WebHistory<R>>::default();
+            let history = Box::<AnyHistoryProviderImplWrapper<R, WebHistory<R>>>::default();
             #[cfg(not(all(target_arch = "wasm32", feature = "web")))]
-            let history = Box::<MemoryHistory<R>>::default();
+            let history = Box::<AnyHistoryProviderImplWrapper<R, MemoryHistory<R>>>::default();
             history
         })
     }
 }
 
-impl<R: Routable> RouterConfig<R> {
+impl<R> RouterConfig<R>
+where
+    R: Routable,
+    <R as std::str::FromStr>::Err: std::fmt::Display,
+{
     /// A function to be called whenever the routing is updated.
     ///
     /// The callback is invoked after the routing is updated, but before components and hooks are
@@ -108,7 +112,7 @@ impl<R: Routable> RouterConfig<R> {
     /// Defaults to [`None`].
     pub fn on_update(
         self,
-        callback: impl Fn(GenericRouterContext<R>) -> Option<NavigationTarget<R>> + 'static,
+        callback: impl Fn(LinkContext<R>) -> Option<NavigationTarget<R>> + 'static,
     ) -> Self {
         Self {
             on_update: Some(Arc::new(callback)),
@@ -121,7 +125,7 @@ impl<R: Routable> RouterConfig<R> {
     /// Defaults to a default [`MemoryHistory`].
     pub fn history(self, history: impl HistoryProvider<R> + 'static) -> Self {
         Self {
-            history: Some(Box::new(history)),
+            history: Some(Box::new(AnyHistoryProviderImplWrapper::new(history))),
             ..self
         }
     }

+ 7 - 9
packages/router/src/utils/use_router_internal.rs

@@ -1,6 +1,6 @@
 use dioxus::prelude::{ScopeId, ScopeState};
 
-use crate::{contexts::router::GenericRouterContext, prelude::*};
+use crate::prelude::*;
 
 /// A private hook to subscribe to the router.
 ///
@@ -8,13 +8,11 @@ use crate::{contexts::router::GenericRouterContext, prelude::*};
 /// single component, but not recommended. Multiple subscriptions will be discarded.
 ///
 /// # Return values
-/// - [`None`], when the current component isn't a descendant of a [`GenericRouter`] component.
+/// - [`None`], when the current component isn't a descendant of a [`Link`] component.
 /// - Otherwise [`Some`].
-pub(crate) fn use_router_internal<R: Routable>(
-    cx: &ScopeState,
-) -> &Option<GenericRouterContext<R>> {
+pub(crate) fn use_router_internal(cx: &ScopeState) -> &Option<RouterContext> {
     let inner = cx.use_hook(|| {
-        let router = cx.consume_context::<GenericRouterContext<R>>()?;
+        let router = cx.consume_context::<RouterContext>()?;
 
         let id = cx.scope_id();
         router.subscribe(id);
@@ -24,12 +22,12 @@ pub(crate) fn use_router_internal<R: Routable>(
     cx.use_hook(|| inner.as_ref().map(|s| s.router.clone()))
 }
 
-struct Subscription<R: Routable> {
-    router: GenericRouterContext<R>,
+struct Subscription {
+    router: RouterContext,
     id: ScopeId,
 }
 
-impl<R: Routable> Drop for Subscription<R> {
+impl Drop for Subscription {
     fn drop(&mut self) {
         self.router.unsubscribe(self.id);
     }

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

@@ -33,7 +33,7 @@ where
     {
         render! {
             h1 { "App" }
-            GenericRouter::<R> {
+            Router::<R> {
                 config: || RouterConfig::default().history(MemoryHistory::default())
             }
         }
@@ -97,7 +97,7 @@ fn href_external() {
     fn Root(cx: Scope) -> Element {
         render! {
             Link {
-                to: NavigationTarget::External("https://dioxuslabs.com/".into()),
+                to: "https://dioxuslabs.com/",
                 "Link"
             }
         }
@@ -318,7 +318,7 @@ fn with_new_tab_external() {
     fn Root(cx: Scope) -> Element {
         render! {
             Link {
-                to: NavigationTarget::External("https://dioxuslabs.com/".into()),
+                to: "https://dioxuslabs.com/",
                 new_tab: true,
                 "Link"
             }

+ 3 - 3
packages/router/tests/via_ssr/outlet.rs

@@ -37,7 +37,7 @@ fn prepare(path: impl Into<String>) -> VirtualDom {
     fn App(cx: Scope<AppProps>) -> Element {
         render! {
             h1 { "App" }
-            Router {
+            Router::<Route> {
                 config: {
                     let path = cx.props.path.parse().unwrap();
                     move || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
@@ -57,7 +57,7 @@ fn prepare(path: impl Into<String>) -> VirtualDom {
     fn Fixed(cx: Scope) -> Element {
         render! {
             h2 { "Fixed" }
-            Outlet { }
+            Outlet::<Route> { }
         }
     }
 
@@ -79,7 +79,7 @@ fn prepare(path: impl Into<String>) -> VirtualDom {
     fn Parameter(cx: Scope, id: u8) -> Element {
         render! {
             h2 { "Parameter {id}" }
-            Outlet { }
+            Outlet::<Route> { }
         }
     }