Merge pull request #2814 from leptos-rs/2719

Provide parent route params to children
This commit is contained in:
Greg Johnston 2024-08-11 18:36:15 -04:00 committed by GitHub
commit 84590b98ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 81 additions and 26 deletions

View file

@ -7,7 +7,7 @@ use crate::{
owner::{Storage, StorageAccess, SyncStorage},
signal::{
guards::{Mapped, Plain, ReadGuard},
ArcReadSignal,
ArcReadSignal, ArcRwSignal,
},
traits::{DefinedAt, Get, ReadUntracked},
};
@ -347,3 +347,13 @@ where
ArcMemo::new(move |_| value.get())
}
}
impl<T> From<ArcRwSignal<T>> for ArcMemo<T, SyncStorage>
where
T: Clone + PartialEq + Send + Sync + 'static,
{
#[track_caller]
fn from(value: ArcRwSignal<T>) -> Self {
ArcMemo::new(move |_| value.get())
}
}

View file

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

View file

@ -156,7 +156,7 @@ pub fn use_location() -> Location {
}
#[track_caller]
fn use_params_raw() -> ArcRwSignal<ParamsMap> {
fn use_params_raw() -> ArcMemo<ParamsMap> {
use_context().expect(
"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.
#[track_caller]
pub fn use_params_map() -> Memo<ParamsMap> {
// TODO this can be optimized in future to map over the signal, rather than cloning
let params = use_params_raw();
Memo::new(move |_| params.get())
use_params_raw().into()
}
/// 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
// it's used for relative route resolution
let matched = ArcRwSignal::new(self.as_matched().to_string());
let matched_including_parents = {
let parents = outlets
let (parent_params, parent_matches): (Vec<_>, Vec<_>) = outlets
.iter()
.map(|route| route.matched.clone())
.collect::<Vec<_>>();
.map(|route| (route.params.clone(), route.matched.clone()))
.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();
ArcMemo::new({
move |_| {
parents
parent_matches
.iter()
.map(|matched| matched.get())
.chain(iter::once(matched.get()))
@ -616,12 +628,11 @@ where
loaders.push(Box::pin(owner.with(|| {
ScopedFuture::new({
let owner = outlet.owner.clone();
let params = outlet.params.clone();
let url = outlet.url.clone();
let matched = Matched(matched_including_parents);
let view_fn = Arc::clone(&outlet.view_fn);
async move {
provide_context(params);
provide_context(params_including_parents);
provide_context(url);
provide_context(matched);
view.preload().await;
@ -667,11 +678,11 @@ where
outlets: &mut Vec<RouteContext<R>>,
parent: &Owner,
) {
let parent_matches = outlets
let (parent_params, parent_matches): (Vec<_>, Vec<_>) = outlets
.iter()
.take(*items)
.map(|route| route.matched.clone())
.collect::<Vec<_>>();
.map(|route| (route.params.clone(), route.matched.clone()))
.unzip();
let current = outlets.get_mut(*items);
match current {
// 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
// in this outlet can be dropped
@ -742,11 +765,10 @@ where
let owner = owner.clone();
let trigger = current.trigger.clone();
let url = current.url.clone();
let params = current.params.clone();
let matched = Matched(matched_including_parents);
let view_fn = Arc::clone(&current.view_fn);
async move {
provide_context(params);
provide_context(params_including_parents);
provide_context(url);
provide_context(matched);
view.preload().await;

View file

@ -2,8 +2,10 @@ use crate::location::{unescape, Url};
use std::{borrow::Cow, mem, str::FromStr, sync::Arc};
use thiserror::Error;
type ParamsMapInner = Vec<(Cow<'static, str>, String)>;
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub struct ParamsMap(Vec<(Cow<'static, str>, String)>);
pub struct ParamsMap(ParamsMapInner);
impl ParamsMap {
/// 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)
/// into a concrete data type. `Self` should typically be a struct in which
/// each field's type implements [`FromStr`].