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>> {}