Boxed and mapped signals preparing for stores (#4413)

This PR contains just the breaking changes required for the upcoming store implementation. It adds two new primitives:

Mutable mapped signals (without a different subscriber list). This lets you scope a writable value and pass it to a child component. It requires two closures to map the immutable and mutable references to the inner signal. With stores, these closures will be derived
Boxed readable and writable values (ReadSignal, and WriteSignal). Any readable or writable types can be converted to a read signal or write signal with the boxed method or built in types are automatically converted into the boxed variants when passing into props


* map signals mutably

* boxed signals

* remove mutable borrow type from boxed writable

* improve map mut example

* restore boxed_mut helper

* make boxed readable/writable types copy

* Move away from the ReadOnlySignal alias in examples

* automatically convert Write

* Fix some tests

* ReadSignal and WriteSignal

* move read only signal alias

* make maps copy

* store prototype

* implement derive macro

* Handle generics in the derive macro

* Make Selector boxable

* derive boxed conversion

* Get rid of store type

* Remove hardcoded type from derive macro

* selector -> store

* Refactor store implementation

* more vec methods

* todo mvc store example

* Implement Storable for HashMap

* fix write

* restore sorting logic in todomvc

* derive readable and writable

* improve todomvc store example

* Result store

* Option store

* collect dead subscribers

* fix clippy and formatting

* recognize some common foreign types automatically

* fix foreign stores in props

* slice and array selectors

* clean up store example

* remove stores

* writable is child owned

* fix typo

* fix clippy

* fix doc tests
This commit is contained in:
Evan Almloff authored and GitHub committed 2025-07-31 13:19:55 -07:00
1 parent 6919f72abc
commit d8d11db403
64 files changed
+1307 -726

No files matched your search

+8 -4
View File
@@ -1,6 +1,6 @@
#![allow(missing_docs)]
use crate::{use_callback, use_hook_did_run, use_signal};
use dioxus_core::{use_hook, Callback, Task};
use dioxus_core::{use_hook, Callback, Subscribers, Task};
use dioxus_signals::*;
use std::future::Future;
use std::ops::Deref;
@@ -152,12 +152,12 @@ impl UseFuture {
}
/// Get the current state of the future.
pub fn state(&self) -> ReadOnlySignal<UseFutureState> {
pub fn state(&self) -> ReadSignal<UseFutureState> {
self.state.into()
}
}
impl From<UseFuture> for ReadOnlySignal<UseFutureState> {
impl From<UseFuture> for ReadSignal<UseFutureState> {
fn from(val: UseFuture) -> Self {
val.state.into()
}
@@ -180,6 +180,10 @@ impl Readable for UseFuture {
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
self.state.try_peek_unchecked()
}
fn subscribers(&self) -> Option<Subscribers> {
self.state.subscribers()
}
}
/// Allow calling a signal with signal() syntax
@@ -189,6 +193,6 @@ impl Deref for UseFuture {
type Target = dyn Fn() -> UseFutureState;
fn deref(&self) -> &Self::Target {
unsafe { Readable::deref_impl(self) }
unsafe { ReadableExt::deref_impl(self) }
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
use dioxus_core::{use_after_render, use_before_render, use_hook};
use dioxus_signals::{CopyValue, Writable};
use dioxus_signals::{CopyValue, WritableExt};
/// A hook that uses before/after lifecycle hooks to determine if the hook was run
#[doc = include_str!("../docs/rules_of_hooks.md")]
+1 -1
View File
@@ -1,4 +1,4 @@
use dioxus_signals::{Readable, Writable};
use dioxus_signals::{ReadableExt, WritableExt};
use crate::use_signal;
+12 -8
View File
@@ -4,7 +4,7 @@ use crate::{use_callback, use_signal};
use dioxus_core::{
spawn, use_hook, Callback, IntoAttributeValue, IntoDynNode, ReactiveContext, RenderError,
SuspendedFuture, Task,
Subscribers, SuspendedFuture, Task,
};
use dioxus_signals::*;
use futures_util::{future, pin_mut, FutureExt, StreamExt};
@@ -349,7 +349,7 @@ impl<T> Resource<T> {
)
}
/// Get the current state of the resource's future. This method returns a [`ReadOnlySignal`] which can be read to get the current state of the resource or passed to other hooks and components.
/// Get the current state of the resource's future. This method returns a [`ReadSignal`] which can be read to get the current state of the resource or passed to other hooks and components.
///
/// ## Example
/// ```rust, no_run
@@ -379,11 +379,11 @@ impl<T> Resource<T> {
/// }
/// }
/// ```
pub fn state(&self) -> ReadOnlySignal<UseResourceState> {
pub fn state(&self) -> ReadSignal<UseResourceState> {
self.state.into()
}
/// Get the current value of the resource's future. This method returns a [`ReadOnlySignal`] which can be read to get the current value of the resource or passed to other hooks and components.
/// Get the current value of the resource's future. This method returns a [`ReadSignal`] which can be read to get the current value of the resource or passed to other hooks and components.
///
/// ## Example
///
@@ -410,12 +410,12 @@ impl<T> Resource<T> {
/// }
/// }
/// ```
pub fn value(&self) -> ReadOnlySignal<Option<T>> {
pub fn value(&self) -> ReadSignal<Option<T>> {
self.value.into()
}
/// Suspend the resource's future and only continue rendering when the future is ready
pub fn suspend(&self) -> std::result::Result<MappedSignal<T>, RenderError> {
pub fn suspend(&self) -> std::result::Result<MappedSignal<T, Signal<Option<T>>>, RenderError> {
match self.state.cloned() {
UseResourceState::Stopped | UseResourceState::Paused | UseResourceState::Pending => {
let task = self.task();
@@ -430,7 +430,7 @@ impl<T> Resource<T> {
}
}
impl<T> From<Resource<T>> for ReadOnlySignal<Option<T>> {
impl<T> From<Resource<T>> for ReadSignal<Option<T>> {
fn from(val: Resource<T>) -> Self {
val.value.into()
}
@@ -453,6 +453,10 @@ impl<T> Readable for Resource<T> {
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
self.value.try_peek_unchecked()
}
fn subscribers(&self) -> Option<Subscribers> {
self.value.subscribers()
}
}
impl<T> IntoAttributeValue for Resource<T>
@@ -480,6 +484,6 @@ impl<T: Clone> Deref for Resource<T> {
type Target = dyn Fn() -> Option<T>;
fn deref(&self) -> &Self::Target {
unsafe { Readable::deref_impl(self) }
unsafe { ReadableExt::deref_impl(self) }
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
use dioxus_core::use_hook;
use dioxus_signals::{ReadOnlySignal, SetCompare};
use dioxus_signals::{ReadSignal, SetCompare};
use std::hash::Hash;
/// Creates a new SetCompare which efficiently tracks when a value changes to check if it is equal to a set of values.
@@ -51,6 +51,6 @@ pub fn use_set_compare<R: Eq + Hash>(f: impl FnMut() -> R + 'static) -> SetCompa
pub fn use_set_compare_equal<R: Eq + Hash>(
value: R,
mut compare: SetCompare<R>,
) -> ReadOnlySignal<bool> {
) -> ReadSignal<bool> {
use_hook(move || compare.equal(value))
}
+3 -3
View File
@@ -2,12 +2,12 @@ use std::cmp::Ordering;
use std::ops::DerefMut;
use crate::use_memo;
use dioxus_signals::{ReadOnlySignal, Signal};
use dioxus_signals::{ReadSignal, Signal};
pub fn use_sorted<V: 'static, T: PartialEq>(
collection: impl FnMut() -> Signal<V>,
) -> ReadOnlySignal<Vec<T>>
// pub fn use_sorted<S, I, T>(iterable: impl FnMut() -> Signal<V>) -> ReadOnlySignal<T>
) -> ReadSignal<Vec<T>>
// pub fn use_sorted<S, I, T>(iterable: impl FnMut() -> Signal<V>) -> ReadSignal<T>
// where
// S: Into<MaybeSignal<I>>,
// T: Ord,