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