Skip to content

Commit 7185ef6

Browse files
committed
feat: made rx collections support extension
This allows them to have elements added, which I didn't implement initially because I forgot about it.
1 parent ee56bc4 commit 7185ef6

File tree

5 files changed

+60
-28
lines changed

5 files changed

+60
-28
lines changed

examples/core/rx_state/src/templates/index.rs

+25-2
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,42 @@
1-
use perseus::prelude::*;
1+
use perseus::{prelude::*, state::rx_collections::RxVec};
22
use serde::{Deserialize, Serialize};
33
use sycamore::prelude::*;
44

55
#[derive(Serialize, Deserialize, Clone, ReactiveState)]
66
#[rx(alias = "IndexPageStateRx")]
77
struct IndexPageState {
8-
pub username: String,
8+
username: String,
9+
#[rx(nested)]
10+
test: RxVec<String>,
911
}
1012

1113
// This macro will make our state reactive *and* store it in the page state
1214
// store, which means it'll be the same even if we go to the about page and come
1315
// back (as long as we're in the same session)
1416
fn index_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a IndexPageStateRx) -> View<G> {
17+
// IMPORTANT: Remember, Perseus caches all reactive state, so, if you come here,
18+
// go to another page, and then come back, *two* elements will have been
19+
// added in total. The state is preserved across routes! To avoid this, use
20+
// unreactive state.
21+
state
22+
.test
23+
.modify()
24+
.push(create_rc_signal("bar".to_string()));
25+
1526
view! { cx,
1627
p { (format!("Greetings, {}!", state.username.get())) }
1728
input(bind:value = state.username, placeholder = "Username")
29+
p { (
30+
state
31+
.test
32+
// Get the underlying `Vec`
33+
.get()
34+
// Now, in that `Vec`, get the third element
35+
.get(2)
36+
// Because that will be `None` initially, display `None` otherwise
37+
.map(|x| x.get())
38+
.unwrap_or("None".to_string().into())
39+
) }
1840

1941
a(href = "about", id = "about-link") { "About" }
2042
}
@@ -39,5 +61,6 @@ pub fn get_template<G: Html>() -> Template<G> {
3961
async fn get_build_state(_info: StateGeneratorInfo<()>) -> IndexPageState {
4062
IndexPageState {
4163
username: "".to_string(),
64+
test: vec!["foo".to_string()].into(),
4265
}
4366
}

packages/perseus/src/state/rx_collections/rx_hash_map.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ where
2121
V: Clone + 'static;
2222
/// The reactive version of [`RxHashMap`].
2323
#[derive(Clone, Debug)]
24-
pub struct RxHashMapRx<K, V>(HashMap<K, RcSignal<V>>)
24+
pub struct RxHashMapRx<K, V>(RcSignal<HashMap<K, RcSignal<V>>>)
2525
where
2626
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
2727
V: Clone + Serialize + DeserializeOwned + 'static;
@@ -35,12 +35,12 @@ where
3535
type Rx = RxHashMapRx<K, V>;
3636

3737
fn make_rx(self) -> Self::Rx {
38-
RxHashMapRx(
38+
RxHashMapRx(create_rc_signal(
3939
self.0
4040
.into_iter()
4141
.map(|(k, v)| (k, create_rc_signal(v)))
4242
.collect(),
43-
)
43+
))
4444
}
4545
}
4646
impl<K, V> MakeUnrx for RxHashMapRx<K, V>
@@ -51,9 +51,9 @@ where
5151
type Unrx = RxHashMap<K, V>;
5252

5353
fn make_unrx(self) -> Self::Unrx {
54+
let map = (*self.0.get_untracked()).clone();
5455
RxHashMap(
55-
self.0
56-
.into_iter()
56+
map.into_iter()
5757
.map(|(k, v)| (k, (*v.get_untracked()).clone()))
5858
.collect(),
5959
)
@@ -79,7 +79,7 @@ where
7979
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
8080
V: Clone + Serialize + DeserializeOwned + 'static,
8181
{
82-
type Target = HashMap<K, RcSignal<V>>;
82+
type Target = RcSignal<HashMap<K, RcSignal<V>>>;
8383

8484
fn deref(&self) -> &Self::Target {
8585
&self.0

packages/perseus/src/state/rx_collections/rx_hash_map_nested.rs

+11-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::hash::Hash;
55
use std::ops::Deref;
66
#[cfg(client)]
77
use sycamore::prelude::Scope;
8+
use sycamore::reactive::{create_rc_signal, RcSignal};
89

910
/// A reactive version of [`HashMap`] that uses nested reactivity on its
1011
/// elements. That means the type inside the vector must implement [`MakeRx`]
@@ -21,7 +22,7 @@ where
2122
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone;
2223
/// The reactive version of [`RxHashMapNested`].
2324
#[derive(Clone, Debug)]
24-
pub struct RxHashMapNestedRx<K, V>(HashMap<K, V::Rx>)
25+
pub struct RxHashMapNestedRx<K, V>(RcSignal<HashMap<K, V::Rx>>)
2526
where
2627
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
2728
V: MakeRx + Serialize + DeserializeOwned + 'static,
@@ -37,7 +38,9 @@ where
3738
type Rx = RxHashMapNestedRx<K, V>;
3839

3940
fn make_rx(self) -> Self::Rx {
40-
RxHashMapNestedRx(self.0.into_iter().map(|(k, v)| (k, v.make_rx())).collect())
41+
RxHashMapNestedRx(create_rc_signal(
42+
self.0.into_iter().map(|(k, v)| (k, v.make_rx())).collect(),
43+
))
4144
}
4245
}
4346
impl<K, V> MakeUnrx for RxHashMapNestedRx<K, V>
@@ -49,17 +52,15 @@ where
4952
type Unrx = RxHashMapNested<K, V>;
5053

5154
fn make_unrx(self) -> Self::Unrx {
52-
RxHashMapNested(
53-
self.0
54-
.into_iter()
55-
.map(|(k, v)| (k, v.make_unrx()))
56-
.collect(),
57-
)
55+
let map = (*self.0.get_untracked()).clone();
56+
RxHashMapNested(map.into_iter().map(|(k, v)| (k, v.make_unrx())).collect())
5857
}
5958

6059
#[cfg(client)]
6160
fn compute_suspense(&self, cx: Scope) {
62-
for elem in self.0.values() {
61+
// We do *not* want to recompute this every time the user changes the state!
62+
// (There lie infinite loops.)
63+
for elem in self.0.get_untracked().values() {
6364
elem.compute_suspense(cx);
6465
}
6566
}
@@ -83,7 +84,7 @@ where
8384
V: MakeRx + Serialize + DeserializeOwned + 'static,
8485
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone,
8586
{
86-
type Target = HashMap<K, V::Rx>;
87+
type Target = RcSignal<HashMap<K, V::Rx>>;
8788

8889
fn deref(&self) -> &Self::Target {
8990
&self.0

packages/perseus/src/state/rx_collections/rx_vec.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ where
1818
T: Clone + 'static;
1919
/// The reactive version of [`RxVec`].
2020
#[derive(Clone, Debug)]
21-
pub struct RxVecRx<T>(Vec<RcSignal<T>>)
21+
pub struct RxVecRx<T>(RcSignal<Vec<RcSignal<T>>>)
2222
where
2323
T: Clone + Serialize + DeserializeOwned + 'static;
2424

@@ -30,7 +30,9 @@ where
3030
type Rx = RxVecRx<T>;
3131

3232
fn make_rx(self) -> Self::Rx {
33-
RxVecRx(self.0.into_iter().map(|x| create_rc_signal(x)).collect())
33+
RxVecRx(create_rc_signal(
34+
self.0.into_iter().map(|x| create_rc_signal(x)).collect(),
35+
))
3436
}
3537
}
3638
impl<T> MakeUnrx for RxVecRx<T>
@@ -40,9 +42,9 @@ where
4042
type Unrx = RxVec<T>;
4143

4244
fn make_unrx(self) -> Self::Unrx {
45+
let vec = (*self.0.get_untracked()).clone();
4346
RxVec(
44-
self.0
45-
.into_iter()
47+
vec.into_iter()
4648
.map(|x| (*x.get_untracked()).clone())
4749
.collect(),
4850
)
@@ -66,7 +68,7 @@ impl<T> Deref for RxVecRx<T>
6668
where
6769
T: Clone + Serialize + DeserializeOwned + 'static,
6870
{
69-
type Target = Vec<RcSignal<T>>;
71+
type Target = RcSignal<Vec<RcSignal<T>>>;
7072

7173
fn deref(&self) -> &Self::Target {
7274
&self.0

packages/perseus/src/state/rx_collections/rx_vec_nested.rs

+11-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
33
use std::ops::Deref;
44
#[cfg(client)]
55
use sycamore::prelude::Scope;
6+
use sycamore::reactive::{create_rc_signal, RcSignal};
67

78
/// A reactive version of [`Vec`] that uses nested reactivity on its elements.
89
/// That means the type inside the vector must implement [`MakeRx`] (usually
@@ -18,7 +19,7 @@ where
1819
T::Rx: MakeUnrx<Unrx = T> + Freeze + Clone;
1920
/// The reactive version of [`RxVecNested`].
2021
#[derive(Clone, Debug)]
21-
pub struct RxVecNestedRx<T>(Vec<T::Rx>)
22+
pub struct RxVecNestedRx<T>(RcSignal<Vec<T::Rx>>)
2223
where
2324
T: MakeRx + Serialize + DeserializeOwned + 'static,
2425
T::Rx: MakeUnrx<Unrx = T> + Freeze + Clone;
@@ -32,7 +33,9 @@ where
3233
type Rx = RxVecNestedRx<T>;
3334

3435
fn make_rx(self) -> Self::Rx {
35-
RxVecNestedRx(self.0.into_iter().map(|x| x.make_rx()).collect())
36+
RxVecNestedRx(create_rc_signal(
37+
self.0.into_iter().map(|x| x.make_rx()).collect(),
38+
))
3639
}
3740
}
3841
impl<T> MakeUnrx for RxVecNestedRx<T>
@@ -43,12 +46,15 @@ where
4346
type Unrx = RxVecNested<T>;
4447

4548
fn make_unrx(self) -> Self::Unrx {
46-
RxVecNested(self.0.into_iter().map(|x| x.make_unrx()).collect())
49+
let vec = (*self.0.get_untracked()).clone();
50+
RxVecNested(vec.into_iter().map(|x| x.make_unrx()).collect())
4751
}
4852

4953
#[cfg(client)]
5054
fn compute_suspense(&self, cx: Scope) {
51-
for elem in self.0.iter() {
55+
// We do *not* want to recompute this every time the user changes the state!
56+
// (There lie infinite loops.)
57+
for elem in self.0.get_untracked().iter() {
5258
elem.compute_suspense(cx);
5359
}
5460
}
@@ -70,7 +76,7 @@ where
7076
T: MakeRx + Serialize + DeserializeOwned + 'static,
7177
T::Rx: MakeUnrx<Unrx = T> + Freeze + Clone,
7278
{
73-
type Target = Vec<T::Rx>;
79+
type Target = RcSignal<Vec<T::Rx>>;
7480

7581
fn deref(&self) -> &Self::Target {
7682
&self.0

0 commit comments

Comments
 (0)