From e22ba5b1e52fe00e08e0b6ac0abae29b6068b8f0 Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Sat, 8 Jan 2022 13:44:53 -0600 Subject: [PATCH 01/10] Add title prop to Link Every element can have a title, but it's particularly useful on links, so I think making it an explicit option is worthwhile. --- packages/router/src/components/link.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index 634696bf4..dc807aa1a 100644 --- a/packages/router/src/components/link.rs +++ b/packages/router/src/components/link.rs @@ -31,6 +31,9 @@ pub struct LinkProps<'a> { #[props(default, strip_option)] id: Option<&'a str>, + #[props(default, strip_option)] + title: Option<&'a str>, + children: Element<'a>, #[props(default)] @@ -44,6 +47,7 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element { href: "{cx.props.to}", class: format_args!("{}", cx.props.class.unwrap_or("")), id: format_args!("{}", cx.props.id.unwrap_or("")), + title: format_args!("{}", cx.props.title.unwrap_or("")), prevent_default: "onclick", onclick: move |_| service.push_route(cx.props.to), From d367e0f89f1b31484bde42efaf10c3531aed1d64 Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Sat, 8 Jan 2022 13:53:45 -0600 Subject: [PATCH 02/10] Fix typo in RouterService struct's "registered_routes" field name This was "registerd_routes", missing an "e". --- packages/router/src/service.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index 7f069b6ba..ae7c1ff05 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -10,7 +10,7 @@ use dioxus_core::ScopeId; pub struct RouterService { pub(crate) regen_route: Rc, history: Rc>, - registerd_routes: RefCell, + registered_routes: RefCell, slots: Rc>>, root_found: Rc>, cur_root: RefCell, @@ -53,7 +53,7 @@ impl RouterService { }); Self { - registerd_routes: RefCell::new(RouteSlot::Routes { + registered_routes: RefCell::new(RouteSlot::Routes { partial: String::from("/"), total: String::from("/"), rest: Vec::new(), From 3a5b417ad1639a31e9aac9f41b05d8b3f074b128 Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Sat, 8 Jan 2022 13:55:13 -0600 Subject: [PATCH 03/10] Add more trace messages to the RouterService code I was trying to debug some issues with my routes and this additional tracing was quite helpful. --- packages/router/src/components/route.rs | 1 + packages/router/src/service.rs | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/router/src/components/route.rs b/packages/router/src/components/route.rs index 2d9a270b8..129953eea 100644 --- a/packages/router/src/components/route.rs +++ b/packages/router/src/components/route.rs @@ -30,6 +30,7 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element { Some(ctx) => ctx.total_route.to_string(), None => cx.props.to.to_string(), }; + log::trace!("total route for {} is {}", cx.props.to, total_route); // provide our route context let route_context = cx.provide_context(RouteContext { diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index ae7c1ff05..7eddeb69d 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -46,8 +46,8 @@ impl RouterService { let listener = history.listen(move || { _root_found.set(false); // checking if the route is valid is cheap, so we do it - for (slot, _) in _slots.borrow_mut().iter().rev() { - log::trace!("regenerating slot {:?}", slot); + for (slot, root) in _slots.borrow_mut().iter().rev() { + log::trace!("regenerating slot {:?} for root '{}'", slot, root); regen(*slot); } }); @@ -68,20 +68,25 @@ impl RouterService { } pub fn push_route(&self, route: &str) { + log::trace!("Pushing route: {}", route); self.history.borrow_mut().push(route); } pub fn register_total_route(&self, route: String, scope: ScopeId, fallback: bool) { + log::trace!("Registered route '{}' with scope id {:?}", route, scope); self.slots.borrow_mut().push((scope, route)); } pub fn should_render(&self, scope: ScopeId) -> bool { + log::trace!("Should render scope id {:?}?", scope); if self.root_found.get() { + log::trace!(" no - because root_found is true"); return false; } let location = self.history.borrow().location(); let path = location.path(); + log::trace!(" current path is '{}'", path); let roots = self.slots.borrow(); @@ -90,14 +95,22 @@ impl RouterService { // fallback logic match root { Some((_id, route)) => { + log::trace!( + " matched given scope id {:?} with route root '{}'", + scope, + route, + ); if route == path { + log::trace!(" and it matches the current path '{}'", path); self.root_found.set(true); true } else { if route == "" { + log::trace!(" and the route is the root, so we will use that without a better match"); self.root_found.set(true); true } else { + log::trace!(" and the route '{}' is not the root nor does it match the current path", route); false } } From 3c6142fb9d8b5f715adf0fb30c2f3534b9ecd923 Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Sun, 9 Jan 2022 23:44:37 -0600 Subject: [PATCH 04/10] Commit WIP on router There are a few different changes in here that probably need to be picked apart. I'm sure much of this is wrong. * Fix missing `dyn` that compiler complained about in router.rs * Make UseRoute store a `Rc` rather than a string so we can get information out of the router like current location. * Implement `UseRoute`'s nth_segment and last_segment methods. I changed the return type to a String because of the above. * Remove some unused imports in platform/mod.rs and service.rs * Implement the `use_route` fn. It panics if called outside a Router { } (I think). I think that makes sense. * Add a `current_location` method to `RouterService` that returns the current location. I needed this both for the `UseRoute` implementation and _also_ so I could get at this in my webapp code. I think having some way to get this will be useful for others, whether or not this exact API is used. In my case, I want to compare the current path to the `to` path of a `Link` so I can use a different class for that `Link` if it is the currently active page. --- packages/router/src/components/router.rs | 2 +- packages/router/src/hooks/use_route.rs | 40 ++++++++++++++++++++---- packages/router/src/platform/mod.rs | 2 -- packages/router/src/service.rs | 7 +++-- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/packages/router/src/components/router.rs b/packages/router/src/components/router.rs index 85f0a62a3..3d9f0c83a 100644 --- a/packages/router/src/components/router.rs +++ b/packages/router/src/components/router.rs @@ -12,7 +12,7 @@ pub struct RouterProps<'a> { children: Element<'a>, #[props(default, strip_option)] - onchange: Option<&'a Fn(&'a str)>, + onchange: Option<&'a dyn Fn(&'a str)>, } #[allow(non_snake_case)] diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index 8307ed1f0..bf76d0975 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -1,7 +1,11 @@ use dioxus_core::ScopeState; +use gloo::history::Location; +use std::rc::Rc; + +use crate::RouterService; pub struct UseRoute<'a> { - cur_route: String, + router: Rc, cx: &'a ScopeState, } @@ -11,20 +15,44 @@ impl<'a> UseRoute<'a> { todo!() } - pub fn nth_segment(&self, n: usize) -> Option<&str> { - todo!() + pub fn nth_segment(&self, n: usize) -> Option { + let mut segments = self.path_segments(); + let len = segments.len(); + if len - 1 < n { + return None; + } + Some(segments.remove(n)) } - pub fn last_segment(&self) -> Option<&'a str> { - todo!() + pub fn last_segment(&self) -> Option { + let mut segments = self.path_segments(); + let len = segments.len(); + if len == 0 { + return None; + } + Some(segments.remove(len - 1)) } /// Parse the segments of the URL, using named parameters (defined in your router) pub fn segment(&self, name: &str) -> Option<&T> { todo!() } + + pub fn current_location(&self) -> Location { + self.router.current_location() + } + + fn path_segments(&self) -> Vec { + let location = self.router.current_location(); + let stripped = &location.path()[1..]; + stripped.split('/').map(str::to_string).collect::>() + } } pub fn use_route<'a>(cx: &'a ScopeState) -> UseRoute<'a> { - todo!() + let router = cx + .consume_context::() + .expect("Cannot call use_route outside the scope of a Router component") + .clone(); + UseRoute { router, cx } } diff --git a/packages/router/src/platform/mod.rs b/packages/router/src/platform/mod.rs index bbb0e7c18..7afc1316a 100644 --- a/packages/router/src/platform/mod.rs +++ b/packages/router/src/platform/mod.rs @@ -1,5 +1,3 @@ -use url::Url; - pub trait RouterProvider { fn get_current_route(&self) -> String; fn subscribe_to_route_changes(&self, callback: Box); diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index 7eddeb69d..c0b383e81 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -1,7 +1,6 @@ -use gloo::history::{BrowserHistory, History, HistoryListener}; +use gloo::history::{BrowserHistory, History, HistoryListener, Location}; use std::{ cell::{Cell, RefCell}, - collections::HashMap, rc::Rc, }; @@ -118,6 +117,10 @@ impl RouterService { None => false, } } + + pub fn current_location(&self) -> Location { + self.history.borrow().location().clone() + } } pub struct RouterCfg { From f8a7e1cd8255fd7d0116384247e0e305bb73bf3d Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Mon, 10 Jan 2022 11:30:26 -0600 Subject: [PATCH 05/10] Implement router matching for path parameters We don't want to have the router just always match paths as exact strings. If a path contains a parameter like "/thing/:id" then the ":id" portion of the route should match _any_ string, not a literal ":id". --- packages/router/src/service.rs | 60 ++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index c0b383e81..9525ac44c 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -72,8 +72,9 @@ impl RouterService { } pub fn register_total_route(&self, route: String, scope: ScopeId, fallback: bool) { - log::trace!("Registered route '{}' with scope id {:?}", route, scope); - self.slots.borrow_mut().push((scope, route)); + let clean = clean_route(route); + log::trace!("Registered route '{}' with scope id {:?}", clean, scope); + self.slots.borrow_mut().push((scope, clean)); } pub fn should_render(&self, scope: ScopeId) -> bool { @@ -99,7 +100,7 @@ impl RouterService { scope, route, ); - if route == path { + if route_matches_path(route, path) { log::trace!(" and it matches the current path '{}'", path); self.root_found.set(true); true @@ -123,6 +124,59 @@ impl RouterService { } } +fn clean_route(route: String) -> String { + if route.as_str() == "/" { + return route; + } + route.trim_end_matches('/').to_string() +} + +fn clean_path(path: &str) -> &str { + if path == "/" { + return path; + } + path.trim_end_matches('/') +} + +fn route_matches_path(route: &str, path: &str) -> bool { + let route_pieces = route.split('/').collect::>(); + let path_pieces = clean_path(path).split('/').collect::>(); + + log::trace!( + " checking route pieces {:?} vs path pieces {:?}", + route_pieces, + path_pieces, + ); + + if route_pieces.len() != path_pieces.len() { + log::trace!(" the routes are different lengths"); + return false; + } + + for (i, r) in route_pieces.iter().enumerate() { + log::trace!(" checking route piece '{}' vs path", r); + // If this is a parameter then it matches as long as there's + // _any_thing in that spot in the path. + if r.starts_with(':') { + log::trace!( + " route piece '{}' starts with a colon so it matches anything", + r, + ); + continue; + } + log::trace!( + " route piece '{}' must be an exact match for path piece '{}'", + r, + path_pieces[i], + ); + if path_pieces[i] != *r { + return false; + } + } + + return true; +} + pub struct RouterCfg { initial_route: String, } From c9408da7310423b1676fab6d41635f9a8000d89e Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Tue, 11 Jan 2022 10:47:17 -0600 Subject: [PATCH 06/10] Implement UseRoute segment method This relies on the RouterService to capture path params when it does path matching. --- packages/router/src/hooks/use_route.rs | 17 ++++++++++---- packages/router/src/service.rs | 31 ++++++++++++++------------ 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index bf76d0975..5ed056f65 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -1,6 +1,6 @@ use dioxus_core::ScopeState; use gloo::history::Location; -use std::rc::Rc; +use std::{rc::Rc, str::FromStr}; use crate::RouterService; @@ -33,9 +33,18 @@ impl<'a> UseRoute<'a> { Some(segments.remove(len - 1)) } - /// Parse the segments of the URL, using named parameters (defined in your router) - pub fn segment(&self, name: &str) -> Option<&T> { - todo!() + /// 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> + where + T: FromStr, + { + self.router + .current_path_params() + .get(name) + .and_then(|v| Some(v.parse::())) } pub fn current_location(&self) -> Location { diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index 9525ac44c..2fac8509b 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -1,6 +1,7 @@ use gloo::history::{BrowserHistory, History, HistoryListener, Location}; use std::{ - cell::{Cell, RefCell}, + cell::{Cell, Ref, RefCell}, + collections::HashMap, rc::Rc, }; @@ -9,10 +10,9 @@ use dioxus_core::ScopeId; pub struct RouterService { pub(crate) regen_route: Rc, history: Rc>, - registered_routes: RefCell, slots: Rc>>, root_found: Rc>, - cur_root: RefCell, + cur_path_params: Rc>>, listener: HistoryListener, } @@ -52,16 +52,11 @@ impl RouterService { }); Self { - registered_routes: RefCell::new(RouteSlot::Routes { - partial: String::from("/"), - total: String::from("/"), - rest: Vec::new(), - }), root_found, history: Rc::new(RefCell::new(history)), regen_route, slots, - cur_root: RefCell::new(path.to_string()), + cur_path_params: Rc::new(RefCell::new(HashMap::new())), listener, } } @@ -100,9 +95,10 @@ impl RouterService { scope, route, ); - if route_matches_path(route, path) { + if let Some(params) = route_matches_path(route, path) { log::trace!(" and it matches the current path '{}'", path); self.root_found.set(true); + *self.cur_path_params.borrow_mut() = params; true } else { if route == "" { @@ -122,6 +118,10 @@ impl RouterService { pub fn current_location(&self) -> Location { self.history.borrow().location().clone() } + + pub fn current_path_params(&self) -> Ref> { + self.cur_path_params.borrow() + } } fn clean_route(route: String) -> String { @@ -138,7 +138,7 @@ fn clean_path(path: &str) -> &str { path.trim_end_matches('/') } -fn route_matches_path(route: &str, path: &str) -> bool { +fn route_matches_path(route: &str, path: &str) -> Option> { let route_pieces = route.split('/').collect::>(); let path_pieces = clean_path(path).split('/').collect::>(); @@ -150,9 +150,10 @@ fn route_matches_path(route: &str, path: &str) -> bool { if route_pieces.len() != path_pieces.len() { log::trace!(" the routes are different lengths"); - return false; + return None; } + let mut matches = HashMap::new(); for (i, r) in route_pieces.iter().enumerate() { log::trace!(" checking route piece '{}' vs path", r); // If this is a parameter then it matches as long as there's @@ -162,6 +163,8 @@ fn route_matches_path(route: &str, path: &str) -> bool { " route piece '{}' starts with a colon so it matches anything", r, ); + let param = &r[1..]; + matches.insert(param.to_string(), path_pieces[i].to_string()); continue; } log::trace!( @@ -170,11 +173,11 @@ fn route_matches_path(route: &str, path: &str) -> bool { path_pieces[i], ); if path_pieces[i] != *r { - return false; + return None; } } - return true; + Some(matches) } pub struct RouterCfg { From e06eac1ce5c6bb0a6680482574d82b16a141a626 Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Tue, 11 Jan 2022 11:27:02 -0600 Subject: [PATCH 07/10] More WIP router implementation --- examples/router.rs | 12 +++++-- packages/router/src/hooks/use_route.rs | 46 +++++++++++++++++--------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/examples/router.rs b/examples/router.rs index bd6d51881..121514423 100644 --- a/examples/router.rs +++ b/examples/router.rs @@ -2,6 +2,7 @@ use dioxus::prelude::*; use dioxus::router::{Link, Route, Router}; +use serde::Deserialize; fn main() { dioxus::desktop::launch(app); @@ -30,7 +31,7 @@ fn app(cx: Scope) -> Element { } fn BlogPost(cx: Scope) -> Element { - let post = dioxus::router::use_route(&cx).last_segment()?; + let post = dioxus::router::use_route(&cx).last_segment(); cx.render(rsx! { div { @@ -40,9 +41,14 @@ fn BlogPost(cx: Scope) -> Element { }) } +#[derive(Deserialize)] +struct Query { + bold: bool, +} + fn User(cx: Scope) -> Element { - let post = dioxus::router::use_route(&cx).last_segment()?; - let bold = dioxus::router::use_route(&cx).param::("bold"); + let post = dioxus::router::use_route(&cx).last_segment(); + let query = dioxus::router::use_route(&cx).query::(); cx.render(rsx! { div { diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index 5ed056f65..97dd6c048 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -1,20 +1,29 @@ use dioxus_core::ScopeState; -use gloo::history::Location; +use gloo::history::{HistoryResult, Location}; +use serde::de::DeserializeOwned; use std::{rc::Rc, str::FromStr}; use crate::RouterService; -pub struct UseRoute<'a> { +/// This struct provides is a wrapper around the internal router +/// implementation, with methods for getting information about the current +/// route. +pub struct UseRoute { router: Rc, - cx: &'a ScopeState, } -impl<'a> UseRoute<'a> { - /// Parse the query part of the URL - pub fn param(&self, param: &str) -> Option<&T> { - todo!() +impl UseRoute { + /// This method simply calls the [`Location::query`] method. + pub fn query(&self) -> HistoryResult + where + T: DeserializeOwned, + { + self.current_location().query::() } + /// 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 { let mut segments = self.path_segments(); let len = segments.len(); @@ -24,13 +33,12 @@ impl<'a> UseRoute<'a> { Some(segments.remove(n)) } - pub fn last_segment(&self) -> Option { + /// 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) -> String { let mut segments = self.path_segments(); - let len = segments.len(); - if len == 0 { - return None; - } - Some(segments.remove(len - 1)) + segments.remove(segments.len() - 1) } /// Get the named parameter from the path, as defined in your router. The @@ -47,21 +55,29 @@ impl<'a> UseRoute<'a> { .and_then(|v| Some(v.parse::())) } + /// Returns the [Location] for the current route. pub fn current_location(&self) -> Location { self.router.current_location() } fn path_segments(&self) -> Vec { let location = self.router.current_location(); + let path = location.path(); + if path == "/" { + return vec![String::new()]; + } let stripped = &location.path()[1..]; stripped.split('/').map(str::to_string).collect::>() } } -pub fn use_route<'a>(cx: &'a ScopeState) -> UseRoute<'a> { +/// 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 router = cx .consume_context::() .expect("Cannot call use_route outside the scope of a Router component") .clone(); - UseRoute { router, cx } + UseRoute { router } } From 81c094ed29fcdc5c6099492dd6ab09a59b79252c Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Fri, 14 Jan 2022 14:55:12 -0600 Subject: [PATCH 08/10] Fix handling of re-renders in the Router Previously the router just stored a `root_found` boolean after it picked a route. But on re-render it would just always return false from `should_render` if this was true. This just aborted routing after a future resolved (or anything else that triggered a re-render). Now we store the matching ScopeId and check that against our routes in a re-render so we actually do re-render the matching route. --- packages/router/src/service.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index 2fac8509b..00642414f 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -11,7 +11,7 @@ pub struct RouterService { pub(crate) regen_route: Rc, history: Rc>, slots: Rc>>, - root_found: Rc>, + root_found: Rc>>, cur_path_params: Rc>>, listener: HistoryListener, } @@ -39,11 +39,11 @@ impl RouterService { let _slots = slots.clone(); - let root_found = Rc::new(Cell::new(false)); + let root_found = Rc::new(Cell::new(None)); let regen = regen_route.clone(); let _root_found = root_found.clone(); let listener = history.listen(move || { - _root_found.set(false); + _root_found.set(None); // checking if the route is valid is cheap, so we do it for (slot, root) in _slots.borrow_mut().iter().rev() { log::trace!("regenerating slot {:?} for root '{}'", slot, root); @@ -74,8 +74,13 @@ impl RouterService { pub fn should_render(&self, scope: ScopeId) -> bool { log::trace!("Should render scope id {:?}?", scope); - if self.root_found.get() { - log::trace!(" no - because root_found is true"); + if let Some(root_id) = self.root_found.get() { + log::trace!(" we already found a root with scope id {:?}", root_id); + if root_id == scope { + log::trace!(" yes - it's a match"); + return true; + } + log::trace!(" no - it's not a match"); return false; } @@ -89,7 +94,7 @@ impl RouterService { // fallback logic match root { - Some((_id, route)) => { + Some((id, route)) => { log::trace!( " matched given scope id {:?} with route root '{}'", scope, @@ -97,13 +102,13 @@ impl RouterService { ); if let Some(params) = route_matches_path(route, path) { log::trace!(" and it matches the current path '{}'", path); - self.root_found.set(true); + self.root_found.set(Some(*id)); *self.cur_path_params.borrow_mut() = params; true } else { if route == "" { log::trace!(" and the route is the root, so we will use that without a better match"); - self.root_found.set(true); + self.root_found.set(Some(*id)); true } else { log::trace!(" and the route '{}' is not the root nor does it match the current path", route); From 9b282d877ba0fe2463687cde7ea899b7f8510425 Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Fri, 14 Jan 2022 15:40:01 -0600 Subject: [PATCH 09/10] Don't expect all components to have a scope in ScopeArena.ensure_drop_safety Not all components will be mounted when using a Router, so we cannot assume all components have a scope. --- packages/core/src/scopes.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 65511470e..5b8f527c5 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -198,12 +198,9 @@ impl ScopeArena { // run the hooks (which hold an &mut Reference) // recursively call ensure_drop_safety on all children items.borrowed_props.drain(..).for_each(|comp| { - let scope_id = comp - .scope - .get() - .expect("VComponents should be associated with a valid Scope"); - - self.ensure_drop_safety(scope_id); + if let Some(scope_id) = comp.scope.get() { + self.ensure_drop_safety(scope_id); + } drop(comp.props.take()); }); From 64080588d02c54a1d380116cbecdd17de16d2392 Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Fri, 14 Jan 2022 15:44:18 -0600 Subject: [PATCH 10/10] Add a warning when Link it called outside of a Router context --- packages/router/src/components/link.rs | 31 ++++++++++++++++---------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index dc807aa1a..6a1b671ab 100644 --- a/packages/router/src/components/link.rs +++ b/packages/router/src/components/link.rs @@ -41,18 +41,25 @@ pub struct LinkProps<'a> { } pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element { - let service = cx.consume_context::()?; - cx.render(rsx! { - a { - href: "{cx.props.to}", - class: format_args!("{}", cx.props.class.unwrap_or("")), - id: format_args!("{}", cx.props.id.unwrap_or("")), - title: format_args!("{}", cx.props.title.unwrap_or("")), + log::debug!("render Link to {}", cx.props.to); + if let Some(service) = cx.consume_context::() { + return cx.render(rsx! { + a { + href: "{cx.props.to}", + class: format_args!("{}", cx.props.class.unwrap_or("")), + id: format_args!("{}", cx.props.id.unwrap_or("")), + title: format_args!("{}", cx.props.title.unwrap_or("")), - prevent_default: "onclick", - onclick: move |_| service.push_route(cx.props.to), + prevent_default: "onclick", + onclick: move |_| service.push_route(cx.props.to), - &cx.props.children - } - }) + &cx.props.children + } + }); + } + log::warn!( + "Attempted to create a Link to {} outside of a Router context", + cx.props.to, + ); + None }