dioxus_stores/impls/
hashmap.rs

1//! Additional utilities for `HashMap` stores.
2
3use std::{
4    borrow::Borrow,
5    collections::HashMap,
6    hash::{BuildHasher, Hash},
7    iter::FusedIterator,
8    panic::Location,
9};
10
11use crate::{store::Store, ReadStore};
12use dioxus_signals::{
13    AnyStorage, BorrowError, BorrowMutError, ReadSignal, Readable, ReadableExt, UnsyncStorage,
14    Writable, WriteLock, WriteSignal,
15};
16use generational_box::ValueDroppedError;
17
18impl<Lens: Readable<Target = HashMap<K, V, St>> + 'static, K: 'static, V: 'static, St: 'static>
19    Store<HashMap<K, V, St>, Lens>
20{
21    /// Get the length of the HashMap. This method will track the store shallowly and only cause
22    /// re-runs when items are added or removed from the map, not when existing values are modified.
23    ///
24    /// # Example
25    /// ```rust, no_run
26    /// use dioxus_stores::*;
27    /// use dioxus::prelude::*;
28    /// use std::collections::HashMap;
29    /// let mut store = use_store(|| HashMap::new());
30    /// assert_eq!(store.len(), 0);
31    /// store.insert(0, "value".to_string());
32    /// assert_eq!(store.len(), 1);
33    /// ```
34    pub fn len(&self) -> usize {
35        self.selector().track_shallow();
36        self.selector().peek().len()
37    }
38
39    /// Check if the HashMap is empty. This method will track the store shallowly and only cause
40    /// re-runs when items are added or removed from the map, not when existing values are modified.
41    ///
42    /// # Example
43    /// ```rust, no_run
44    /// use dioxus_stores::*;
45    /// use dioxus::prelude::*;
46    /// use std::collections::HashMap;
47    /// let mut store = use_store(|| HashMap::new());
48    /// assert!(store.is_empty());
49    /// store.insert(0, "value".to_string());
50    /// assert!(!store.is_empty());
51    /// ```
52    pub fn is_empty(&self) -> bool {
53        self.selector().track_shallow();
54        self.selector().peek().is_empty()
55    }
56
57    /// Iterate over the current entries in the HashMap, returning a tuple of the key and a store for the value. This method
58    /// will track the store shallowly and only cause re-runs when items are added or removed from the map, not when existing
59    /// values are modified.
60    ///
61    /// # Example
62    ///
63    /// ```rust, no_run
64    /// use dioxus_stores::*;
65    /// use dioxus::prelude::*;
66    /// use std::collections::HashMap;
67    /// let mut store = use_store(|| HashMap::new());
68    /// store.insert(0, "value1".to_string());
69    /// store.insert(1, "value2".to_string());
70    /// for (key, value_store) in store.iter() {
71    ///     println!("{}: {}", key, value_store.read());
72    /// }
73    /// ```
74    pub fn iter(
75        &self,
76    ) -> impl ExactSizeIterator<Item = (K, Store<V, GetWrite<K, Lens>>)>
77           + DoubleEndedIterator
78           + FusedIterator
79           + '_
80    where
81        K: Eq + Hash + Clone,
82        St: BuildHasher,
83        Lens: Clone,
84    {
85        self.selector().track_shallow();
86        let keys: Vec<_> = self.selector().peek_unchecked().keys().cloned().collect();
87        keys.into_iter()
88            .map(move |key| (key.clone(), self.clone().get_unchecked(key)))
89    }
90
91    /// Get an iterator over the values in the HashMap. This method will track the store shallowly and only cause
92    /// re-runs when items are added or removed from the map, not when existing values are modified.
93    ///
94    /// # Example
95    /// ```rust, no_run
96    /// use dioxus_stores::*;
97    /// use dioxus::prelude::*;
98    /// use std::collections::HashMap;
99    /// let mut store = use_store(|| HashMap::new());
100    /// store.insert(0, "value1".to_string());
101    /// store.insert(1, "value2".to_string());
102    /// for value_store in store.values() {
103    ///     println!("{}", value_store.read());
104    /// }
105    /// ```
106    pub fn values(
107        &self,
108    ) -> impl ExactSizeIterator<Item = Store<V, GetWrite<K, Lens>>>
109           + DoubleEndedIterator
110           + FusedIterator
111           + '_
112    where
113        K: Eq + Hash + Clone,
114        St: BuildHasher,
115        Lens: Clone,
116    {
117        self.selector().track_shallow();
118        let keys = self.selector().peek().keys().cloned().collect::<Vec<_>>();
119        keys.into_iter()
120            .map(move |key| self.clone().get_unchecked(key))
121    }
122
123    /// Insert a new key-value pair into the HashMap. This method will mark the store as shallowly dirty, causing
124    /// re-runs of any reactive scopes that depend on the shape of the map.
125    ///
126    /// # Example
127    /// ```rust, no_run
128    /// use dioxus_stores::*;
129    /// use dioxus::prelude::*;
130    /// use std::collections::HashMap;
131    /// let mut store = use_store(|| HashMap::new());
132    /// assert!(store.get(0).is_none());
133    /// store.insert(0, "value".to_string());
134    /// assert_eq!(store.get(0).unwrap().cloned(), "value".to_string());
135    /// ```
136    pub fn insert(&mut self, key: K, value: V)
137    where
138        K: Eq + Hash,
139        St: BuildHasher,
140        Lens: Writable,
141    {
142        // Mark the store itself as dirty since the keys may have changed
143        self.selector().mark_dirty_shallow();
144        // Mark the existing value as dirty if it exists
145        self.selector()
146            .as_ref()
147            .hash_child_unmapped(key.borrow())
148            .mark_dirty();
149        self.selector().write_untracked().insert(key, value);
150    }
151
152    /// Remove a key-value pair from the HashMap. This method will mark the store as shallowly dirty, causing
153    /// re-runs of any reactive scopes that depend on the shape of the map or the value of the removed key.
154    ///
155    /// # Example
156    /// ```rust, no_run
157    /// use dioxus_stores::*;
158    /// use dioxus::prelude::*;
159    /// use std::collections::HashMap;
160    /// let mut store = use_store(|| HashMap::new());
161    /// store.insert(0, "value".to_string());
162    /// assert_eq!(store.get(0).unwrap().cloned(), "value".to_string());
163    /// let removed_value = store.remove(&0);
164    /// assert_eq!(removed_value, Some("value".to_string()));
165    /// assert!(store.get(0).is_none());
166    /// ```
167    pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
168    where
169        Q: ?Sized + Hash + Eq + 'static,
170        K: Borrow<Q> + Eq + Hash,
171        St: BuildHasher,
172        Lens: Writable,
173    {
174        self.selector().mark_dirty_shallow();
175        self.selector().write_untracked().remove(key)
176    }
177
178    /// Clear the HashMap, removing all key-value pairs. This method will mark the store as shallowly dirty,
179    /// causing re-runs of any reactive scopes that depend on the shape of the map.
180    ///
181    /// # Example
182    /// ```rust, no_run
183    /// use dioxus_stores::*;
184    /// use dioxus::prelude::*;
185    /// use std::collections::HashMap;
186    /// let mut store = use_store(|| HashMap::new());
187    /// store.insert(1, "value1".to_string());
188    /// store.insert(2, "value2".to_string());
189    /// assert_eq!(store.len(), 2);
190    /// store.clear();
191    /// assert!(store.is_empty());
192    /// ```
193    pub fn clear(&mut self)
194    where
195        Lens: Writable,
196    {
197        self.selector().mark_dirty_shallow();
198        self.selector().write_untracked().clear();
199    }
200
201    /// Retain only the key-value pairs that satisfy the given predicate. This method will mark the store as shallowly dirty,
202    /// causing re-runs of any reactive scopes that depend on the shape of the map or the values retained.
203    ///
204    /// # Example
205    /// ```rust, no_run
206    /// use dioxus_stores::*;
207    /// use dioxus::prelude::*;
208    /// use std::collections::HashMap;
209    /// let mut store = use_store(|| HashMap::new());
210    /// store.insert(1, "value1".to_string());
211    /// store.insert(2, "value2".to_string());
212    /// store.retain(|key, value| *key == 1);
213    /// assert_eq!(store.len(), 1);
214    /// assert!(store.get(1).is_some());
215    /// assert!(store.get(2).is_none());
216    /// ```
217    pub fn retain(&mut self, mut f: impl FnMut(&K, &V) -> bool)
218    where
219        Lens: Writable,
220    {
221        self.selector().mark_dirty_shallow();
222        self.selector().write_untracked().retain(|k, v| f(k, v));
223    }
224
225    /// Check if the HashMap contains a key. This method will track the store shallowly and only cause
226    /// re-runs when items are added or removed from the map, not when existing values are modified.
227    ///
228    /// # Example
229    /// ```rust, no_run
230    /// use dioxus_stores::*;
231    /// use dioxus::prelude::*;
232    /// use std::collections::HashMap;
233    /// let mut store = use_store(|| HashMap::new());
234    /// assert!(!store.contains_key(&0));
235    /// store.insert(0, "value".to_string());
236    /// assert!(store.contains_key(&0));
237    /// ```
238    pub fn contains_key<Q>(&self, key: &Q) -> bool
239    where
240        Q: ?Sized + Hash + Eq + 'static,
241        K: Borrow<Q> + Eq + Hash,
242        St: BuildHasher,
243    {
244        self.selector().track_shallow();
245        self.selector().peek().contains_key(key)
246    }
247
248    /// Get a store for the value associated with the given key. This method creates a new store scope
249    /// that tracks just changes to the value associated with the key.
250    ///
251    /// # Example
252    /// ```rust, no_run
253    /// use dioxus_stores::*;
254    /// use dioxus::prelude::*;
255    /// use std::collections::HashMap;
256    /// let mut store = use_store(|| HashMap::new());
257    /// assert!(store.get(0).is_none());
258    /// store.insert(0, "value".to_string());
259    /// assert_eq!(store.get(0).unwrap().cloned(), "value".to_string());
260    /// ```
261    pub fn get<Q>(self, key: Q) -> Option<Store<V, GetWrite<Q, Lens>>>
262    where
263        Q: Hash + Eq + 'static,
264        K: Borrow<Q> + Eq + Hash,
265        St: BuildHasher,
266    {
267        self.contains_key(&key).then(|| self.get_unchecked(key))
268    }
269
270    /// Get a store for the value associated with the given key without checking if the key exists.
271    /// This method creates a new store scope that tracks just changes to the value associated with the key.
272    ///
273    /// This is not unsafe, but it will panic when you try to read the value if it does not exist.
274    ///
275    /// # Example
276    /// ```rust, no_run
277    /// use dioxus_stores::*;
278    /// use dioxus::prelude::*;
279    /// use std::collections::HashMap;
280    /// let mut store = use_store(|| HashMap::new());
281    /// store.insert(0, "value".to_string());
282    /// assert_eq!(store.get_unchecked(0).cloned(), "value".to_string());
283    /// ```
284    #[track_caller]
285    pub fn get_unchecked<Q>(self, key: Q) -> Store<V, GetWrite<Q, Lens>>
286    where
287        Q: Hash + Eq + 'static,
288        K: Borrow<Q> + Eq + Hash,
289        St: BuildHasher,
290    {
291        let location = Location::caller();
292        self.into_selector()
293            .hash_child_unmapped(key.borrow())
294            .map_writer(move |writer| GetWrite {
295                index: key,
296                write: writer,
297                created: location,
298            })
299            .into()
300    }
301}
302
303/// A specific index in a `Readable` / `Writable` hashmap
304#[derive(Clone, Copy)]
305pub struct GetWrite<Index, Write> {
306    index: Index,
307    write: Write,
308    created: &'static Location<'static>,
309}
310
311impl<Index, Write, K, V, St> Readable for GetWrite<Index, Write>
312where
313    Write: Readable<Target = HashMap<K, V, St>>,
314    Index: Hash + Eq + 'static,
315    K: Borrow<Index> + Eq + Hash + 'static,
316    St: BuildHasher + 'static,
317{
318    type Target = V;
319
320    type Storage = Write::Storage;
321
322    fn try_read_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>
323    where
324        Self::Target: 'static,
325    {
326        self.write.try_read_unchecked().and_then(|value| {
327            Self::Storage::try_map(value, |value: &Write::Target| value.get(&self.index))
328                .ok_or_else(|| BorrowError::Dropped(ValueDroppedError::new(self.created)))
329        })
330    }
331
332    fn try_peek_unchecked(&self) -> Result<dioxus_signals::ReadableRef<'static, Self>, BorrowError>
333    where
334        Self::Target: 'static,
335    {
336        self.write.try_peek_unchecked().and_then(|value| {
337            Self::Storage::try_map(value, |value: &Write::Target| value.get(&self.index))
338                .ok_or_else(|| BorrowError::Dropped(ValueDroppedError::new(self.created)))
339        })
340    }
341
342    fn subscribers(&self) -> dioxus_core::Subscribers
343    where
344        Self::Target: 'static,
345    {
346        self.write.subscribers()
347    }
348}
349
350impl<Index, Write, K, V, St> Writable for GetWrite<Index, Write>
351where
352    Write: Writable<Target = HashMap<K, V, St>>,
353    Index: Hash + Eq + 'static,
354    K: Borrow<Index> + Eq + Hash + 'static,
355    St: BuildHasher + 'static,
356{
357    type WriteMetadata = Write::WriteMetadata;
358
359    fn try_write_unchecked(
360        &self,
361    ) -> Result<dioxus_signals::WritableRef<'static, Self>, BorrowMutError>
362    where
363        Self::Target: 'static,
364    {
365        self.write.try_write_unchecked().and_then(|value| {
366            WriteLock::filter_map(value, |value: &mut Write::Target| {
367                value.get_mut(&self.index)
368            })
369            .ok_or_else(|| BorrowMutError::Dropped(ValueDroppedError::new(self.created)))
370        })
371    }
372}
373
374impl<Index, Write, K, V, St> ::std::convert::From<Store<V, GetWrite<Index, Write>>>
375    for Store<V, WriteSignal<V>>
376where
377    Write::WriteMetadata: 'static,
378    Write: Writable<Target = HashMap<K, V, St>, Storage = UnsyncStorage> + 'static,
379    Index: Hash + Eq + 'static,
380    K: Borrow<Index> + Eq + Hash + 'static,
381    St: BuildHasher + 'static,
382    V: 'static,
383{
384    fn from(value: Store<V, GetWrite<Index, Write>>) -> Self {
385        value
386            .into_selector()
387            .map_writer(|writer| WriteSignal::new(writer))
388            .into()
389    }
390}
391
392impl<Index, Write, K, V, St> ::std::convert::From<Store<V, GetWrite<Index, Write>>> for ReadStore<V>
393where
394    Write: Readable<Target = HashMap<K, V, St>, Storage = UnsyncStorage> + 'static,
395    Index: Hash + Eq + 'static,
396    K: Borrow<Index> + Eq + Hash + 'static,
397    St: BuildHasher + 'static,
398    V: 'static,
399{
400    fn from(value: Store<V, GetWrite<Index, Write>>) -> Self {
401        value
402            .into_selector()
403            .map_writer(|writer| ReadSignal::new(writer))
404            .into()
405    }
406}