From 64399794c0c52e9e2a2a79e5108693e6b45cadcd Mon Sep 17 00:00:00 2001 From: Adrian Wannenmacher Date: Tue, 13 Dec 2022 10:46:30 +0100 Subject: [PATCH] document hooks --- packages/router/Cargo.toml | 1 + packages/router/src/hooks/use_navigate.rs | 53 ++++++++++++ packages/router/src/hooks/use_route.rs | 52 ++++++++++++ packages/router/src/hooks/use_router.rs | 99 +++++++++++++++++++++++ packages/router/src/lib.rs | 6 ++ 5 files changed, 211 insertions(+) diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 219684379..47295ace9 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -28,3 +28,4 @@ serde = ["dioxus-router-core/serde"] [dev-dependencies] dioxus = { path = "../dioxus" } dioxus-desktop = { path = "../desktop" } +dioxus-ssr = { path = "../ssr" } diff --git a/packages/router/src/hooks/use_navigate.rs b/packages/router/src/hooks/use_navigate.rs index 20e5f617c..134c0aa83 100644 --- a/packages/router/src/hooks/use_navigate.rs +++ b/packages/router/src/hooks/use_navigate.rs @@ -4,6 +4,59 @@ use log::error; use crate::utils::use_router_internal::use_router_internal; +/// A hook that allows for programmatic navigation. +/// +/// # Return values +/// - [`None`], when the calling component is not nested within another component calling the +/// [`use_router`] hook. +/// - Otherwise [`Some`]. +/// +/// # Panic +/// - When the calling component is not nested within another component calling the [`use_router`] +/// hook, but only in debug builds. +/// +/// # Example +/// ```rust +/// # use dioxus::prelude::*; +/// # use dioxus_router::prelude::*; +/// fn App(cx: Scope) -> Element { +/// let (state, _) = use_router( +/// &cx, +/// &|| RouterConfiguration { +/// synchronous: true, // asynchronicity not needed for doc test +/// ..Default::default() +/// }, +/// &|| Segment::content(comp(Redirect)).fixed("content", comp(Content)) +/// ); +/// +/// render! { +/// h1 { "App" } +/// Outlet { } +/// } +/// } +/// +/// fn Redirect(cx: Scope) -> Element { +/// let nav = use_navigate(&cx).unwrap(); +/// nav.push("/content"); +/// render! { () } +/// } +/// +/// fn Content(cx: Scope) -> Element { +/// render! { +/// p { "Content" } +/// } +/// } +/// # +/// # let mut vdom = VirtualDom::new(App); +/// # +/// # // first render with Redirect component +/// # let _ = vdom.rebuild(); +/// # assert_eq!(dioxus_ssr::render(&vdom), "

App

"); +/// # +/// # // second render with Content component +/// # let _ = vdom.rebuild(); +/// # assert_eq!(dioxus_ssr::render(&vdom), "

App

Content

"); +/// ``` #[must_use] pub fn use_navigate(cx: &ScopeState) -> Option> { match use_router_internal(cx) { diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index 1caff8d52..9f1a2c5b3 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -5,6 +5,58 @@ use log::error; use crate::utils::use_router_internal::use_router_internal; +/// A hook that provides access to information about the current routing location. +/// +/// # Return values +/// - [`None`], when the calling component is not nested within another component calling the +/// [`use_router`] hook. +/// - Otherwise [`Some`]. +/// +/// # Important usage information +/// Make sure to [`drop`] the returned [`RwLockReadGuard`] when done rendering. Otherwise the router +/// will be frozen. +/// +/// # Panic +/// - When the calling component is not nested within another component calling the [`use_router`] +/// hook, but only in debug builds. +/// +/// # Example +/// ```rust +/// # use dioxus::prelude::*; +/// # use dioxus_router::{history::*, prelude::*}; +/// fn App(cx: Scope) -> Element { +/// use_router( +/// &cx, +/// &|| RouterConfiguration { +/// synchronous: true, // asynchronicity not needed for doc test +/// history: Box::new(MemoryHistory::with_initial_path("/some/path").unwrap()), +/// ..Default::default() +/// }, +/// &|| Segment::empty() +/// ); +/// +/// render! { +/// h1 { "App" } +/// Content { } +/// } +/// } +/// +/// fn Content(cx: Scope) -> Element { +/// let state = use_route(&cx).unwrap(); +/// let path = state.path.clone(); +/// +/// render! { +/// h2 { "Current Path" } +/// p { "{path}" } +/// } +/// } +/// # +/// # let mut vdom = VirtualDom::new(App); +/// # let _ = vdom.rebuild(); +/// # assert_eq!(dioxus_ssr::render(&vdom), "

