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}