dioxus_signals/
write.rs

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