1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
use crate::{
create_memo, IntoSignalSetter, RwSignal, Scope, Signal, SignalSetter,
SignalUpdate, SignalWith,
};
/// Derives a reactive slice of an [`RwSignal`](crate::RwSignal).
///
/// Slices have the same guarantees as [`Memo`s](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 together, 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());
///
/// // some global state with independent fields
/// #[derive(Default, Clone, Debug)]
/// struct GlobalState {
/// count: u32,
/// name: String,
/// }
///
/// let state = create_rw_signal(cx, GlobalState::default());
///
/// // `create_slice` lets us create a "lens" into the data
/// let (count, set_count) = create_slice(
/// cx,
/// // we take a slice *from* `state`
/// state,
/// // our getter returns a "slice" of the data
/// |state| state.count,
/// // our setter describes how to mutate that slice, given a new value
/// |state, n| state.count = n,
/// );
///
/// // this slice is completely independent of the `count` slice
/// // neither of them will cause the other to rerun
/// let (name, set_name) = create_slice(
/// cx,
/// // we take a slice *from* `state`
/// state,
/// // our getter returns a "slice" of the data
/// |state| state.name.clone(),
/// // our setter describes how to mutate that slice, given a new value
/// |state, n| state.name = n,
/// );
///
/// create_effect(cx, move |_| {
/// // note: in the browser, use leptos::log! instead
/// println!("name is {}", name.get());
/// });
/// create_effect(cx, move |_| {
/// println!("count is {}", count.get());
/// });
///
/// // setting count only causes count to log, not name
/// set_count.set(42);
///
/// // setting name only causes name to log, not count
/// set_name.set("Bob".into());
/// ```
#[track_caller]
pub fn create_slice<T, O, S>(
cx: Scope,
signal: RwSignal<T>,
getter: impl Fn(&T) -> O + Clone + Copy + 'static,
setter: impl Fn(&mut T, S) + Clone + Copy + 'static,
) -> (Signal<O>, SignalSetter<S>)
where
O: PartialEq,
{
(
create_read_slice(cx, signal, getter),
create_write_slice(cx, signal, setter),
)
}
/// Takes a memoized, read-only slice of a signal. This is equivalent to the
/// read-only half of [`create_slice`].
#[track_caller]
pub fn create_read_slice<T, O>(
cx: Scope,
signal: RwSignal<T>,
getter: impl Fn(&T) -> O + Clone + Copy + 'static,
) -> Signal<O>
where
O: PartialEq,
{
create_memo(cx, move |_| signal.with(getter)).into()
}
/// Creates a setter to access one slice of a signal. This is equivalent to the
/// write-only half of [`create_slice`].
#[track_caller]
pub fn create_write_slice<T, O>(
cx: Scope,
signal: RwSignal<T>,
setter: impl Fn(&mut T, O) + Clone + Copy + 'static,
) -> SignalSetter<O> {
let setter = move |value| signal.update(|x| setter(x, value));
setter.mapped_signal_setter(cx)
}