diff --git a/reactive_stores/src/arc_field.rs b/reactive_stores/src/arc_field.rs index 90f4bd0e4..a6bfcb5be 100644 --- a/reactive_stores/src/arc_field.rs +++ b/reactive_stores/src/arc_field.rs @@ -29,6 +29,7 @@ where #[cfg(any(debug_assertions, leptos_debuginfo))] defined_at: &'static Location<'static>, path: Arc StorePath + Send + Sync>, + path_unkeyed: Arc StorePath + Send + Sync>, get_trigger: Arc StoreFieldTrigger + Send + Sync>, get_trigger_unkeyed: Arc StoreFieldTrigger + Send + Sync>, @@ -113,6 +114,10 @@ impl StoreField for ArcField { (self.path)() } + fn path_unkeyed(&self) -> impl IntoIterator { + (self.path_unkeyed)() + } + fn reader(&self) -> Option { (self.read)().map(StoreFieldReader::new) } @@ -137,6 +142,9 @@ where #[cfg(any(debug_assertions, leptos_debuginfo))] defined_at: Location::caller(), path: Arc::new(move || value.path().into_iter().collect()), + path_unkeyed: Arc::new(move || { + value.path_unkeyed().into_iter().collect() + }), get_trigger: Arc::new(move |path| value.get_trigger(path)), get_trigger_unkeyed: Arc::new(move |path| { value.get_trigger_unkeyed(path) @@ -163,6 +171,10 @@ where let value = value.clone(); move || value.path().into_iter().collect() }), + path_unkeyed: Arc::new({ + let value = value.clone(); + move || value.path_unkeyed().into_iter().collect() + }), get_trigger: Arc::new({ let value = value.clone(); move |path| value.get_trigger(path) @@ -211,6 +223,10 @@ where let value = value.clone(); move || value.path().into_iter().collect() }), + path_unkeyed: Arc::new({ + let value = value.clone(); + move || value.path_unkeyed().into_iter().collect() + }), get_trigger: Arc::new({ let value = value.clone(); move |path| value.get_trigger(path) @@ -258,6 +274,10 @@ where let value = value.clone(); move || value.path().into_iter().collect() }), + path_unkeyed: Arc::new({ + let value = value.clone(); + move || value.path_unkeyed().into_iter().collect() + }), get_trigger: Arc::new({ let value = value.clone(); move |path| value.get_trigger(path) @@ -306,6 +326,10 @@ where let value = value.clone(); move || value.path().into_iter().collect() }), + path_unkeyed: Arc::new({ + let value = value.clone(); + move || value.path_unkeyed().into_iter().collect() + }), get_trigger: Arc::new({ let value = value.clone(); move |path| value.get_trigger(path) @@ -358,6 +382,10 @@ where let value = value.clone(); move || value.path().into_iter().collect() }), + path_unkeyed: Arc::new({ + let value = value.clone(); + move || value.path_unkeyed().into_iter().collect() + }), get_trigger: Arc::new({ let value = value.clone(); move |path| value.get_trigger(path) @@ -396,6 +424,7 @@ impl Clone for ArcField { #[cfg(any(debug_assertions, leptos_debuginfo))] defined_at: self.defined_at, path: self.path.clone(), + path_unkeyed: self.path_unkeyed.clone(), get_trigger: Arc::clone(&self.get_trigger), get_trigger_unkeyed: Arc::clone(&self.get_trigger_unkeyed), read: Arc::clone(&self.read), diff --git a/reactive_stores/src/deref.rs b/reactive_stores/src/deref.rs index 93f6619ec..87aa1a446 100644 --- a/reactive_stores/src/deref.rs +++ b/reactive_stores/src/deref.rs @@ -76,6 +76,11 @@ where fn path(&self) -> impl IntoIterator { self.inner.path() } + + fn path_unkeyed(&self) -> impl IntoIterator { + self.inner.path_unkeyed() + } + fn reader(&self) -> Option { let inner = self.inner.reader()?; Some(Mapped::new_with_guard(inner, |n| n.deref())) diff --git a/reactive_stores/src/field.rs b/reactive_stores/src/field.rs index 927fe31ec..0b3dc316c 100644 --- a/reactive_stores/src/field.rs +++ b/reactive_stores/src/field.rs @@ -73,6 +73,13 @@ where .unwrap_or_default() } + fn path_unkeyed(&self) -> impl IntoIterator { + self.inner + .try_get_value() + .map(|inner| inner.path_unkeyed().into_iter().collect::>()) + .unwrap_or_default() + } + fn reader(&self) -> Option { self.inner.try_get_value().and_then(|inner| inner.reader()) } diff --git a/reactive_stores/src/iter.rs b/reactive_stores/src/iter.rs index 9db7c1c67..6924fb747 100644 --- a/reactive_stores/src/iter.rs +++ b/reactive_stores/src/iter.rs @@ -80,6 +80,13 @@ where .chain(iter::once(self.index.into())) } + fn path_unkeyed(&self) -> impl IntoIterator { + self.inner + .path_unkeyed() + .into_iter() + .chain(iter::once(self.index.into())) + } + fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger { self.inner.get_trigger(path) } diff --git a/reactive_stores/src/keyed.rs b/reactive_stores/src/keyed.rs index 424fc3189..ea27b0484 100644 --- a/reactive_stores/src/keyed.rs +++ b/reactive_stores/src/keyed.rs @@ -106,6 +106,13 @@ where .chain(iter::once(self.path_segment)) } + fn path_unkeyed(&self) -> impl IntoIterator { + self.inner + .path_unkeyed() + .into_iter() + .chain(iter::once(self.path_segment)) + } + fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger { self.inner.get_trigger(path) } @@ -444,6 +451,24 @@ where inner.into_iter().chain(this) } + fn path_unkeyed(&self) -> impl IntoIterator { + let inner = + self.inner.path_unkeyed().into_iter().collect::(); + let keys = self + .inner + .keys() + .expect("using keys on a store with no keys"); + let this = keys + .with_field_keys( + inner.clone(), + |keys| (keys.get(&self.key), vec![]), + || self.inner.latest_keys(), + ) + .flatten() + .map(|(_, idx)| StorePathSegment(idx)); + inner.into_iter().chain(this) + } + fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger { self.inner.get_trigger(path) } @@ -721,18 +746,19 @@ mod tests { effect::Effect, traits::{GetUntracked, ReadUntracked, Set, Track, Write}, }; + use reactive_stores::Patch; use std::sync::{ atomic::{AtomicUsize, Ordering}, Arc, }; - #[derive(Debug, Store, Default)] + #[derive(Debug, Store, Default, Patch)] struct Todos { #[store(key: usize = |todo| todo.id)] todos: Vec, } - #[derive(Debug, Store, Default, Clone, PartialEq, Eq)] + #[derive(Debug, Store, Default, Clone, PartialEq, Eq, Patch)] struct Todo { id: usize, label: String, diff --git a/reactive_stores/src/patch.rs b/reactive_stores/src/patch.rs index 6b8f78888..17ef92c5b 100644 --- a/reactive_stores/src/patch.rs +++ b/reactive_stores/src/patch.rs @@ -30,7 +30,7 @@ where type Value = T::Value; fn patch(&self, new: Self::Value) { - let path = self.path().into_iter().collect::(); + let path = self.path_unkeyed().into_iter().collect::(); if let Some(mut writer) = self.writer() { // don't track the writer for the whole store writer.untrack(); diff --git a/reactive_stores/src/store_field.rs b/reactive_stores/src/store_field.rs index 7856d0788..310aa8ddf 100644 --- a/reactive_stores/src/store_field.rs +++ b/reactive_stores/src/store_field.rs @@ -38,6 +38,13 @@ pub trait StoreField: Sized { #[track_caller] fn path(&self) -> impl IntoIterator; + /// The path of this field (see [`StorePath`]). Uses unkeyed indices for any keyed fields. + #[track_caller] + fn path_unkeyed(&self) -> impl IntoIterator { + // TODO remove default impl next time we do a breaking release + self.path() + } + /// Reactively tracks this field. #[track_caller] fn track_field(&self) { @@ -129,7 +136,9 @@ where trigger } + #[track_caller] fn get_trigger_unkeyed(&self, path: StorePath) -> StoreFieldTrigger { + let caller = std::panic::Location::caller(); let orig_path = path.clone(); let mut path = StorePath::with_capacity(orig_path.len()); @@ -140,7 +149,13 @@ where let key = self .keys .get_key_for_index(&(path.clone(), segment.0)) - .expect("could not find key for index"); + .unwrap_or_else(|| { + panic!( + "could not find key for index {:?} at {}", + &(path.clone(), segment.0), + caller + ) + }); path.push(key); } else { path.push(*segment); @@ -154,6 +169,11 @@ where iter::empty() } + #[track_caller] + fn path_unkeyed(&self) -> impl IntoIterator { + iter::empty() + } + #[track_caller] fn reader(&self) -> Option { Plain::try_new(Arc::clone(&self.value)) @@ -205,6 +225,14 @@ where .unwrap_or_default() } + #[track_caller] + fn path_unkeyed(&self) -> impl IntoIterator { + self.inner + .try_get_value() + .map(|n| n.path_unkeyed().into_iter().collect::>()) + .unwrap_or_default() + } + #[track_caller] fn reader(&self) -> Option { self.inner.try_get_value().and_then(|n| n.reader()) diff --git a/reactive_stores/src/subfield.rs b/reactive_stores/src/subfield.rs index 823d3e212..8e506584e 100644 --- a/reactive_stores/src/subfield.rs +++ b/reactive_stores/src/subfield.rs @@ -84,6 +84,13 @@ where .chain(iter::once(self.path_segment)) } + fn path_unkeyed(&self) -> impl IntoIterator { + self.inner + .path_unkeyed() + .into_iter() + .chain(iter::once(self.path_segment)) + } + fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger { self.inner.get_trigger(path) }