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}