use crate::{ParsedRoute, RouteContext, RouterCore, RouterService}; use dioxus::core::{ScopeId, ScopeState}; use std::{borrow::Cow, str::FromStr, sync::Arc}; use url::Url; /// This hook provides access to information about the current location in the /// context of a [`Router`]. If this function is called outside of a `Router` /// component it will panic. pub fn use_route(cx: &ScopeState) -> &UseRoute { let handle = cx.use_hook(|_| { let router = cx .consume_context::() .expect("Cannot call use_route outside the scope of a Router component"); let route_context = cx.consume_context::(); router.subscribe_onchange(cx.scope_id()); UseRouteListener { state: UseRoute { route_context, route: router.current_location(), }, router, scope: cx.scope_id(), } }); handle.state.route = handle.router.current_location(); &handle.state } /// A handle to the current location of the router. pub struct UseRoute { pub(crate) route: Arc, /// If `use_route` is used inside a `Route` component this has some context otherwise `None`. pub(crate) route_context: Option, } impl UseRoute { /// Get the underlying [`Url`] of the current location. pub fn url(&self) -> &Url { &self.route.url } /// Get the first query parameter given the parameter name. /// /// If you need to get more than one parameter, use [`query_pairs`] on the [`Url`] instead. #[cfg(feature = "query")] pub fn query(&self) -> Option { let query = self.url().query()?; serde_urlencoded::from_str(query).ok() } /// Get the first query parameter given the parameter name. /// /// If you need to get more than one parameter, use [`query_pairs`] on the [`Url`] instead. pub fn query_param(&self, param: &str) -> Option> { self.route .url .query_pairs() .find(|(k, _)| k == param) .map(|(_, v)| v) } /// Returns the nth segment in the path. Paths that end with a slash have /// the slash removed before determining the segments. If the path has /// fewer segments than `n` then this method returns `None`. pub fn nth_segment(&self, n: usize) -> Option<&str> { self.route.url.path_segments()?.nth(n) } /// Returns the last segment in the path. Paths that end with a slash have /// the slash removed before determining the segments. The root path, `/`, /// will return an empty string. pub fn last_segment(&self) -> Option<&str> { self.route.url.path_segments()?.last() } /// Get the named parameter from the path, as defined in your router. The /// value will be parsed into the type specified by `T` by calling /// `value.parse::()`. This method returns `None` if the named /// parameter does not exist in the current path. pub fn segment(&self, name: &str) -> Option<&str> { let total_route = match self.route_context { None => self.route.url.path(), Some(ref ctx) => &ctx.total_route, }; let index = total_route .trim_start_matches('/') .split('/') .position(|segment| segment.starts_with(':') && &segment[1..] == name)?; self.route.url.path_segments()?.nth(index) } /// Get the named parameter from the path, as defined in your router. The /// value will be parsed into the type specified by `T` by calling /// `value.parse::()`. This method returns `None` if the named /// parameter does not exist in the current path. pub fn parse_segment(&self, name: &str) -> Option> where T: FromStr, { self.segment(name).map(|value| value.parse::()) } } // The entire purpose of this struct is to unubscribe this component when it is unmounted. // The UseRoute can be cloned into async contexts, so we can't rely on its drop to unubscribe. // Instead, we hide the drop implementation on this private type exclusive to the hook, // and reveal our cached version of UseRoute to the component. struct UseRouteListener { state: UseRoute, router: Arc, scope: ScopeId, } impl Drop for UseRouteListener { fn drop(&mut self) { self.router.unsubscribe_onchange(self.scope) } }