fix: provide parent params to children (closes #2719)

This commit is contained in:
Greg Johnston 2024-08-11 17:20:25 -04:00
parent 7ed4d08dab
commit 0222182286
4 changed files with 70 additions and 25 deletions

View file

@ -9,7 +9,7 @@ use any_spawner::Executor;
use either_of::{Either, EitherOf3}; use either_of::{Either, EitherOf3};
use futures::FutureExt; use futures::FutureExt;
use reactive_graph::{ use reactive_graph::{
computed::ScopedFuture, computed::{ArcMemo, ScopedFuture},
owner::{provide_context, Owner}, owner::{provide_context, Owner},
signal::ArcRwSignal, signal::ArcRwSignal,
traits::{ReadUntracked, Set}, traits::{ReadUntracked, Set},
@ -202,6 +202,7 @@ where
.map(|n| n.to_params().into_iter().collect()) .map(|n| n.to_params().into_iter().collect())
.unwrap_or_default(), .unwrap_or_default(),
); );
let params_memo = ArcMemo::from(params.clone());
match new_match { match new_match {
None => Rc::new(RefCell::new(FlatRoutesViewState { None => Rc::new(RefCell::new(FlatRoutesViewState {
@ -224,10 +225,9 @@ where
let mut view = Box::pin(owner.with(|| { let mut view = Box::pin(owner.with(|| {
ScopedFuture::new({ ScopedFuture::new({
let params = params.clone();
let url = url.clone(); let url = url.clone();
async move { async move {
provide_context(params); provide_context(params_memo);
provide_context(url); provide_context(url);
view.choose().await view.choose().await
} }
@ -322,6 +322,7 @@ where
let owner = outer_owner.child(); let owner = outer_owner.child();
let url = ArcRwSignal::new(url_snapshot.to_owned()); let url = ArcRwSignal::new(url_snapshot.to_owned());
let params = ArcRwSignal::new(matched_params); let params = ArcRwSignal::new(matched_params);
let params_memo = ArcMemo::from(params.clone());
let old_owner = mem::replace(&mut initial_state.owner, owner.clone()); let old_owner = mem::replace(&mut initial_state.owner, owner.clone());
let old_url = mem::replace(&mut initial_state.url, url.clone()); let old_url = mem::replace(&mut initial_state.url, url.clone());
let old_params = let old_params =
@ -336,7 +337,7 @@ where
None => { None => {
owner.with(|| { owner.with(|| {
provide_context(url); provide_context(url);
provide_context(params); provide_context(params_memo);
EitherOf3::B(fallback()) EitherOf3::B(fallback())
.rebuild(&mut state.borrow_mut().view) .rebuild(&mut state.borrow_mut().view)
}); });
@ -358,7 +359,7 @@ where
let state = Rc::clone(state); let state = Rc::clone(state);
async move { async move {
provide_context(url); provide_context(url);
provide_context(params); provide_context(params_memo);
let view = let view =
if let Some(set_is_routing) = set_is_routing { if let Some(set_is_routing) = set_is_routing {
set_is_routing.set(true); set_is_routing.set(true);
@ -439,6 +440,7 @@ where
.map(|n| n.to_params().into_iter().collect::<ParamsMap>()) .map(|n| n.to_params().into_iter().collect::<ParamsMap>())
.unwrap_or_default(), .unwrap_or_default(),
); );
let params_memo = ArcMemo::from(params.clone());
let view = match new_match { let view = match new_match {
None => Either::Left((self.fallback)()), None => Either::Left((self.fallback)()),
Some(matched) => { Some(matched) => {
@ -447,7 +449,7 @@ where
.with(|| { .with(|| {
ScopedFuture::new(async move { ScopedFuture::new(async move {
provide_context(url); provide_context(url);
provide_context(params); provide_context(params_memo);
view.choose().await view.choose().await
}) })
}) })
@ -594,6 +596,7 @@ where
.map(|n| n.to_params().into_iter().collect()) .map(|n| n.to_params().into_iter().collect())
.unwrap_or_default(), .unwrap_or_default(),
); );
let params_memo = ArcMemo::from(params.clone());
match new_match { match new_match {
None => Rc::new(RefCell::new(FlatRoutesViewState { None => Rc::new(RefCell::new(FlatRoutesViewState {
@ -617,10 +620,9 @@ where
let mut view = Box::pin(owner.with(|| { let mut view = Box::pin(owner.with(|| {
ScopedFuture::new({ ScopedFuture::new({
let params = params.clone();
let url = url.clone(); let url = url.clone();
async move { async move {
provide_context(params); provide_context(params_memo);
provide_context(url); provide_context(url);
view.choose().await view.choose().await
} }

View file

@ -156,7 +156,7 @@ pub fn use_location() -> Location {
} }
#[track_caller] #[track_caller]
fn use_params_raw() -> ArcRwSignal<ParamsMap> { fn use_params_raw() -> ArcMemo<ParamsMap> {
use_context().expect( use_context().expect(
"Tried to access params outside the context of a matched <Route>.", "Tried to access params outside the context of a matched <Route>.",
) )
@ -165,9 +165,7 @@ fn use_params_raw() -> ArcRwSignal<ParamsMap> {
/// Returns a raw key-value map of route params. /// Returns a raw key-value map of route params.
#[track_caller] #[track_caller]
pub fn use_params_map() -> Memo<ParamsMap> { pub fn use_params_map() -> Memo<ParamsMap> {
// TODO this can be optimized in future to map over the signal, rather than cloning use_params_raw().into()
let params = use_params_raw();
Memo::new(move |_| params.get())
} }
/// Returns the current route params, parsed into the given type, or an error. /// Returns the current route params, parsed into the given type, or an error.

View file

@ -569,15 +569,27 @@ where
// the matched signal will also be updated on every match // the matched signal will also be updated on every match
// it's used for relative route resolution // it's used for relative route resolution
let matched = ArcRwSignal::new(self.as_matched().to_string()); let matched = ArcRwSignal::new(self.as_matched().to_string());
let matched_including_parents = { let (parent_params, parent_matches): (Vec<_>, Vec<_>) = outlets
let parents = outlets
.iter() .iter()
.map(|route| route.matched.clone()) .map(|route| (route.params.clone(), route.matched.clone()))
.collect::<Vec<_>>(); .unzip();
let params_including_parents = {
let params = params.clone();
ArcMemo::new({
move |_| {
parent_params
.iter()
.flat_map(|params| params.get().into_iter())
.chain(params.get())
.collect::<ParamsMap>()
}
})
};
let matched_including_parents = {
let matched = matched.clone(); let matched = matched.clone();
ArcMemo::new({ ArcMemo::new({
move |_| { move |_| {
parents parent_matches
.iter() .iter()
.map(|matched| matched.get()) .map(|matched| matched.get())
.chain(iter::once(matched.get())) .chain(iter::once(matched.get()))
@ -616,12 +628,11 @@ where
loaders.push(Box::pin(owner.with(|| { loaders.push(Box::pin(owner.with(|| {
ScopedFuture::new({ ScopedFuture::new({
let owner = outlet.owner.clone(); let owner = outlet.owner.clone();
let params = outlet.params.clone();
let url = outlet.url.clone(); let url = outlet.url.clone();
let matched = Matched(matched_including_parents); let matched = Matched(matched_including_parents);
let view_fn = Arc::clone(&outlet.view_fn); let view_fn = Arc::clone(&outlet.view_fn);
async move { async move {
provide_context(params); provide_context(params_including_parents);
provide_context(url); provide_context(url);
provide_context(matched); provide_context(matched);
view.preload().await; view.preload().await;
@ -667,11 +678,11 @@ where
outlets: &mut Vec<RouteContext<R>>, outlets: &mut Vec<RouteContext<R>>,
parent: &Owner, parent: &Owner,
) { ) {
let parent_matches = outlets let (parent_params, parent_matches): (Vec<_>, Vec<_>) = outlets
.iter() .iter()
.take(*items) .take(*items)
.map(|route| route.matched.clone()) .map(|route| (route.params.clone(), route.matched.clone()))
.collect::<Vec<_>>(); .unzip();
let current = outlets.get_mut(*items); let current = outlets.get_mut(*items);
match current { match current {
// if there's nothing currently in the routes at this point, build from here // if there's nothing currently in the routes at this point, build from here
@ -727,6 +738,18 @@ where
} }
}) })
}; };
let params_including_parents = {
let params = current.params.clone();
ArcMemo::new({
move |_| {
parent_params
.iter()
.flat_map(|params| params.get().into_iter())
.chain(params.get())
.collect::<ParamsMap>()
}
})
};
// assign a new owner, so that contexts and signals owned by the previous route // assign a new owner, so that contexts and signals owned by the previous route
// in this outlet can be dropped // in this outlet can be dropped
@ -742,11 +765,10 @@ where
let owner = owner.clone(); let owner = owner.clone();
let trigger = current.trigger.clone(); let trigger = current.trigger.clone();
let url = current.url.clone(); let url = current.url.clone();
let params = current.params.clone();
let matched = Matched(matched_including_parents); let matched = Matched(matched_including_parents);
let view_fn = Arc::clone(&current.view_fn); let view_fn = Arc::clone(&current.view_fn);
async move { async move {
provide_context(params); provide_context(params_including_parents);
provide_context(url); provide_context(url);
provide_context(matched); provide_context(matched);
view.preload().await; view.preload().await;

View file

@ -2,8 +2,10 @@ use crate::location::{unescape, Url};
use std::{borrow::Cow, mem, str::FromStr, sync::Arc}; use std::{borrow::Cow, mem, str::FromStr, sync::Arc};
use thiserror::Error; use thiserror::Error;
type ParamsMapInner = Vec<(Cow<'static, str>, String)>;
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub struct ParamsMap(Vec<(Cow<'static, str>, String)>); pub struct ParamsMap(ParamsMapInner);
impl ParamsMap { impl ParamsMap {
/// Creates an empty map. /// Creates an empty map.
@ -88,6 +90,27 @@ where
} }
} }
impl IntoIterator for ParamsMap {
type Item = (Cow<'static, str>, String);
type IntoIter = ParamsMapIter;
fn into_iter(self) -> Self::IntoIter {
ParamsMapIter(self.0.into_iter())
}
}
/// An iterator over the keys and values of a [`ParamsMap`].
#[derive(Debug)]
pub struct ParamsMapIter(<ParamsMapInner as IntoIterator>::IntoIter);
impl Iterator for ParamsMapIter {
type Item = (Cow<'static, str>, String);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
/// A simple method of deserializing key-value data (like route params or URL search) /// A simple method of deserializing key-value data (like route params or URL search)
/// into a concrete data type. `Self` should typically be a struct in which /// into a concrete data type. `Self` should typically be a struct in which
/// each field's type implements [`FromStr`]. /// each field's type implements [`FromStr`].