From fe601e2a4808c3a7e5698ee02dea157c22c04355 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 1 Jun 2023 09:45:31 -0500 Subject: [PATCH] restore navigator --- packages/router-macro/src/lib.rs | 8 +-- packages/router/examples/simple_routes.rs | 16 +++--- packages/router/src/contexts/navigator.rs | 53 ++++++++++++++++++ packages/router/src/hooks/use_navigator.rs | 62 ++++++++++++++++++++++ packages/router/src/hooks/use_route.rs | 2 + packages/router/src/hooks/use_router.rs | 45 +--------------- packages/router/src/lib.rs | 9 +++- 7 files changed, 137 insertions(+), 58 deletions(-) create mode 100644 packages/router/src/contexts/navigator.rs create mode 100644 packages/router/src/hooks/use_navigator.rs diff --git a/packages/router-macro/src/lib.rs b/packages/router-macro/src/lib.rs index 5f62fabbd..2c7a43719 100644 --- a/packages/router-macro/src/lib.rs +++ b/packages/router-macro/src/lib.rs @@ -61,14 +61,14 @@ pub fn routable(input: TokenStream) -> TokenStream { 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) } + #vis fn use_navigator(cx: &dioxus::prelude::ScopeState) -> &dioxus_router::prelude::GenericNavigator<#name> { + dioxus_router::prelude::use_generic_navigator(cx) + } + #error_type #parse_impl diff --git a/packages/router/examples/simple_routes.rs b/packages/router/examples/simple_routes.rs index 9b8821c25..d87a1fe3c 100644 --- a/packages/router/examples/simple_routes.rs +++ b/packages/router/examples/simple_routes.rs @@ -79,10 +79,10 @@ fn Route2(cx: Scope, user_id: usize) -> Element { #[inline_props] fn Route3(cx: Scope, dynamic: String) -> Element { - let router = use_router(cx); - let router_route = router.current(); - let current_route = use_ref(cx, String::new); - let parsed = Route::from_str(¤t_route.read()); + let navigator = use_navigator(cx); + let current_route = use_route(cx)?; + let current_route_str = use_ref(cx, String::new); + let parsed = Route::from_str(¤t_route_str.read()); let site_map = Route::SITE_MAP .iter() @@ -92,9 +92,9 @@ fn Route3(cx: Scope, dynamic: String) -> Element { render! { input { oninput: move |evt| { - *current_route.write() = evt.value.clone(); + *current_route_str.write() = evt.value.clone(); }, - value: "{current_route.read()}" + value: "{current_route_str.read()}" } "dynamic: {dynamic}" Link { @@ -102,14 +102,14 @@ fn Route3(cx: Scope, dynamic: String) -> Element { "hello world link" } button { - onclick: move |_| { router.push(NavigationTarget::External("https://www.google.com".to_string())); }, + onclick: move |_| { navigator.push(NavigationTarget::External("https://www.google.com".to_string())); }, "google link" } p { "Site Map" } pre { "{site_map:#?}" } p { "Dynamic link" } if let Ok(route) = parsed { - if route != router_route { + if route != current_route { render! { Link { target: route.clone(), diff --git a/packages/router/src/contexts/navigator.rs b/packages/router/src/contexts/navigator.rs new file mode 100644 index 000000000..57538dc58 --- /dev/null +++ b/packages/router/src/contexts/navigator.rs @@ -0,0 +1,53 @@ +use crate::prelude::{ExternalNavigationFailure, GenericRouterContext, NavigationTarget, Routable}; + +/// A view into the navigation state of a router. +#[derive(Clone)] +pub struct GenericNavigator(pub(crate) GenericRouterContext); + +impl GenericNavigator { + /// Check whether there is a previous page to navigate back to. + #[must_use] + pub fn can_go_back(&self) -> bool { + self.0.can_go_back() + } + + /// Check whether there is a future page to navigate forward to. + #[must_use] + pub fn can_go_forward(&self) -> bool { + self.0.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.0.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.0.go_forward(); + } + + /// Push a new location. + /// + /// The previous location will be available to go back to. + pub fn push( + &self, + target: impl Into>, + ) -> Option { + self.0.push(target) + } + + /// Replace the current location. + /// + /// The previous location will **not** be available to go back to. + pub fn replace( + &self, + target: impl Into>, + ) -> Option { + self.0.replace(target) + } +} diff --git a/packages/router/src/hooks/use_navigator.rs b/packages/router/src/hooks/use_navigator.rs new file mode 100644 index 000000000..545e8f947 --- /dev/null +++ b/packages/router/src/hooks/use_navigator.rs @@ -0,0 +1,62 @@ +use dioxus::prelude::ScopeState; + +use crate::{ + prelude::{GenericNavigator, GenericRouterContext}, + routable::Routable, +}; + +/// 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 +/// +/// > The Routable macro will define a version of this hook with an explicit type. +/// +/// ```rust +/// # use dioxus::prelude::*; +/// # use dioxus_router::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 navigator = use_navigator(&cx); +/// +/// render! { +/// button { +/// onclick: move |_| { navigator.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_navigator(cx: &ScopeState) -> &GenericNavigator { + &*cx.use_hook(|| { + let router = cx + .consume_context::>() + .expect("Must be called in a descendant of a Router component"); + + GenericNavigator(router) + }) +} diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index 28fbd03f1..75f6c1332 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -5,6 +5,8 @@ use crate::utils::use_router_internal::use_router_internal; /// A hook that provides access to information about the current routing location. /// +/// > The Routable macro will define a version of this hook with an explicit type. +/// /// # Return values /// - None, when not called inside a [`GenericRouter`] component. /// - Otherwise the current route. diff --git a/packages/router/src/hooks/use_router.rs b/packages/router/src/hooks/use_router.rs index 4b644e5ea..b9485577a 100644 --- a/packages/router/src/hooks/use_router.rs +++ b/packages/router/src/hooks/use_router.rs @@ -5,50 +5,7 @@ use crate::{ utils::use_router_internal::use_router_internal, }; -/// 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::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(); -/// ``` +/// A hook that provides access to information about the router. pub fn use_generic_router(cx: &ScopeState) -> &GenericRouterContext { use_router_internal(cx) .as_ref() diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs index fc15a0381..76cfc2068 100644 --- a/packages/router/src/lib.rs +++ b/packages/router/src/lib.rs @@ -25,9 +25,11 @@ mod components { } mod contexts { + pub(crate) mod navigator; pub(crate) mod outlet; pub(crate) mod router; - pub use router::*; + pub use navigator::*; + pub(crate) use router::*; } mod router_cfg; @@ -37,10 +39,13 @@ mod history; /// Hooks for interacting with the router in components. mod hooks { mod use_router; - pub use use_router::*; + pub(crate) use use_router::*; mod use_route; pub use use_route::*; + + mod use_navigator; + pub use use_navigator::*; } /// A collection of useful items most applications might need.