From 0571ea4103f69ad424d1557264ba0d401357ca99 Mon Sep 17 00:00:00 2001 From: Ramon Klass Date: Wed, 11 Jan 2023 00:36:09 +0100 Subject: [PATCH 1/4] create_slice for RwSignal with 2 closures --- leptos_reactive/src/lib.rs | 2 ++ leptos_reactive/src/slice.rs | 47 ++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 leptos_reactive/src/slice.rs diff --git a/leptos_reactive/src/lib.rs b/leptos_reactive/src/lib.rs index e3ca926d7a..33f705e10a 100644 --- a/leptos_reactive/src/lib.rs +++ b/leptos_reactive/src/lib.rs @@ -81,6 +81,7 @@ mod serialization; mod signal; mod signal_wrappers_read; mod signal_wrappers_write; +mod slice; mod spawn; mod spawn_microtask; mod stored_value; @@ -98,6 +99,7 @@ pub use serialization::*; pub use signal::*; pub use signal_wrappers_read::*; pub use signal_wrappers_write::*; +pub use slice::*; pub use spawn::*; pub use spawn_microtask::*; pub use stored_value::*; diff --git a/leptos_reactive/src/slice.rs b/leptos_reactive/src/slice.rs new file mode 100644 index 0000000000..5626bc109c --- /dev/null +++ b/leptos_reactive/src/slice.rs @@ -0,0 +1,47 @@ +use crate::{create_memo, IntoSignalSetter, RwSignal, Scope, Signal, SignalSetter}; + +/// create a slice of a Signal +/// +/// +/// ``` +/// # use leptos_reactive::*; +/// +/// # let (cx, disposer) = raw_scope_and_disposer(create_runtime()); +/// +/// pub struct Stuff { +/// inner: String, +/// other: i32, +/// } +/// +/// let complex = create_rw_signal( +/// cx, +/// Stuff { +/// inner: "".into(), +/// other: 0, +/// }, +/// ); +/// let (inner, set_inner) = create_slice(cx, complex, |c| c.inner.clone(), |c, s| c.inner = s); +/// let logs = create_rw_signal(cx, 0); +/// logs.with(|logs| assert_eq!(logs, &0)); +/// create_effect(cx, move |_| { inner.with(|inner| {}); logs.update(|logs| *logs += 1) }); +/// logs.with(|logs| assert_eq!(logs, &1)); +/// set_inner.set("Hello World".into()); +/// logs.with(|logs| assert_eq!(logs, &2)); +/// complex.update(|complex| complex.other = 10); +/// logs.with(|logs| assert_eq!(logs, &2)); +/// ``` +pub fn create_slice( + cx: Scope, + signal: RwSignal, + getter: G, + setter: S, +) -> (Signal, SignalSetter) +where + G: Fn(&T) -> O + Clone + Copy + 'static, + S: Fn(&mut T, O) + Clone + Copy + 'static, + O: Eq + core::fmt::Debug, +{ + let getter = create_memo(cx, move |_| signal.with(getter)); + let setter = move |value| signal.update(|x| setter(x, value)); + (getter.into(), setter.mapped_signal_setter(cx)) +} From 56426170b07a50ff79e4961273ddca634dcde17c Mon Sep 17 00:00:00 2001 From: Ramon Klass Date: Wed, 11 Jan 2023 00:56:17 +0100 Subject: [PATCH 2/4] remove Debug from create_memo --- leptos_reactive/src/memo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leptos_reactive/src/memo.rs b/leptos_reactive/src/memo.rs index 07447de17e..480d07921e 100644 --- a/leptos_reactive/src/memo.rs +++ b/leptos_reactive/src/memo.rs @@ -67,7 +67,7 @@ use std::fmt::Debug; )] pub fn create_memo(cx: Scope, f: impl Fn(Option<&T>) -> T + 'static) -> Memo where - T: PartialEq + Debug + 'static, + T: PartialEq + 'static, { cx.runtime.create_memo(f) } From 89be6bc68e82f3673a25ff500e4f205e06d9e793 Mon Sep 17 00:00:00 2001 From: Ramon Klass Date: Wed, 11 Jan 2023 00:56:42 +0100 Subject: [PATCH 3/4] removed Debug from create_slice --- leptos_reactive/src/slice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leptos_reactive/src/slice.rs b/leptos_reactive/src/slice.rs index 5626bc109c..00789e5895 100644 --- a/leptos_reactive/src/slice.rs +++ b/leptos_reactive/src/slice.rs @@ -39,7 +39,7 @@ pub fn create_slice( where G: Fn(&T) -> O + Clone + Copy + 'static, S: Fn(&mut T, O) + Clone + Copy + 'static, - O: Eq + core::fmt::Debug, + O: Eq, { let getter = create_memo(cx, move |_| signal.with(getter)); let setter = move |value| signal.update(|x| setter(x, value)); From 9180aaad7e96bc08c056b0e23df4a96a0ce3e93c Mon Sep 17 00:00:00 2001 From: Ramon Klass Date: Thu, 12 Jan 2023 16:30:38 +0100 Subject: [PATCH 4/4] create_slice: actual documentation --- leptos_reactive/src/slice.rs | 75 +++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/leptos_reactive/src/slice.rs b/leptos_reactive/src/slice.rs index 00789e5895..cab6ecddb5 100644 --- a/leptos_reactive/src/slice.rs +++ b/leptos_reactive/src/slice.rs @@ -1,44 +1,73 @@ use crate::{create_memo, IntoSignalSetter, RwSignal, Scope, Signal, SignalSetter}; -/// create a slice of a Signal +/// derives a reactive slice from an [RwSignal](crate::RwSignal) /// +/// Slices have the same guarantees as [Memos](crate::Memo), +/// they only emit their value when it has actually been changed. +/// +/// slices need a getter and a setter, and you must make sure that +/// the setter and getter only touch their respective field and nothing else. +/// They optimally should not have any side effects. +/// +/// you can use slices whenever you want to react to only parts +/// of a bigger signal, the prime example would be state management +/// where you want all state variables grouped up but also need +/// fine-grained signals for each or some of these variables. +/// In the example below, setting an auth token will only trigger +/// the token signal, but none of the other derived signals. /// /// ``` /// # use leptos_reactive::*; -/// /// # let (cx, disposer) = raw_scope_and_disposer(create_runtime()); /// -/// pub struct Stuff { -/// inner: String, -/// other: i32, +/// // this could be serialized to and from localstorage with miniserde +/// pub struct State { +/// token: String, +/// dark_mode: bool, /// } /// -/// let complex = create_rw_signal( +/// let state = create_rw_signal( /// cx, -/// Stuff { -/// inner: "".into(), -/// other: 0, +/// State { +/// token: "".into(), +/// // this would cause flickering on reload, +/// // use a cookie for the initial value in real projects +/// dark_mode: false, /// }, /// ); -/// let (inner, set_inner) = create_slice(cx, complex, |c| c.inner.clone(), |c, s| c.inner = s); -/// let logs = create_rw_signal(cx, 0); -/// logs.with(|logs| assert_eq!(logs, &0)); -/// create_effect(cx, move |_| { inner.with(|inner| {}); logs.update(|logs| *logs += 1) }); -/// logs.with(|logs| assert_eq!(logs, &1)); -/// set_inner.set("Hello World".into()); -/// logs.with(|logs| assert_eq!(logs, &2)); -/// complex.update(|complex| complex.other = 10); -/// logs.with(|logs| assert_eq!(logs, &2)); +/// let (token, set_token) = create_slice( +/// cx, +/// state, +/// |state| state.token.clone(), +/// |state, value| state.token = value, +/// ); +/// let (dark_mode, set_dark_mode) = create_slice( +/// cx, +/// state, +/// |state| state.dark_mode, +/// |state, value| state.dark_mode = value, +/// ); +/// let count_token_updates = create_rw_signal(cx, 0); +/// count_token_updates.with(|counter| assert_eq!(counter, &0)); +/// create_effect(cx, move |_| { +/// token.with(|_| {}); +/// count_token_updates.update(|counter| *counter += 1) +/// }); +/// count_token_updates.with(|counter| assert_eq!(counter, &1)); +/// set_token.set("this is not a token!".into()); +/// // token was updated with the new token +/// count_token_updates.with(|counter| assert_eq!(counter, &2)); +/// set_dark_mode.set(true); +/// // since token didn't change, there was also no update emitted +/// count_token_updates.with(|counter| assert_eq!(counter, &2)); /// ``` -pub fn create_slice( +pub fn create_slice( cx: Scope, signal: RwSignal, - getter: G, - setter: S, + getter: impl Fn(&T) -> O + Clone + Copy + 'static, + setter: impl Fn(&mut T, O) + Clone + Copy + 'static, ) -> (Signal, SignalSetter) where - G: Fn(&T) -> O + Clone + Copy + 'static, - S: Fn(&mut T, O) + Clone + Copy + 'static, O: Eq, { let getter = create_memo(cx, move |_| signal.with(getter));