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}