App

Current Path

/some/path

") +/// ``` +/// +/// [`use_router`]: super::use_router #[must_use] pub fn use_route<'a>(cx: &'a ScopeState) -> Option>> { match use_router_internal(cx) { diff --git a/packages/router/src/hooks/use_router.rs b/packages/router/src/hooks/use_router.rs index 9575380cb..4ae01b524 100644 --- a/packages/router/src/hooks/use_router.rs +++ b/packages/router/src/hooks/use_router.rs @@ -16,6 +16,52 @@ use crate::{ }, }; +/// The basic building block required for all other router components and hooks. +/// +/// This manages a [`dioxus_router_core::RouterService`], which in turn is required for basically +/// all router functionality. All other components and hooks provided by [`dioxus_router`](crate) +/// will only work as/in components nested within a component calling [`use_router`]. +/// +/// Components calling [`use_router`] should not be nested within each other. +/// +/// # Return values +/// This hook returns the current router state and a navigator. For more information about the +/// state, see the [`use_route`](super::use_route) hook. For more information about the +/// [`Navigator`], see its own documentation and the [`use_navigate`](super::use_navigate) hook. +/// +// # Panic +// - When used within a component, that is nested inside another component calling [`use_router`], +// but only in debug builds. +/// +/// # Example +/// ```rust,no_run +/// # use dioxus::prelude::*; +/// # use dioxus_router::prelude::*; +/// fn App(cx: Scope) -> Element { +/// let (_, _) = use_router( +/// &cx, +/// &|| RouterConfiguration { +/// synchronous: true, // asynchronicity not needed for doc test +/// ..Default::default() +/// }, +/// &|| Segment::content(comp(Content)) +/// ); +/// +/// render! { +/// h1 { "App" } +/// Outlet { } +/// } +/// } +/// +/// fn Content(cx: Scope) -> Element { +/// render! { +/// p { "Some content" } +/// } +/// } +/// # let mut vdom = VirtualDom::new(App); +/// # let _ = vdom.rebuild(); +/// # assert_eq!(dioxus_ssr::render(&vdom), "

App

Some content

"); +/// ``` pub fn use_router<'a>( cx: &'a ScopeState, cfg: &dyn Fn() -> RouterConfiguration, @@ -25,6 +71,8 @@ pub fn use_router<'a>( Navigator, ) { let (service, state, sender) = cx.use_hook(|| { + // todo: ensure no router context is found + let cfg = cfg(); let content = content(); @@ -45,6 +93,7 @@ pub fn use_router<'a>( ( if cfg.synchronous { + service.init(); Some(service) } else { cx.spawn(async move { service.run().await }); @@ -69,12 +118,62 @@ pub fn use_router<'a>( ) } +/// Global configuration options for the router. +/// +/// This implements [`Default`], so you can use it like this: +/// ```rust +/// # use dioxus_router::prelude::RouterConfiguration; +/// let cfg = RouterConfiguration { +/// synchronous: false, +/// ..Default::default() +/// }; +/// ``` pub struct RouterConfiguration { + /// A component to render when an external navigation fails. + /// + /// 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`]. pub failure_external_navigation: ContentAtom, + /// 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`]. pub failure_named_navigation: ContentAtom, + /// 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`]. pub failure_redirection_limit: ContentAtom, + /// The [`HistoryProvider`] the router should use. + /// + /// Defaults to a default [`MemoryHistory`]. pub history: Box, + /// 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 + /// updated. + /// + /// If the callback returns a [`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. + /// + /// The callback is called no more than once per rerouting. It will not be called if a + /// navigation failure occurs. + /// + /// Defaults to [`None`]. + /// + /// [`NavigationTarget`]: dioxus_router_core::navigation::NavigationTarget pub on_update: Option>, + /// Whether the router should run in synchronous mode. + /// + /// If [`true`], the router will only update its state whenever the component using the + /// [`use_router`] hook rerenders. If [`false`], an asynchronous task is launched and the router + /// will update whenever it receives new input. + /// + /// Defaults to [`false`]. pub synchronous: bool, } diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs index 47e179c0a..30f3a610f 100644 --- a/packages/router/src/lib.rs +++ b/packages/router/src/lib.rs @@ -15,6 +15,12 @@ mod contexts { pub(crate) mod router; } +pub mod history { + pub use dioxus_router_core::history::*; +} + +/// Hooks for interacting with the router in components. +#[forbid(missing_docs)] pub mod hooks { mod use_navigate; pub use use_navigate::*;