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