From 7185ef6ba60685f26ae1de38a1bc64be52c2a530 Mon Sep 17 00:00:00 2001 From: arctic-hen7 Date: Sun, 8 Jan 2023 12:17:15 +1100 Subject: [PATCH] feat: made rx collections support extension This allows them to have elements added, which I didn't implement initially because I forgot about it. --- examples/core/rx_state/src/templates/index.rs | 27 +++++++++++++++++-- .../src/state/rx_collections/rx_hash_map.rs | 12 ++++----- .../rx_collections/rx_hash_map_nested.rs | 21 ++++++++------- .../src/state/rx_collections/rx_vec.rs | 12 +++++---- .../src/state/rx_collections/rx_vec_nested.rs | 16 +++++++---- 5 files changed, 60 insertions(+), 28 deletions(-) diff --git a/examples/core/rx_state/src/templates/index.rs b/examples/core/rx_state/src/templates/index.rs index 52350f511a..e3b2344e6d 100644 --- a/examples/core/rx_state/src/templates/index.rs +++ b/examples/core/rx_state/src/templates/index.rs @@ -1,20 +1,42 @@ -use perseus::prelude::*; +use perseus::{prelude::*, state::rx_collections::RxVec}; use serde::{Deserialize, Serialize}; use sycamore::prelude::*; #[derive(Serialize, Deserialize, Clone, ReactiveState)] #[rx(alias = "IndexPageStateRx")] struct IndexPageState { - pub username: String, + username: String, + #[rx(nested)] + test: RxVec, } // This macro will make our state reactive *and* store it in the page state // store, which means it'll be the same even if we go to the about page and come // back (as long as we're in the same session) fn index_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a IndexPageStateRx) -> View { + // IMPORTANT: Remember, Perseus caches all reactive state, so, if you come here, + // go to another page, and then come back, *two* elements will have been + // added in total. The state is preserved across routes! To avoid this, use + // unreactive state. + state + .test + .modify() + .push(create_rc_signal("bar".to_string())); + view! { cx, p { (format!("Greetings, {}!", state.username.get())) } input(bind:value = state.username, placeholder = "Username") + p { ( + state + .test + // Get the underlying `Vec` + .get() + // Now, in that `Vec`, get the third element + .get(2) + // Because that will be `None` initially, display `None` otherwise + .map(|x| x.get()) + .unwrap_or("None".to_string().into()) + ) } a(href = "about", id = "about-link") { "About" } } @@ -39,5 +61,6 @@ pub fn get_template() -> Template { async fn get_build_state(_info: StateGeneratorInfo<()>) -> IndexPageState { IndexPageState { username: "".to_string(), + test: vec!["foo".to_string()].into(), } } diff --git a/packages/perseus/src/state/rx_collections/rx_hash_map.rs b/packages/perseus/src/state/rx_collections/rx_hash_map.rs index 84fa186d58..49d6a07118 100644 --- a/packages/perseus/src/state/rx_collections/rx_hash_map.rs +++ b/packages/perseus/src/state/rx_collections/rx_hash_map.rs @@ -21,7 +21,7 @@ where V: Clone + 'static; /// The reactive version of [`RxHashMap`]. #[derive(Clone, Debug)] -pub struct RxHashMapRx(HashMap>) +pub struct RxHashMapRx(RcSignal>>) where K: Clone + Serialize + DeserializeOwned + Eq + Hash, V: Clone + Serialize + DeserializeOwned + 'static; @@ -35,12 +35,12 @@ where type Rx = RxHashMapRx; fn make_rx(self) -> Self::Rx { - RxHashMapRx( + RxHashMapRx(create_rc_signal( self.0 .into_iter() .map(|(k, v)| (k, create_rc_signal(v))) .collect(), - ) + )) } } impl MakeUnrx for RxHashMapRx @@ -51,9 +51,9 @@ where type Unrx = RxHashMap; fn make_unrx(self) -> Self::Unrx { + let map = (*self.0.get_untracked()).clone(); RxHashMap( - self.0 - .into_iter() + map.into_iter() .map(|(k, v)| (k, (*v.get_untracked()).clone())) .collect(), ) @@ -79,7 +79,7 @@ where K: Clone + Serialize + DeserializeOwned + Eq + Hash, V: Clone + Serialize + DeserializeOwned + 'static, { - type Target = HashMap>; + type Target = RcSignal>>; fn deref(&self) -> &Self::Target { &self.0 diff --git a/packages/perseus/src/state/rx_collections/rx_hash_map_nested.rs b/packages/perseus/src/state/rx_collections/rx_hash_map_nested.rs index 209d9a80eb..b35ca9300b 100644 --- a/packages/perseus/src/state/rx_collections/rx_hash_map_nested.rs +++ b/packages/perseus/src/state/rx_collections/rx_hash_map_nested.rs @@ -5,6 +5,7 @@ use std::hash::Hash; use std::ops::Deref; #[cfg(client)] use sycamore::prelude::Scope; +use sycamore::reactive::{create_rc_signal, RcSignal}; /// A reactive version of [`HashMap`] that uses nested reactivity on its /// elements. That means the type inside the vector must implement [`MakeRx`] @@ -21,7 +22,7 @@ where V::Rx: MakeUnrx + Freeze + Clone; /// The reactive version of [`RxHashMapNested`]. #[derive(Clone, Debug)] -pub struct RxHashMapNestedRx(HashMap) +pub struct RxHashMapNestedRx(RcSignal>) where K: Clone + Serialize + DeserializeOwned + Eq + Hash, V: MakeRx + Serialize + DeserializeOwned + 'static, @@ -37,7 +38,9 @@ where type Rx = RxHashMapNestedRx; fn make_rx(self) -> Self::Rx { - RxHashMapNestedRx(self.0.into_iter().map(|(k, v)| (k, v.make_rx())).collect()) + RxHashMapNestedRx(create_rc_signal( + self.0.into_iter().map(|(k, v)| (k, v.make_rx())).collect(), + )) } } impl MakeUnrx for RxHashMapNestedRx @@ -49,17 +52,15 @@ where type Unrx = RxHashMapNested; fn make_unrx(self) -> Self::Unrx { - RxHashMapNested( - self.0 - .into_iter() - .map(|(k, v)| (k, v.make_unrx())) - .collect(), - ) + let map = (*self.0.get_untracked()).clone(); + RxHashMapNested(map.into_iter().map(|(k, v)| (k, v.make_unrx())).collect()) } #[cfg(client)] fn compute_suspense(&self, cx: Scope) { - for elem in self.0.values() { + // We do *not* want to recompute this every time the user changes the state! + // (There lie infinite loops.) + for elem in self.0.get_untracked().values() { elem.compute_suspense(cx); } } @@ -83,7 +84,7 @@ where V: MakeRx + Serialize + DeserializeOwned + 'static, V::Rx: MakeUnrx + Freeze + Clone, { - type Target = HashMap; + type Target = RcSignal>; fn deref(&self) -> &Self::Target { &self.0 diff --git a/packages/perseus/src/state/rx_collections/rx_vec.rs b/packages/perseus/src/state/rx_collections/rx_vec.rs index 28aba64a49..a3202f66a7 100644 --- a/packages/perseus/src/state/rx_collections/rx_vec.rs +++ b/packages/perseus/src/state/rx_collections/rx_vec.rs @@ -18,7 +18,7 @@ where T: Clone + 'static; /// The reactive version of [`RxVec`]. #[derive(Clone, Debug)] -pub struct RxVecRx(Vec>) +pub struct RxVecRx(RcSignal>>) where T: Clone + Serialize + DeserializeOwned + 'static; @@ -30,7 +30,9 @@ where type Rx = RxVecRx; fn make_rx(self) -> Self::Rx { - RxVecRx(self.0.into_iter().map(|x| create_rc_signal(x)).collect()) + RxVecRx(create_rc_signal( + self.0.into_iter().map(|x| create_rc_signal(x)).collect(), + )) } } impl MakeUnrx for RxVecRx @@ -40,9 +42,9 @@ where type Unrx = RxVec; fn make_unrx(self) -> Self::Unrx { + let vec = (*self.0.get_untracked()).clone(); RxVec( - self.0 - .into_iter() + vec.into_iter() .map(|x| (*x.get_untracked()).clone()) .collect(), ) @@ -66,7 +68,7 @@ impl Deref for RxVecRx where T: Clone + Serialize + DeserializeOwned + 'static, { - type Target = Vec>; + type Target = RcSignal>>; fn deref(&self) -> &Self::Target { &self.0 diff --git a/packages/perseus/src/state/rx_collections/rx_vec_nested.rs b/packages/perseus/src/state/rx_collections/rx_vec_nested.rs index e4d4fc8ea7..4241a9fa53 100644 --- a/packages/perseus/src/state/rx_collections/rx_vec_nested.rs +++ b/packages/perseus/src/state/rx_collections/rx_vec_nested.rs @@ -3,6 +3,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::ops::Deref; #[cfg(client)] use sycamore::prelude::Scope; +use sycamore::reactive::{create_rc_signal, RcSignal}; /// A reactive version of [`Vec`] that uses nested reactivity on its elements. /// That means the type inside the vector must implement [`MakeRx`] (usually @@ -18,7 +19,7 @@ where T::Rx: MakeUnrx + Freeze + Clone; /// The reactive version of [`RxVecNested`]. #[derive(Clone, Debug)] -pub struct RxVecNestedRx(Vec) +pub struct RxVecNestedRx(RcSignal>) where T: MakeRx + Serialize + DeserializeOwned + 'static, T::Rx: MakeUnrx + Freeze + Clone; @@ -32,7 +33,9 @@ where type Rx = RxVecNestedRx; fn make_rx(self) -> Self::Rx { - RxVecNestedRx(self.0.into_iter().map(|x| x.make_rx()).collect()) + RxVecNestedRx(create_rc_signal( + self.0.into_iter().map(|x| x.make_rx()).collect(), + )) } } impl MakeUnrx for RxVecNestedRx @@ -43,12 +46,15 @@ where type Unrx = RxVecNested; fn make_unrx(self) -> Self::Unrx { - RxVecNested(self.0.into_iter().map(|x| x.make_unrx()).collect()) + let vec = (*self.0.get_untracked()).clone(); + RxVecNested(vec.into_iter().map(|x| x.make_unrx()).collect()) } #[cfg(client)] fn compute_suspense(&self, cx: Scope) { - for elem in self.0.iter() { + // We do *not* want to recompute this every time the user changes the state! + // (There lie infinite loops.) + for elem in self.0.get_untracked().iter() { elem.compute_suspense(cx); } } @@ -70,7 +76,7 @@ where T: MakeRx + Serialize + DeserializeOwned + 'static, T::Rx: MakeUnrx + Freeze + Clone, { - type Target = Vec; + type Target = RcSignal>; fn deref(&self) -> &Self::Target { &self.0