dioxus_stores/
store.rs

1use crate::{
2    scope::SelectorScope,
3    subscriptions::{StoreSubscriptions, TinyVec},
4};
5use dioxus_core::{
6    use_hook, AttributeValue, DynamicNode, IntoAttributeValue, IntoDynNode, Subscribers, SuperInto,
7};
8use dioxus_signals::{
9    read_impls, write_impls, BorrowError, BorrowMutError, BoxedSignalStorage, CopyValue,
10    CreateBoxedSignalStorage, Global, InitializeFromFunction, MappedMutSignal, ReadSignal,
11    Readable, ReadableExt, ReadableRef, Storage, UnsyncStorage, Writable, WritableExt, WritableRef,
12    WriteSignal,
13};
14use std::marker::PhantomData;
15
16/// A type alias for a store that has been mapped with a function
17pub(crate) type MappedStore<
18    T,
19    Lens,
20    F = fn(&<Lens as Readable>::Target) -> &T,
21    FMut = fn(&mut <Lens as Readable>::Target) -> &mut T,
22> = Store<T, MappedMutSignal<T, Lens, F, FMut>>;
23
24/// A type alias for a boxed read-only store.
25pub type ReadStore<T, S = UnsyncStorage> = Store<T, ReadSignal<T, S>>;
26
27/// A type alias for a boxed writable-only store.
28pub type WriteStore<T, S = UnsyncStorage> = Store<T, WriteSignal<T, S>>;
29
30/// Stores are a reactive type built for nested data structures. Each store will lazily create signals
31/// for each field/member of the data structure as needed.
32///
33/// By default stores act a lot like [`dioxus_signals::Signal`]s, but they provide more granular
34/// subscriptions without requiring nested signals. You should derive [`Store`](dioxus_stores_macro::Store) on your data
35/// structures to generate selectors that let you scope the store to a specific part of your data.
36///
37/// You can also use the [`#[store]`](dioxus_stores_macro::store) macro on an impl block to add any additional methods to your store
38/// with an extension trait. This lets you add methods to the store even though the type is not defined in your crate.
39///
40/// # Example
41///
42/// ```rust, no_run
43/// use dioxus::prelude::*;
44/// use dioxus_stores::*;
45///
46/// fn main() {
47///     dioxus::launch(app);
48/// }
49///
50/// // Deriving the store trait provides methods to scope the store to specific parts of your data structure.
51/// // The `Store` macro generates a `count` and `children` method for the `CounterTree` struct.
52/// #[derive(Store, Default)]
53/// struct CounterTree {
54///     count: i32,
55///     children: Vec<CounterTree>,
56/// }
57///
58/// // The store macro generates an extension trait with additional methods for the store based on the impl block.
59/// #[store]
60/// impl<Lens> Store<CounterTree, Lens> {
61///     // Methods that take &self automatically require the lens to implement `Readable` which lets you read the store.
62///     fn sum(&self) -> i32 {
63///        self.count().cloned() + self.children().iter().map(|c| c.sum()).sum::<i32>()
64///     }
65/// }
66///
67/// fn app() -> Element {
68///     let value = use_store(Default::default);
69///
70///     rsx! {
71///         Tree {
72///             value
73///         }
74///     }
75/// }
76///
77/// #[component]
78/// fn Tree(value: Store<CounterTree>) -> Element {
79///     // Calling the generated `count` method returns a new store that can only
80///     // read and write the count field
81///     let mut count = value.count();
82///     let mut children = value.children();
83///     rsx! {
84///         button {
85///             // Incrementing the count will only rerun parts of the app that have read the count field
86///             onclick: move |_| count += 1,
87///             "Increment"
88///         }
89///         button {
90///             // Stores are aware of data structures like `Vec` and `Hashmap`. When we push an item to the vec
91///             // it will only rerun the parts of the app that depend on the length of the vec
92///             onclick: move |_| children.push(Default::default()),
93///             "Push child"
94///         }
95///         "sum: {value.sum()}"
96///         ul {
97///             // Iterating over the children gives us stores scoped to each child.
98///             for value in children.iter() {
99///                 li {
100///                     Tree { value }
101///                 }
102///             }
103///         }
104///     }
105/// }
106/// ```
107pub struct Store<T: ?Sized, Lens = WriteSignal<T>> {
108    selector: SelectorScope<Lens>,
109    _phantom: PhantomData<Box<T>>,
110}
111
112impl<T: 'static, S: Storage<T>> Store<T, CopyValue<T, S>> {
113    /// Creates a new `Store` that might be sync. This allocates memory in the current scope, so this should only be called
114    /// inside of an initialization closure like the closure passed to [`use_hook`].
115    #[track_caller]
116    pub fn new_maybe_sync(value: T) -> Self {
117        let store = StoreSubscriptions::new();
118        let value = CopyValue::new_maybe_sync(value);
119
120        let path = TinyVec::new();
121        let selector = SelectorScope::new(path, store, value);
122        selector.into()
123    }
124}
125
126impl<T: 'static> Store<T> {
127    /// Creates a new `Store`. This allocates memory in the current scope, so this should only be called
128    /// inside of an initialization closure like the closure passed to [`use_hook`].
129    #[track_caller]
130    pub fn new(value: T) -> Self {
131        let store = StoreSubscriptions::new();
132        let value = CopyValue::new_maybe_sync(value);
133        let value = value.into();
134
135        let path = TinyVec::new();
136        let selector = SelectorScope::new(path, store, value);
137        selector.into()
138    }
139}
140
141impl<T: ?Sized, Lens> Store<T, Lens> {
142    /// Get the underlying selector for this store. The selector provides low level access to the lazy tracking system
143    /// of the store. This can be useful to create selectors for custom data structures in libraries. For most applications
144    /// the selectors generated by the [`Store`](dioxus_stores_macro::Store) macro provide all the functionality you need.
145    pub fn selector(&self) -> &SelectorScope<Lens> {
146        &self.selector
147    }
148
149    /// Convert the store into the underlying selector
150    pub fn into_selector(self) -> SelectorScope<Lens> {
151        self.selector
152    }
153}
154
155impl<T: ?Sized, Lens> From<SelectorScope<Lens>> for Store<T, Lens> {
156    fn from(selector: SelectorScope<Lens>) -> Self {
157        Self {
158            selector,
159            _phantom: PhantomData,
160        }
161    }
162}
163
164impl<T: ?Sized, Lens> PartialEq for Store<T, Lens>
165where
166    Lens: PartialEq,
167{
168    fn eq(&self, other: &Self) -> bool {
169        self.selector == other.selector
170    }
171}
172impl<T: ?Sized, Lens> Clone for Store<T, Lens>
173where
174    Lens: Clone,
175{
176    fn clone(&self) -> Self {
177        Self {
178            selector: self.selector.clone(),
179            _phantom: ::std::marker::PhantomData,
180        }
181    }
182}
183impl<T: ?Sized, Lens> Copy for Store<T, Lens> where Lens: Copy {}
184
185impl<__F, __FMut, T: ?Sized, S, Lens> ::std::convert::From<MappedStore<T, Lens, __F, __FMut>>
186    for WriteStore<T, S>
187where
188    Lens: Writable<Storage = S> + 'static,
189    __F: Fn(&Lens::Target) -> &T + 'static,
190    __FMut: Fn(&mut Lens::Target) -> &mut T + 'static,
191    S: BoxedSignalStorage<T> + CreateBoxedSignalStorage<MappedMutSignal<T, Lens, __F, __FMut>>,
192    T: 'static,
193{
194    fn from(value: MappedStore<T, Lens, __F, __FMut>) -> Self {
195        Store {
196            selector: value.selector.map_writer(::std::convert::Into::into),
197            _phantom: ::std::marker::PhantomData,
198        }
199    }
200}
201impl<__F, __FMut, T: ?Sized, S, Lens> ::std::convert::From<MappedStore<T, Lens, __F, __FMut>>
202    for ReadStore<T, S>
203where
204    Lens: Writable<Storage = S> + 'static,
205    __F: Fn(&Lens::Target) -> &T + 'static,
206    __FMut: Fn(&mut Lens::Target) -> &mut T + 'static,
207    S: BoxedSignalStorage<T> + CreateBoxedSignalStorage<MappedMutSignal<T, Lens, __F, __FMut>>,
208    T: 'static,
209{
210    fn from(value: MappedStore<T, Lens, __F, __FMut>) -> Self {
211        Store {
212            selector: value.selector.map_writer(::std::convert::Into::into),
213            _phantom: ::std::marker::PhantomData,
214        }
215    }
216}
217impl<T, S> ::std::convert::From<WriteStore<T, S>> for ReadStore<T, S>
218where
219    T: ?Sized + 'static,
220    S: BoxedSignalStorage<T> + CreateBoxedSignalStorage<WriteSignal<T, S>>,
221{
222    fn from(value: Store<T, WriteSignal<T, S>>) -> Self {
223        Self {
224            selector: value.selector.map_writer(::std::convert::Into::into),
225            _phantom: ::std::marker::PhantomData,
226        }
227    }
228}
229impl<T, S> ::std::convert::From<Store<T, CopyValue<T, S>>> for ReadStore<T, S>
230where
231    T: 'static,
232    S: BoxedSignalStorage<T> + CreateBoxedSignalStorage<CopyValue<T, S>> + Storage<T>,
233{
234    fn from(value: Store<T, CopyValue<T, S>>) -> Self {
235        Self {
236            selector: value.selector.map_writer(::std::convert::Into::into),
237            _phantom: ::std::marker::PhantomData,
238        }
239    }
240}
241impl<T, S> ::std::convert::From<Store<T, CopyValue<T, S>>> for WriteStore<T, S>
242where
243    T: 'static,
244    S: BoxedSignalStorage<T> + CreateBoxedSignalStorage<CopyValue<T, S>> + Storage<T>,
245{
246    fn from(value: Store<T, CopyValue<T, S>>) -> Self {
247        Self {
248            selector: value.selector.map_writer(::std::convert::Into::into),
249            _phantom: ::std::marker::PhantomData,
250        }
251    }
252}
253
254#[doc(hidden)]
255pub struct SuperIntoReadSignalMarker;
256impl<T, S, Lens> SuperInto<ReadSignal<T, S>, SuperIntoReadSignalMarker> for Store<T, Lens>
257where
258    T: ?Sized + 'static,
259    Lens: Readable<Target = T, Storage = S> + 'static,
260    S: CreateBoxedSignalStorage<Store<T, Lens>> + BoxedSignalStorage<T>,
261{
262    fn super_into(self) -> ReadSignal<T, S> {
263        ReadSignal::new_maybe_sync(self)
264    }
265}
266
267#[doc(hidden)]
268pub struct SuperIntoWriteSignalMarker;
269impl<T, S, Lens> SuperInto<WriteSignal<T, S>, SuperIntoWriteSignalMarker> for Store<T, Lens>
270where
271    T: ?Sized + 'static,
272    Lens: Writable<Target = T, Storage = S> + 'static,
273    S: CreateBoxedSignalStorage<Store<T, Lens>> + BoxedSignalStorage<T>,
274{
275    fn super_into(self) -> WriteSignal<T, S> {
276        WriteSignal::new_maybe_sync(self)
277    }
278}
279
280impl<T: ?Sized, Lens> Readable for Store<T, Lens>
281where
282    Lens: Readable<Target = T>,
283    T: 'static,
284{
285    type Storage = Lens::Storage;
286    type Target = T;
287    fn try_read_unchecked(&self) -> Result<ReadableRef<'static, Self>, BorrowError> {
288        self.selector.try_read_unchecked()
289    }
290    fn try_peek_unchecked(&self) -> Result<ReadableRef<'static, Self>, BorrowError> {
291        self.selector.try_peek_unchecked()
292    }
293    fn subscribers(&self) -> Subscribers {
294        self.selector.subscribers()
295    }
296}
297impl<T: ?Sized, Lens> Writable for Store<T, Lens>
298where
299    Lens: Writable<Target = T>,
300    T: 'static,
301{
302    type WriteMetadata = Lens::WriteMetadata;
303    fn try_write_unchecked(&self) -> Result<WritableRef<'static, Self>, BorrowMutError> {
304        self.selector.try_write_unchecked()
305    }
306}
307impl<T, Lens> IntoAttributeValue for Store<T, Lens>
308where
309    Self: Readable<Target = T>,
310    T: ::std::clone::Clone + IntoAttributeValue + 'static,
311{
312    fn into_value(self) -> AttributeValue {
313        ReadableExt::cloned(&self).into_value()
314    }
315}
316impl<T, Lens> IntoDynNode for Store<T, Lens>
317where
318    Self: Readable<Target = T>,
319    T: ::std::clone::Clone + IntoDynNode + 'static,
320{
321    fn into_dyn_node(self) -> DynamicNode {
322        ReadableExt::cloned(&self).into_dyn_node()
323    }
324}
325impl<T, Lens> ::std::ops::Deref for Store<T, Lens>
326where
327    Self: Readable<Target = T> + 'static,
328    T: ::std::clone::Clone + 'static,
329{
330    type Target = dyn Fn() -> T;
331    fn deref(&self) -> &Self::Target {
332        unsafe { ReadableExt::deref_impl(self) }
333    }
334}
335
336read_impls!(Store<T, Lens> where Lens: Readable<Target = T>);
337write_impls!(Store<T, Lens> where Lens: Writable<Target = T>);
338
339/// Create a new [`Store`]. Stores are a reactive type built for nested data structures.
340///
341///
342/// By default stores act a lot like [`dioxus_signals::Signal`]s, but they provide more granular
343/// subscriptions without requiring nested signals. You should derive [`Store`](dioxus_stores_macro::Store) on your data
344/// structures to generate selectors that let you scope the store to a specific part of your data structure.
345///
346/// # Example
347///
348/// ```rust, no_run
349/// use dioxus::prelude::*;
350/// use dioxus_stores::*;
351///
352/// fn main() {
353///     dioxus::launch(app);
354/// }
355///
356/// // Deriving the store trait provides methods to scope the store to specific parts of your data structure.
357/// // The `Store` macro generates a `count` and `children` method for `Store<CounterTree>`.
358/// #[derive(Store, Default)]
359/// struct CounterTree {
360///     count: i32,
361///     children: Vec<CounterTree>,
362/// }
363///
364/// fn app() -> Element {
365///     let value = use_store(Default::default);
366///
367///     rsx! {
368///         Tree {
369///             value
370///         }
371///     }
372/// }
373///
374/// #[component]
375/// fn Tree(value: Store<CounterTree>) -> Element {
376///     // Calling the generated `count` method returns a new store that can only
377///     // read and write the count field
378///     let mut count = value.count();
379///     let mut children = value.children();
380///     rsx! {
381///         button {
382///             // Incrementing the count will only rerun parts of the app that have read the count field
383///             onclick: move |_| count += 1,
384///             "Increment"
385///         }
386///         button {
387///             // Stores are aware of data structures like `Vec` and `Hashmap`. When we push an item to the vec
388///             // it will only rerun the parts of the app that depend on the length of the vec
389///             onclick: move |_| children.push(Default::default()),
390///             "Push child"
391///         }
392///         ul {
393///             // Iterating over the children gives us stores scoped to each child.
394///             for value in children.iter() {
395///                 li {
396///                     Tree { value }
397///                 }
398///             }
399///         }
400///     }
401/// }
402/// ```
403pub fn use_store<T: 'static>(init: impl FnOnce() -> T) -> Store<T> {
404    use_hook(move || Store::new(init()))
405}
406
407/// A type alias for global stores
408///
409/// # Example
410/// ```rust, no_run
411/// use dioxus::prelude::*;
412/// use dioxus_stores::*;
413///
414/// #[derive(Store)]
415/// struct Counter {
416///    count: i32,
417/// }
418///
419/// static COUNTER: GlobalStore<Counter> = Global::new(|| Counter { count: 0 });
420///
421/// fn app() -> Element {
422///     let mut count = COUNTER.resolve().count();
423///
424///     rsx! {
425///         button {
426///             onclick: move |_| count += 1,
427///             "{count}"
428///         }
429///     }
430/// }
431/// ```
432pub type GlobalStore<T> = Global<Store<T>, T>;
433
434impl<T: 'static> InitializeFromFunction<T> for Store<T> {
435    fn initialize_from_function(f: fn() -> T) -> Self {
436        Store::new(f())
437    }
438}