Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions packages/hooks/docs/derived_signal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Creates a new Signal that is derived from other state. The derived signal will automatically update whenever any of the reactive values it reads on are written to. Note the signal is not memorized and the update is not immediate, the signal will be set to the value after the next async tick. Derived signals are useful for initializing values from props.

```rust
# use dioxus::prelude::*;
#
# fn App() -> Element {
# rsx! {
# Router::<Route> {}
# }
# }
#[derive(Routable, Clone)]
enum Route {
// When you first navigate to this route, initial_count will be used to set the value of
// the count signal
#[route("/:initial_count")]
Counter { initial_count: u32 },
}

#[component]
fn Counter(initial_count: ReadSignal<u32>) -> Element {
// The count will reset to the value of the prop whenever the prop changes
let mut count = use_derived_signal(move || initial_count());

rsx! {
button {
onclick: move |_| count += 1,
"{count}"
}
Link {
// Navigating to this link will change the initial_count prop to 10. Note, this
// only updates the props, the component is not remounted
to: Route::Counter { initial_count: 10 },
"Go to initial count 10"
}
}
}
```
3 changes: 3 additions & 0 deletions packages/hooks/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,6 @@ pub use use_action::*;

mod use_waker;
pub use use_waker::*;

mod use_derived_signal;
pub use use_derived_signal::*;
14 changes: 14 additions & 0 deletions packages/hooks/src/use_derived_signal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use crate::use_callback;
use dioxus_core::use_hook;
use dioxus_signals::Signal;

#[doc = include_str!("../docs/derived_signal.md")]
#[doc = include_str!("../docs/rules_of_hooks.md")]
#[doc = include_str!("../docs/moving_state_around.md")]
#[track_caller]
pub fn use_derived_signal<R: 'static>(mut f: impl FnMut() -> R + 'static) -> Signal<R> {
let callback = use_callback(move |_| f());
let caller = std::panic::Location::caller();
#[allow(clippy::redundant_closure)]
use_hook(|| Signal::derived_signal_with_location(move || callback(()), caller))
}
32 changes: 32 additions & 0 deletions packages/signals/src/derived_signal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use dioxus_core::{current_scope_id, spawn_isomorphic, ReactiveContext};
use futures_util::StreamExt;

use crate::{Signal, WritableExt};

pub(crate) fn derived_signal<T: 'static>(
mut init: impl FnMut() -> T + 'static,
location: &'static std::panic::Location<'static>,
) -> Signal<T> {
let (tx, mut rx) = futures_channel::mpsc::unbounded();

let rc = ReactiveContext::new_with_callback(
move || _ = tx.unbounded_send(()),
current_scope_id(),
location,
);

// Create a new signal in that context, wiring up its dependencies and subscribers
let mut recompute = move || rc.reset_and_run_in(&mut init);
let value = recompute();
let mut state: Signal<T> = Signal::new_with_caller(value, location);

spawn_isomorphic(async move {
while rx.next().await.is_some() {
// Remove any pending updates
while rx.try_next().is_ok() {}
state.set(recompute());
}
});

state
}
2 changes: 2 additions & 0 deletions packages/signals/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,5 @@ pub mod warnings;

mod boxed;
pub use boxed::*;

mod derived_signal;
18 changes: 2 additions & 16 deletions packages/signals/src/memo.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{read::Readable, write_impls, ReadableRef, Signal};
use crate::CopyValue;
use crate::{read::Readable, ReadableRef, Signal};
use crate::{read_impls, GlobalMemo, ReadableExt, WritableExt};
use crate::{CopyValue, Writable};
use std::{
cell::RefCell,
ops::Deref,
Expand Down Expand Up @@ -214,19 +214,6 @@ where
}
}

impl<T: 'static + PartialEq> Writable for Memo<T> {
type WriteMetadata = <Signal<T> as Writable>::WriteMetadata;

fn try_write_unchecked(
&self,
) -> Result<crate::WritableRef<'static, Self>, generational_box::BorrowMutError>
where
Self::Target: 'static,
{
self.inner.try_write_unchecked()
}
}

impl<T> IntoAttributeValue for Memo<T>
where
T: Clone + IntoAttributeValue + PartialEq + 'static,
Expand Down Expand Up @@ -263,7 +250,6 @@ where
}

read_impls!(Memo<T> where T: PartialEq);
write_impls!(Memo<T> where T: PartialEq);

impl<T> Clone for Memo<T> {
fn clone(&self) -> Self {
Expand Down
29 changes: 23 additions & 6 deletions packages/signals/src/signal.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
default_impl, fmt_impls, read::*, write::*, write_impls, CopyValue, Global, GlobalMemo,
GlobalSignal, Memo, ReadableRef, WritableRef,
default_impl, derived_signal::derived_signal, fmt_impls, read::*, write::*, write_impls,
CopyValue, Global, GlobalMemo, GlobalSignal, Memo, ReadableRef, WritableRef,
};
use dioxus_core::{IntoAttributeValue, IntoDynNode, ReactiveContext, ScopeId, Subscribers};
use generational_box::{BorrowResult, Storage, SyncStorage, UnsyncStorage};
Expand Down Expand Up @@ -120,17 +120,17 @@ impl<T: PartialEq + 'static> Signal<T> {
GlobalMemo::new(constructor)
}

/// Creates a new unsync Selector. The selector will be run immediately and whenever any signal it reads changes.
/// Creates a new unsync Memo. The memo will be run immediately and whenever any signal it reads changes.
///
/// Selectors can be used to efficiently compute derived data from signals.
/// Memos can be used to efficiently compute derived data from signals.
#[track_caller]
pub fn memo(f: impl FnMut() -> T + 'static) -> Memo<T> {
Memo::new(f)
}

/// Creates a new unsync Selector with an explicit location. The selector will be run immediately and whenever any signal it reads changes.
/// Creates a new unsync Memo with an explicit location. The memo will be run immediately and whenever any signal it reads changes.
///
/// Selectors can be used to efficiently compute derived data from signals.
/// Memos can be used to efficiently compute derived data from signals.
pub fn memo_with_location(
f: impl FnMut() -> T + 'static,
location: &'static std::panic::Location<'static>,
Expand All @@ -139,6 +139,23 @@ impl<T: PartialEq + 'static> Signal<T> {
}
}

impl<T: 'static> Signal<T> {
/// Creates a new derived Signal. The signal will contain the result of the provided function and will automatically update after the
/// next async tick whenever any signal read during the function changes.
pub fn derived_signal(f: impl FnMut() -> T + 'static) -> Self {
derived_signal(f, std::panic::Location::caller())
}

/// Creates a new derived Signal with an explicit location. The signal will contain the result of the provided function and will automatically update after the
/// next async tick whenever any signal read during the function changes.
pub fn derived_signal_with_location(
f: impl FnMut() -> T + 'static,
location: &'static std::panic::Location<'static>,
) -> Self {
derived_signal(f, location)
}
}

impl<T, S: Storage<SignalData<T>>> Signal<T, S> {
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
#[track_caller]
Expand Down
Loading