Skip to content

Commit 04e1629

Browse files
committed
feat: added more reactive collections
There are now nested and non-nested equivalents for both `Vec` and `HashMap` (along with `RxResult`).
1 parent 307c4fe commit 04e1629

File tree

4 files changed

+331
-1
lines changed

4 files changed

+331
-1
lines changed

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,17 @@
7878
//! although there is presently no defined reactive container for this.*
7979
//!
8080
//! **Note:** as a user, you will still have to use `#[rx(nested)]` over any
81-
//! reactive types you use!
81+
//! reactive types you use, even those that are not nested! This is because,
82+
//! without this attribute, the `ReactiveState` derive macro will just wrap
83+
//! the whole field in an `RcSignal` and call it a day, rather than using the
84+
//! fine-grained reactivity enabled by these types.
8285
86+
mod rx_hash_map;
87+
mod rx_hash_map_nested;
88+
mod rx_vec;
8389
mod rx_vec_nested;
8490

91+
pub use rx_hash_map::{RxHashMap, RxHashMapRx};
92+
pub use rx_hash_map_nested::{RxHashMapNested, RxHashMapNestedRx};
93+
pub use rx_vec::{RxVec, RxVecRx};
8594
pub use rx_vec_nested::{RxVecNested, RxVecNestedRx};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use crate::state::{Freeze, MakeRx, MakeUnrx};
2+
use serde::{de::DeserializeOwned, Deserialize, Serialize};
3+
use std::collections::HashMap;
4+
use std::hash::Hash;
5+
use std::ops::Deref;
6+
#[cfg(target_arch = "wasm32")]
7+
use sycamore::prelude::Scope;
8+
use sycamore::reactive::{create_rc_signal, RcSignal};
9+
10+
/// A reactive version of [`Vec`] that uses nested reactivity on its elements.
11+
/// This requires nothing by `Clone + 'static` of the elements inside the map,
12+
/// and it wraps them in `RcSignal`s to make them reactive. If you want to store
13+
/// nested reactive types inside the map (e.g. `String`s), you should
14+
/// use [`RxVecNested`].
15+
#[derive(Serialize, Deserialize, Clone, Debug)]
16+
pub struct RxHashMap<K, V>(HashMap<K, V>)
17+
where
18+
K: Clone + Eq + Hash,
19+
// We get the `Deserialize` derive macro working by tricking Serde by not
20+
// including the actual bounds here
21+
V: Clone + 'static;
22+
/// The reactive version of [`RxHashMap`].
23+
#[derive(Clone, Debug)]
24+
pub struct RxHashMapRx<K, V>(HashMap<K, RcSignal<V>>)
25+
where
26+
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
27+
V: Clone + Serialize + DeserializeOwned + 'static;
28+
29+
// --- Reactivity implementations ---
30+
impl<K, V> MakeRx for RxHashMap<K, V>
31+
where
32+
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
33+
V: Clone + Serialize + DeserializeOwned + 'static,
34+
{
35+
type Rx = RxHashMapRx<K, V>;
36+
37+
fn make_rx(self) -> Self::Rx {
38+
RxHashMapRx(
39+
self.0
40+
.into_iter()
41+
.map(|(k, v)| (k, create_rc_signal(v)))
42+
.collect(),
43+
)
44+
}
45+
}
46+
impl<K, V> MakeUnrx for RxHashMapRx<K, V>
47+
where
48+
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
49+
V: Clone + Serialize + DeserializeOwned + 'static,
50+
{
51+
type Unrx = RxHashMap<K, V>;
52+
53+
fn make_unrx(self) -> Self::Unrx {
54+
RxHashMap(
55+
self.0
56+
.into_iter()
57+
.map(|(k, v)| (k, (*v.get_untracked()).clone()))
58+
.collect(),
59+
)
60+
}
61+
62+
#[cfg(target_arch = "wasm32")]
63+
fn compute_suspense(&self, cx: Scope) {}
64+
}
65+
// --- Dereferencing ---
66+
impl<K, V> Deref for RxHashMap<K, V>
67+
where
68+
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
69+
V: Clone + Serialize + DeserializeOwned + 'static,
70+
{
71+
type Target = HashMap<K, V>;
72+
73+
fn deref(&self) -> &Self::Target {
74+
&self.0
75+
}
76+
}
77+
impl<K, V> Deref for RxHashMapRx<K, V>
78+
where
79+
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
80+
V: Clone + Serialize + DeserializeOwned + 'static,
81+
{
82+
type Target = HashMap<K, RcSignal<V>>;
83+
84+
fn deref(&self) -> &Self::Target {
85+
&self.0
86+
}
87+
}
88+
// --- Conversion implementation ---
89+
impl<K, V> From<HashMap<K, V>> for RxHashMap<K, V>
90+
where
91+
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
92+
V: Clone + Serialize + DeserializeOwned + 'static,
93+
{
94+
fn from(value: HashMap<K, V>) -> Self {
95+
Self(value)
96+
}
97+
}
98+
99+
// --- Freezing implementation ---
100+
impl<K, V> Freeze for RxHashMapRx<K, V>
101+
where
102+
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
103+
V: Clone + Serialize + DeserializeOwned + 'static,
104+
{
105+
fn freeze(&self) -> String {
106+
let unrx = Self(self.0.clone()).make_unrx();
107+
// This should never panic, because we're dealing with a vector
108+
serde_json::to_string(&unrx).unwrap()
109+
}
110+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
use crate::state::{Freeze, MakeRx, MakeUnrx};
2+
use serde::{de::DeserializeOwned, Deserialize, Serialize};
3+
use std::collections::HashMap;
4+
use std::hash::Hash;
5+
use std::ops::Deref;
6+
#[cfg(target_arch = "wasm32")]
7+
use sycamore::prelude::Scope;
8+
9+
/// A reactive version of [`Vec`] that uses nested reactivity on its elements.
10+
/// That means the type inside the vector must implement [`MakeRx`] (usually
11+
/// derived with the `ReactiveState` macro). If you want to store simple types
12+
/// inside the vector, without nested reactivity (e.g. `String`s), you should
13+
/// use [`RxVec`].
14+
#[derive(Serialize, Deserialize, Clone, Debug)]
15+
pub struct RxHashMapNested<K, V>(HashMap<K, V>)
16+
where
17+
K: Clone + Eq + Hash,
18+
// We get the `Deserialize` derive macro working by tricking Serde by not
19+
// including the actual bounds here
20+
V: MakeRx + 'static,
21+
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone;
22+
/// The reactive version of [`RxHashMap`].
23+
#[derive(Clone, Debug)]
24+
pub struct RxHashMapNestedRx<K, V>(HashMap<K, V::Rx>)
25+
where
26+
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
27+
V: MakeRx + Serialize + DeserializeOwned + 'static,
28+
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone;
29+
30+
// --- Reactivity implementations ---
31+
impl<K, V> MakeRx for RxHashMapNested<K, V>
32+
where
33+
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
34+
V: MakeRx + Serialize + DeserializeOwned + 'static,
35+
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone,
36+
{
37+
type Rx = RxHashMapNestedRx<K, V>;
38+
39+
fn make_rx(self) -> Self::Rx {
40+
RxHashMapNestedRx(self.0.into_iter().map(|(k, v)| (k, v.make_rx())).collect())
41+
}
42+
}
43+
impl<K, V> MakeUnrx for RxHashMapNestedRx<K, V>
44+
where
45+
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
46+
V: MakeRx + Serialize + DeserializeOwned + 'static,
47+
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone,
48+
{
49+
type Unrx = RxHashMapNested<K, V>;
50+
51+
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+
)
58+
}
59+
60+
#[cfg(target_arch = "wasm32")]
61+
fn compute_suspense(&self, cx: Scope) {
62+
for elem in self.0.values() {
63+
elem.compute_suspense(cx);
64+
}
65+
}
66+
}
67+
// --- Dereferencing ---
68+
impl<K, V> Deref for RxHashMapNested<K, V>
69+
where
70+
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
71+
V: MakeRx + Serialize + DeserializeOwned + 'static,
72+
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone,
73+
{
74+
type Target = HashMap<K, V>;
75+
76+
fn deref(&self) -> &Self::Target {
77+
&self.0
78+
}
79+
}
80+
impl<K, V> Deref for RxHashMapNestedRx<K, V>
81+
where
82+
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
83+
V: MakeRx + Serialize + DeserializeOwned + 'static,
84+
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone,
85+
{
86+
type Target = HashMap<K, V::Rx>;
87+
88+
fn deref(&self) -> &Self::Target {
89+
&self.0
90+
}
91+
}
92+
// --- Conversion implementation ---
93+
impl<K, V> From<HashMap<K, V>> for RxHashMapNested<K, V>
94+
where
95+
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
96+
V: MakeRx + Serialize + DeserializeOwned + 'static,
97+
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone,
98+
{
99+
fn from(value: HashMap<K, V>) -> Self {
100+
Self(value)
101+
}
102+
}
103+
104+
// --- Freezing implementation ---
105+
impl<K, V> Freeze for RxHashMapNestedRx<K, V>
106+
where
107+
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
108+
V: MakeRx + Serialize + DeserializeOwned + 'static,
109+
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone,
110+
{
111+
fn freeze(&self) -> String {
112+
let unrx = Self(self.0.clone()).make_unrx();
113+
// This should never panic, because we're dealing with a vector
114+
serde_json::to_string(&unrx).unwrap()
115+
}
116+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use crate::state::{Freeze, MakeRx, MakeUnrx};
2+
use serde::{de::DeserializeOwned, Deserialize, Serialize};
3+
use std::ops::Deref;
4+
#[cfg(target_arch = "wasm32")]
5+
use sycamore::prelude::Scope;
6+
use sycamore::reactive::{create_rc_signal, RcSignal};
7+
8+
/// A reactive version of [`Vec`] that uses nested reactivity on its elements.
9+
/// This requires nothing by `Clone + 'static` of the elements inside the
10+
/// vector, and it wraps them in `RcSignal`s to make them reactive. If you want
11+
/// to store nested reactive types inside the vector (e.g. `String`s), you
12+
/// should use [`RxVecNested`].
13+
#[derive(Serialize, Deserialize, Clone, Debug)]
14+
pub struct RxVec<T>(Vec<T>)
15+
where
16+
// We get the `Deserialize` derive macro working by tricking Serde by not
17+
// including the actual bounds here
18+
T: Clone + 'static;
19+
/// The reactive version of [`RxVec`].
20+
#[derive(Clone, Debug)]
21+
pub struct RxVecRx<T>(Vec<RcSignal<T>>)
22+
where
23+
T: Clone + Serialize + DeserializeOwned + 'static;
24+
25+
// --- Reactivity implementations ---
26+
impl<T> MakeRx for RxVec<T>
27+
where
28+
T: Clone + Serialize + DeserializeOwned + 'static,
29+
{
30+
type Rx = RxVecRx<T>;
31+
32+
fn make_rx(self) -> Self::Rx {
33+
RxVecRx(self.0.into_iter().map(|x| create_rc_signal(x)).collect())
34+
}
35+
}
36+
impl<T> MakeUnrx for RxVecRx<T>
37+
where
38+
T: Clone + Serialize + DeserializeOwned + 'static,
39+
{
40+
type Unrx = RxVec<T>;
41+
42+
fn make_unrx(self) -> Self::Unrx {
43+
RxVec(
44+
self.0
45+
.into_iter()
46+
.map(|x| (*x.get_untracked()).clone())
47+
.collect(),
48+
)
49+
}
50+
51+
#[cfg(target_arch = "wasm32")]
52+
fn compute_suspense(&self, cx: Scope) {}
53+
}
54+
// --- Dereferencing ---
55+
impl<T> Deref for RxVec<T>
56+
where
57+
T: Clone + Serialize + DeserializeOwned + 'static,
58+
{
59+
type Target = Vec<T>;
60+
61+
fn deref(&self) -> &Self::Target {
62+
&self.0
63+
}
64+
}
65+
impl<T> Deref for RxVecRx<T>
66+
where
67+
T: Clone + Serialize + DeserializeOwned + 'static,
68+
{
69+
type Target = Vec<RcSignal<T>>;
70+
71+
fn deref(&self) -> &Self::Target {
72+
&self.0
73+
}
74+
}
75+
// --- Conversion implementation ---
76+
impl<T> From<Vec<T>> for RxVec<T>
77+
where
78+
T: Clone + Serialize + DeserializeOwned + 'static,
79+
{
80+
fn from(value: Vec<T>) -> Self {
81+
Self(value)
82+
}
83+
}
84+
85+
// --- Freezing implementation ---
86+
impl<T> Freeze for RxVecRx<T>
87+
where
88+
T: Clone + Serialize + DeserializeOwned + 'static,
89+
{
90+
fn freeze(&self) -> String {
91+
let unrx = Self(self.0.clone()).make_unrx();
92+
// This should never panic, because we're dealing with a vector
93+
serde_json::to_string(&unrx).unwrap()
94+
}
95+
}

0 commit comments

Comments
 (0)