dioxus_signals/write.rs
1use std::{
2 collections::{HashMap, HashSet},
3 ops::{Deref, DerefMut, IndexMut},
4};
5
6use generational_box::{AnyStorage, UnsyncStorage};
7
8use crate::{ext_methods, read::Readable, read::ReadableExt, MappedMutSignal, WriteSignal};
9
10/// A reference to a value that can be written to.
11#[allow(type_alias_bounds)]
12pub type WritableRef<'a, T: Writable, O = <T as Readable>::Target> =
13 WriteLock<'a, O, <T as Readable>::Storage, <T as Writable>::WriteMetadata>;
14
15/// A trait for states that can be written to like [`crate::Signal`]. You may choose to accept this trait as a parameter instead of the concrete type to allow for more flexibility in your API.
16///
17/// # Example
18/// ```rust
19/// # use dioxus::prelude::*;
20/// enum MyEnum {
21/// String(String),
22/// Number(i32),
23/// }
24///
25/// fn MyComponent(mut count: Signal<MyEnum>) -> Element {
26/// rsx! {
27/// button {
28/// onclick: move |_| {
29/// // You can use any methods from the Writable trait on Signals
30/// match &mut *count.write() {
31/// MyEnum::String(s) => s.push('a'),
32/// MyEnum::Number(n) => *n += 1,
33/// }
34/// },
35/// "Add value"
36/// }
37/// }
38/// }
39/// ```
40pub trait Writable: Readable {
41 /// Additional data associated with the write reference.
42 type WriteMetadata;
43
44 /// Try to get a mutable reference to the value without checking the lifetime. This will update any subscribers.
45 ///
46 /// NOTE: This method is completely safe because borrow checking is done at runtime.
47 fn try_write_unchecked(
48 &self,
49 ) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError>
50 where
51 Self::Target: 'static;
52}
53
54/// A mutable reference to a writable value. This reference acts similarly to [`std::cell::RefMut`], but it has extra debug information
55/// and integrates with the reactive system to automatically update dependents.
56///
57/// [`WriteLock`] implements [`DerefMut`] which means you can call methods on the inner value just like you would on a mutable reference
58/// to the inner value. If you need to get the inner reference directly, you can call [`WriteLock::deref_mut`].
59///
60/// # Example
61/// ```rust
62/// # use dioxus::prelude::*;
63/// fn app() -> Element {
64/// let mut value = use_signal(|| String::from("hello"));
65///
66/// rsx! {
67/// button {
68/// onclick: move |_| {
69/// let mut mutable_reference = value.write();
70///
71/// // You call methods like `push_str` on the reference just like you would with the inner String
72/// mutable_reference.push_str("world");
73/// },
74/// "Click to add world to the string"
75/// }
76/// div { "{value}" }
77/// }
78/// }
79/// ```
80///
81/// ## Matching on WriteLock
82///
83/// You need to get the inner mutable reference with [`WriteLock::deref_mut`] before you match the inner value. If you try to match
84/// without calling [`WriteLock::deref_mut`], you will get an error like this:
85///
86/// ```compile_fail
87/// # use dioxus::prelude::*;
88/// #[derive(Debug)]
89/// enum Colors {
90/// Red(u32),
91/// Green
92/// }
93/// fn app() -> Element {
94/// let mut value = use_signal(|| Colors::Red(0));
95///
96/// rsx! {
97/// button {
98/// onclick: move |_| {
99/// let mut mutable_reference = value.write();
100///
101/// match mutable_reference {
102/// // Since we are matching on the `Write` type instead of &mut Colors, we can't match on the enum directly
103/// Colors::Red(brightness) => *brightness += 1,
104/// Colors::Green => {}
105/// }
106/// },
107/// "Click to add brightness to the red color"
108/// }
109/// div { "{value:?}" }
110/// }
111/// }
112/// ```
113///
114/// ```text
115/// error[E0308]: mismatched types
116/// --> src/main.rs:18:21
117/// |
118/// 16 | match mutable_reference {
119/// | ----------------- this expression has type `dioxus::prelude::Write<'_, Colors>`
120/// 17 | // Since we are matching on the `Write` t...
121/// 18 | Colors::Red(brightness) => *brightness += 1,
122/// | ^^^^^^^^^^^^^^^^^^^^^^^ expected `Write<'_, Colors>`, found `Colors`
123/// |
124/// = note: expected struct `dioxus::prelude::Write<'_, Colors, >`
125/// found enum `Colors`
126/// ```
127///
128/// Instead, you need to call deref mut on the reference to get the inner value **before** you match on it:
129///
130/// ```rust
131/// use std::ops::DerefMut;
132/// # use dioxus::prelude::*;
133/// #[derive(Debug)]
134/// enum Colors {
135/// Red(u32),
136/// Green
137/// }
138/// fn app() -> Element {
139/// let mut value = use_signal(|| Colors::Red(0));
140///
141/// rsx! {
142/// button {
143/// onclick: move |_| {
144/// let mut mutable_reference = value.write();
145///
146/// // DerefMut converts the `Write` into a `&mut Colors`
147/// match mutable_reference.deref_mut() {
148/// // Now we can match on the inner value
149/// Colors::Red(brightness) => *brightness += 1,
150/// Colors::Green => {}
151/// }
152/// },
153/// "Click to add brightness to the red color"
154/// }
155/// div { "{value:?}" }
156/// }
157/// }
158/// ```
159///
160/// ## Generics
161/// - T is the current type of the write
162/// - S is the storage type of the signal. This type determines if the signal is local to the current thread, or it can be shared across threads.
163/// - D is the additional data associated with the write reference. This is used by signals to track when the write is dropped
164pub struct WriteLock<'a, T: ?Sized + 'a, S: AnyStorage = UnsyncStorage, D = ()> {
165 write: S::Mut<'a, T>,
166 data: D,
167}
168
169impl<'a, T: ?Sized, S: AnyStorage> WriteLock<'a, T, S> {
170 /// Create a new write reference
171 pub fn new(write: S::Mut<'a, T>) -> Self {
172 Self { write, data: () }
173 }
174}
175
176impl<'a, T: ?Sized, S: AnyStorage, D> WriteLock<'a, T, S, D> {
177 /// Create a new write reference with additional data.
178 pub fn new_with_metadata(write: S::Mut<'a, T>, data: D) -> Self {
179 Self { write, data }
180 }
181
182 /// Get the inner value of the write reference.
183 pub fn into_inner(self) -> S::Mut<'a, T> {
184 self.write
185 }
186
187 /// Get the additional data associated with the write reference.
188 pub fn data(&self) -> &D {
189 &self.data
190 }
191
192 /// Split into the inner value and the additional data.
193 pub fn into_parts(self) -> (S::Mut<'a, T>, D) {
194 (self.write, self.data)
195 }
196
197 /// Map the metadata of the write reference to a new type.
198 pub fn map_metadata<O>(self, f: impl FnOnce(D) -> O) -> WriteLock<'a, T, S, O> {
199 WriteLock {
200 write: self.write,
201 data: f(self.data),
202 }
203 }
204
205 /// Map the mutable reference to the signal's value to a new type.
206 pub fn map<O: ?Sized>(
207 myself: Self,
208 f: impl FnOnce(&mut T) -> &mut O,
209 ) -> WriteLock<'a, O, S, D> {
210 let Self { write, data, .. } = myself;
211 WriteLock {
212 write: S::map_mut(write, f),
213 data,
214 }
215 }
216
217 /// Try to map the mutable reference to the signal's value to a new type
218 pub fn filter_map<O: ?Sized>(
219 myself: Self,
220 f: impl FnOnce(&mut T) -> Option<&mut O>,
221 ) -> Option<WriteLock<'a, O, S, D>> {
222 let Self { write, data, .. } = myself;
223 let write = S::try_map_mut(write, f);
224 write.map(|write| WriteLock { write, data })
225 }
226
227 /// Downcast the lifetime of the mutable reference to the signal's value.
228 ///
229 /// This function enforces the variance of the lifetime parameter `'a` in Mut. Rust will typically infer this cast with a concrete type, but it cannot with a generic type.
230 pub fn downcast_lifetime<'b>(mut_: Self) -> WriteLock<'b, T, S, D>
231 where
232 'a: 'b,
233 {
234 WriteLock {
235 write: S::downcast_lifetime_mut(mut_.write),
236 data: mut_.data,
237 }
238 }
239}
240
241impl<T, S, D> Deref for WriteLock<'_, T, S, D>
242where
243 S: AnyStorage,
244 T: ?Sized,
245{
246 type Target = T;
247
248 fn deref(&self) -> &Self::Target {
249 &self.write
250 }
251}
252
253impl<T, S, D> DerefMut for WriteLock<'_, T, S, D>
254where
255 S: AnyStorage,
256 T: ?Sized,
257{
258 fn deref_mut(&mut self) -> &mut Self::Target {
259 &mut self.write
260 }
261}
262
263/// An extension trait for [`Writable`] that provides some convenience methods.
264pub trait WritableExt: Writable {
265 /// Get a mutable reference to the value. If the value has been dropped, this will panic.
266 #[track_caller]
267 fn write(&mut self) -> WritableRef<'_, Self>
268 where
269 Self::Target: 'static,
270 {
271 self.try_write().unwrap()
272 }
273
274 /// Try to get a mutable reference to the value.
275 #[track_caller]
276 fn try_write(&mut self) -> Result<WritableRef<'_, Self>, generational_box::BorrowMutError>
277 where
278 Self::Target: 'static,
279 {
280 self.try_write_unchecked().map(WriteLock::downcast_lifetime)
281 }
282
283 /// Get a mutable reference to the value without checking the lifetime. This will update any subscribers.
284 ///
285 /// NOTE: This method is completely safe because borrow checking is done at runtime.
286 #[track_caller]
287 fn write_unchecked(&self) -> WritableRef<'static, Self>
288 where
289 Self::Target: 'static,
290 {
291 self.try_write_unchecked().unwrap()
292 }
293
294 /// Map the references and mutable references of the writable value to a new type. This lets you provide a view
295 /// into the writable value without creating a new signal or cloning the value.
296 ///
297 /// Anything that subscribes to the writable value will be rerun whenever the original value changes or you write to this
298 /// scoped value, even if the view does not change. If you want to memorize the view, you can use a [`crate::Memo`] instead.
299 /// For fine grained scoped updates, use stores instead
300 ///
301 /// # Example
302 /// ```rust
303 /// # use dioxus::prelude::*;
304 /// fn List(list: Signal<Vec<i32>>) -> Element {
305 /// rsx! {
306 /// for index in 0..list.len() {
307 /// // We can use the `map` method to provide a view into the single item in the list that the child component will render
308 /// Item { item: list.map_mut(move |v| &v[index], move |v| &mut v[index]) }
309 /// }
310 /// }
311 /// }
312 ///
313 /// // The child component doesn't need to know that the mapped value is coming from a list
314 /// #[component]
315 /// fn Item(item: WriteSignal<i32>) -> Element {
316 /// rsx! {
317 /// button {
318 /// onclick: move |_| *item.write() += 1,
319 /// "{item}"
320 /// }
321 /// }
322 /// }
323 /// ```
324 fn map_mut<O, F, FMut>(self, f: F, f_mut: FMut) -> MappedMutSignal<O, Self, F, FMut>
325 where
326 Self: Sized,
327 O: ?Sized,
328 F: Fn(&Self::Target) -> &O,
329 FMut: Fn(&mut Self::Target) -> &mut O,
330 {
331 MappedMutSignal::new(self, f, f_mut)
332 }
333
334 /// Run a function with a mutable reference to the value. If the value has been dropped, this will panic.
335 #[track_caller]
336 fn with_mut<O>(&mut self, f: impl FnOnce(&mut Self::Target) -> O) -> O
337 where
338 Self::Target: 'static,
339 {
340 f(&mut *self.write())
341 }
342
343 /// Set the value of the signal. This will trigger an update on all subscribers.
344 #[track_caller]
345 fn set(&mut self, value: Self::Target)
346 where
347 Self::Target: Sized + 'static,
348 {
349 *self.write() = value;
350 }
351
352 /// Invert the boolean value of the signal. This will trigger an update on all subscribers.
353 #[track_caller]
354 fn toggle(&mut self)
355 where
356 Self::Target: std::ops::Not<Output = Self::Target> + Clone + 'static,
357 {
358 let inverted = !(*self.peek()).clone();
359 self.set(inverted);
360 }
361
362 /// Index into the inner value and return a reference to the result.
363 #[track_caller]
364 fn index_mut<I>(
365 &mut self,
366 index: I,
367 ) -> WritableRef<'_, Self, <Self::Target as std::ops::Index<I>>::Output>
368 where
369 Self::Target: std::ops::IndexMut<I> + 'static,
370 {
371 WriteLock::map(self.write(), |v| v.index_mut(index))
372 }
373
374 /// Takes the value out of the Signal, leaving a Default in its place.
375 #[track_caller]
376 fn take(&mut self) -> Self::Target
377 where
378 Self::Target: Default + 'static,
379 {
380 self.with_mut(std::mem::take)
381 }
382
383 /// Replace the value in the Signal, returning the old value.
384 #[track_caller]
385 fn replace(&mut self, value: Self::Target) -> Self::Target
386 where
387 Self::Target: Sized + 'static,
388 {
389 self.with_mut(|v| std::mem::replace(v, value))
390 }
391}
392
393impl<W: Writable + ?Sized> WritableExt for W {}
394
395/// An extension trait for [`Writable`] values that can be boxed into a trait object.
396pub trait WritableBoxedExt: Writable<Storage = UnsyncStorage> {
397 /// Box the writable value into a trait object. This is useful for passing around writable values without knowing their concrete type.
398 fn boxed_mut(self) -> WriteSignal<Self::Target>
399 where
400 Self: Sized + 'static,
401 {
402 WriteSignal::new(self)
403 }
404}
405
406impl<T: Writable<Storage = UnsyncStorage> + 'static> WritableBoxedExt for T {
407 fn boxed_mut(self) -> WriteSignal<Self::Target> {
408 WriteSignal::new(self)
409 }
410}
411
412/// An extension trait for [`Writable<Option<T>>`]` that provides some convenience methods.
413pub trait WritableOptionExt<T>: Writable<Target = Option<T>> {
414 /// Gets the value out of the Option, or inserts the given value if the Option is empty.
415 #[track_caller]
416 fn get_or_insert(&mut self, default: T) -> WritableRef<'_, Self, T>
417 where
418 T: 'static,
419 {
420 self.get_or_insert_with(|| default)
421 }
422
423 /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty.
424 #[track_caller]
425 fn get_or_insert_with(&mut self, default: impl FnOnce() -> T) -> WritableRef<'_, Self, T>
426 where
427 T: 'static,
428 {
429 let is_none = self.read().is_none();
430 if is_none {
431 self.with_mut(|v| *v = Some(default()));
432 WriteLock::map(self.write(), |v| v.as_mut().unwrap())
433 } else {
434 WriteLock::map(self.write(), |v| v.as_mut().unwrap())
435 }
436 }
437
438 /// Attempts to write the inner value of the Option.
439 #[track_caller]
440 fn as_mut(&mut self) -> Option<WritableRef<'_, Self, T>>
441 where
442 T: 'static,
443 {
444 WriteLock::filter_map(self.write(), |v: &mut Option<T>| v.as_mut())
445 }
446}
447
448impl<T, W> WritableOptionExt<T> for W where W: Writable<Target = Option<T>> {}
449
450/// An extension trait for [`Writable<Vec<T>>`] that provides some convenience methods.
451pub trait WritableVecExt<T>: Writable<Target = Vec<T>> {
452 /// Pushes a new value to the end of the vector.
453 #[track_caller]
454 fn push(&mut self, value: T)
455 where
456 T: 'static,
457 {
458 self.with_mut(|v| v.push(value))
459 }
460
461 /// Pops the last value from the vector.
462 #[track_caller]
463 fn pop(&mut self) -> Option<T>
464 where
465 T: 'static,
466 {
467 self.with_mut(|v| v.pop())
468 }
469
470 /// Inserts a new value at the given index.
471 #[track_caller]
472 fn insert(&mut self, index: usize, value: T)
473 where
474 T: 'static,
475 {
476 self.with_mut(|v| v.insert(index, value))
477 }
478
479 /// Removes the value at the given index.
480 #[track_caller]
481 fn remove(&mut self, index: usize) -> T
482 where
483 T: 'static,
484 {
485 self.with_mut(|v| v.remove(index))
486 }
487
488 /// Clears the vector, removing all values.
489 #[track_caller]
490 fn clear(&mut self)
491 where
492 T: 'static,
493 {
494 self.with_mut(|v| v.clear())
495 }
496
497 /// Extends the vector with the given iterator.
498 #[track_caller]
499 fn extend(&mut self, iter: impl IntoIterator<Item = T>)
500 where
501 T: 'static,
502 {
503 self.with_mut(|v| v.extend(iter))
504 }
505
506 /// Truncates the vector to the given length.
507 #[track_caller]
508 fn truncate(&mut self, len: usize)
509 where
510 T: 'static,
511 {
512 self.with_mut(|v| v.truncate(len))
513 }
514
515 /// Swaps two values in the vector.
516 #[track_caller]
517 fn swap_remove(&mut self, index: usize) -> T
518 where
519 T: 'static,
520 {
521 self.with_mut(|v| v.swap_remove(index))
522 }
523
524 /// Retains only the values that match the given predicate.
525 #[track_caller]
526 fn retain(&mut self, f: impl FnMut(&T) -> bool)
527 where
528 T: 'static,
529 {
530 self.with_mut(|v| v.retain(f))
531 }
532
533 /// Splits the vector into two at the given index.
534 #[track_caller]
535 fn split_off(&mut self, at: usize) -> Vec<T>
536 where
537 T: 'static,
538 {
539 self.with_mut(|v| v.split_off(at))
540 }
541
542 /// Try to mutably get an element from the vector.
543 #[track_caller]
544 fn get_mut(&mut self, index: usize) -> Option<WritableRef<'_, Self, T>>
545 where
546 T: 'static,
547 {
548 WriteLock::filter_map(self.write(), |v: &mut Vec<T>| v.get_mut(index))
549 }
550
551 /// Gets an iterator over the values of the vector.
552 #[track_caller]
553 fn iter_mut(&mut self) -> WritableValueIterator<'_, Self>
554 where
555 Self: Sized + Clone,
556 {
557 WritableValueIterator {
558 index: 0,
559 value: self,
560 }
561 }
562}
563
564/// An iterator over the values of a [`Writable<Vec<T>>`].
565pub struct WritableValueIterator<'a, R> {
566 index: usize,
567 value: &'a mut R,
568}
569
570impl<'a, T: 'static, R: Writable<Target = Vec<T>>> Iterator for WritableValueIterator<'a, R> {
571 type Item = WritableRef<'a, R, T>;
572
573 fn next(&mut self) -> Option<Self::Item> {
574 let index = self.index;
575 self.index += 1;
576 WriteLock::filter_map(
577 self.value.try_write_unchecked().unwrap(),
578 |v: &mut Vec<T>| v.get_mut(index),
579 )
580 .map(WriteLock::downcast_lifetime)
581 }
582}
583
584impl<W, T> WritableVecExt<T> for W where W: Writable<Target = Vec<T>> {}
585
586/// An extension trait for [`Writable<String>`] that provides some convenience methods.
587pub trait WritableStringExt: Writable<Target = String> {
588 ext_methods! {
589 /// Pushes a character to the end of the string.
590 fn push_str(&mut self, s: &str) = String::push_str;
591
592 /// Pushes a character to the end of the string.
593 fn push(&mut self, c: char) = String::push;
594
595 /// Pops a character from the end of the string.
596 fn pop(&mut self) -> Option<char> = String::pop;
597
598 /// Inserts a string at the given index.
599 fn insert_str(&mut self, idx: usize, s: &str) = String::insert_str;
600
601 /// Inserts a character at the given index.
602 fn insert(&mut self, idx: usize, c: char) = String::insert;
603
604 /// Remove a character at the given index
605 fn remove(&mut self, idx: usize) -> char = String::remove;
606
607 /// Replace a range of the string with the given string.
608 fn replace_range(&mut self, range: impl std::ops::RangeBounds<usize>, replace_with: &str) = String::replace_range;
609
610 /// Clears the string, removing all characters.
611 fn clear(&mut self) = String::clear;
612
613 /// Extends the string with the given iterator of characters.
614 fn extend(&mut self, iter: impl IntoIterator<Item = char>) = String::extend;
615
616 /// Truncates the string to the given length.
617 fn truncate(&mut self, len: usize) = String::truncate;
618
619 /// Splits the string off at the given index, returning the tail as a new string.
620 fn split_off(&mut self, at: usize) -> String = String::split_off;
621 }
622}
623
624impl<W> WritableStringExt for W where W: Writable<Target = String> {}
625
626/// An extension trait for [`Writable<HashMap<K, V, H>>`] that provides some convenience methods.
627pub trait WritableHashMapExt<K: 'static, V: 'static, H: 'static>:
628 Writable<Target = HashMap<K, V, H>>
629{
630 ext_methods! {
631 /// Clears the map, removing all key-value pairs.
632 fn clear(&mut self) = HashMap::clear;
633
634 /// Retains only the key-value pairs that match the given predicate.
635 fn retain(&mut self, f: impl FnMut(&K, &mut V) -> bool) = HashMap::retain;
636 }
637
638 /// Inserts a key-value pair into the map. If the key was already present, the old value is returned.
639 #[track_caller]
640 fn insert(&mut self, k: K, v: V) -> Option<V>
641 where
642 K: std::cmp::Eq + std::hash::Hash,
643 H: std::hash::BuildHasher,
644 {
645 self.with_mut(|map: &mut HashMap<K, V, H>| map.insert(k, v))
646 }
647
648 /// Extends the map with the key-value pairs from the given iterator.
649 #[track_caller]
650 fn extend(&mut self, iter: impl IntoIterator<Item = (K, V)>)
651 where
652 K: std::cmp::Eq + std::hash::Hash,
653 H: std::hash::BuildHasher,
654 {
655 self.with_mut(|map: &mut HashMap<K, V, H>| map.extend(iter))
656 }
657
658 /// Removes a key from the map, returning the value at the key if the key was previously in the map.
659 #[track_caller]
660 fn remove(&mut self, k: &K) -> Option<V>
661 where
662 K: std::cmp::Eq + std::hash::Hash,
663 H: std::hash::BuildHasher,
664 {
665 self.with_mut(|map: &mut HashMap<K, V, H>| map.remove(k))
666 }
667
668 /// Get a mutable reference to the value at the given key.
669 #[track_caller]
670 fn get_mut(&mut self, k: &K) -> Option<WritableRef<'_, Self, V>>
671 where
672 K: std::cmp::Eq + std::hash::Hash,
673 H: std::hash::BuildHasher,
674 {
675 WriteLock::filter_map(self.write(), |map: &mut HashMap<K, V, H>| map.get_mut(k))
676 }
677}
678
679impl<K: 'static, V: 'static, H: 'static, R> WritableHashMapExt<K, V, H> for R where
680 R: Writable<Target = HashMap<K, V, H>>
681{
682}
683
684/// An extension trait for [`Writable<HashSet<V, H>>`] that provides some convenience methods.
685pub trait WritableHashSetExt<V: 'static, H: 'static>: Writable<Target = HashSet<V, H>> {
686 ext_methods! {
687 /// Clear the hash set.
688 fn clear(&mut self) = HashSet::clear;
689
690 /// Retain only the elements specified by the predicate.
691 fn retain(&mut self, f: impl FnMut(&V) -> bool) = HashSet::retain;
692 }
693
694 /// Inserts a value into the set. Returns true if the value was not already present.
695 #[track_caller]
696 fn insert(&mut self, k: V) -> bool
697 where
698 V: std::cmp::Eq + std::hash::Hash,
699 H: std::hash::BuildHasher,
700 {
701 self.with_mut(|set| set.insert(k))
702 }
703
704 /// Extends the set with the values from the given iterator.
705 #[track_caller]
706 fn extend(&mut self, iter: impl IntoIterator<Item = V>)
707 where
708 V: std::cmp::Eq + std::hash::Hash,
709 H: std::hash::BuildHasher,
710 {
711 self.with_mut(|set| set.extend(iter))
712 }
713
714 /// Removes a value from the set. Returns true if the value was present.
715 #[track_caller]
716 fn remove(&mut self, k: &V) -> bool
717 where
718 V: std::cmp::Eq + std::hash::Hash,
719 H: std::hash::BuildHasher,
720 {
721 self.with_mut(|set| set.remove(k))
722 }
723}
724
725impl<V: 'static, H: 'static, R> WritableHashSetExt<V, H> for R where
726 R: Writable<Target = HashSet<V, H>>
727{
728}