Skip to content

Commit

Permalink
feat: made rx collections support extension
Browse files Browse the repository at this point in the history
This allows them to have elements added, which I didn't implement
initially because I forgot about it.
  • Loading branch information
arctic-hen7 committed Jan 8, 2023
1 parent ee56bc4 commit 7185ef6
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 28 deletions.
27 changes: 25 additions & 2 deletions examples/core/rx_state/src/templates/index.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
}

// 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<G> {
// 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" }
}
Expand All @@ -39,5 +61,6 @@ pub fn get_template<G: Html>() -> Template<G> {
async fn get_build_state(_info: StateGeneratorInfo<()>) -> IndexPageState {
IndexPageState {
username: "".to_string(),
test: vec!["foo".to_string()].into(),
}
}
12 changes: 6 additions & 6 deletions packages/perseus/src/state/rx_collections/rx_hash_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ where
V: Clone + 'static;
/// The reactive version of [`RxHashMap`].
#[derive(Clone, Debug)]
pub struct RxHashMapRx<K, V>(HashMap<K, RcSignal<V>>)
pub struct RxHashMapRx<K, V>(RcSignal<HashMap<K, RcSignal<V>>>)
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: Clone + Serialize + DeserializeOwned + 'static;
Expand All @@ -35,12 +35,12 @@ where
type Rx = RxHashMapRx<K, V>;

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<K, V> MakeUnrx for RxHashMapRx<K, V>
Expand All @@ -51,9 +51,9 @@ where
type Unrx = RxHashMap<K, V>;

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(),
)
Expand All @@ -79,7 +79,7 @@ where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: Clone + Serialize + DeserializeOwned + 'static,
{
type Target = HashMap<K, RcSignal<V>>;
type Target = RcSignal<HashMap<K, RcSignal<V>>>;

fn deref(&self) -> &Self::Target {
&self.0
Expand Down
21 changes: 11 additions & 10 deletions packages/perseus/src/state/rx_collections/rx_hash_map_nested.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`]
Expand All @@ -21,7 +22,7 @@ where
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone;
/// The reactive version of [`RxHashMapNested`].
#[derive(Clone, Debug)]
pub struct RxHashMapNestedRx<K, V>(HashMap<K, V::Rx>)
pub struct RxHashMapNestedRx<K, V>(RcSignal<HashMap<K, V::Rx>>)
where
K: Clone + Serialize + DeserializeOwned + Eq + Hash,
V: MakeRx + Serialize + DeserializeOwned + 'static,
Expand All @@ -37,7 +38,9 @@ where
type Rx = RxHashMapNestedRx<K, V>;

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<K, V> MakeUnrx for RxHashMapNestedRx<K, V>
Expand All @@ -49,17 +52,15 @@ where
type Unrx = RxHashMapNested<K, V>;

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);
}
}
Expand All @@ -83,7 +84,7 @@ where
V: MakeRx + Serialize + DeserializeOwned + 'static,
V::Rx: MakeUnrx<Unrx = V> + Freeze + Clone,
{
type Target = HashMap<K, V::Rx>;
type Target = RcSignal<HashMap<K, V::Rx>>;

fn deref(&self) -> &Self::Target {
&self.0
Expand Down
12 changes: 7 additions & 5 deletions packages/perseus/src/state/rx_collections/rx_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ where
T: Clone + 'static;
/// The reactive version of [`RxVec`].
#[derive(Clone, Debug)]
pub struct RxVecRx<T>(Vec<RcSignal<T>>)
pub struct RxVecRx<T>(RcSignal<Vec<RcSignal<T>>>)
where
T: Clone + Serialize + DeserializeOwned + 'static;

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

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<T> MakeUnrx for RxVecRx<T>
Expand All @@ -40,9 +42,9 @@ where
type Unrx = RxVec<T>;

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(),
)
Expand All @@ -66,7 +68,7 @@ impl<T> Deref for RxVecRx<T>
where
T: Clone + Serialize + DeserializeOwned + 'static,
{
type Target = Vec<RcSignal<T>>;
type Target = RcSignal<Vec<RcSignal<T>>>;

fn deref(&self) -> &Self::Target {
&self.0
Expand Down
16 changes: 11 additions & 5 deletions packages/perseus/src/state/rx_collections/rx_vec_nested.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -18,7 +19,7 @@ where
T::Rx: MakeUnrx<Unrx = T> + Freeze + Clone;
/// The reactive version of [`RxVecNested`].
#[derive(Clone, Debug)]
pub struct RxVecNestedRx<T>(Vec<T::Rx>)
pub struct RxVecNestedRx<T>(RcSignal<Vec<T::Rx>>)
where
T: MakeRx + Serialize + DeserializeOwned + 'static,
T::Rx: MakeUnrx<Unrx = T> + Freeze + Clone;
Expand All @@ -32,7 +33,9 @@ where
type Rx = RxVecNestedRx<T>;

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<T> MakeUnrx for RxVecNestedRx<T>
Expand All @@ -43,12 +46,15 @@ where
type Unrx = RxVecNested<T>;

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);
}
}
Expand All @@ -70,7 +76,7 @@ where
T: MakeRx + Serialize + DeserializeOwned + 'static,
T::Rx: MakeUnrx<Unrx = T> + Freeze + Clone,
{
type Target = Vec<T::Rx>;
type Target = RcSignal<Vec<T::Rx>>;

fn deref(&self) -> &Self::Target {
&self.0
Expand Down

0 comments on commit 7185ef6

Please sign in to comment.