Files
dioxus/packages/hooks/src/use_future.rs
T
Evan Almloff d8d11db403 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
2025-07-31 13:19:55 -07:00

199 lines
5.7 KiB
Rust

#![allow(missing_docs)]
use crate::{use_callback, use_hook_did_run, use_signal};
use dioxus_core::{use_hook, Callback, Subscribers, Task};
use dioxus_signals::*;
use std::future::Future;
use std::ops::Deref;
/// A hook that allows you to spawn a future the first time you render a component.
///
///
/// This future will **not** run on the server. To run a future on the server, you should use [`dioxus_core::spawn_isomorphic`] directly.
///
///
/// `use_future` **won't return a value**. If you want to return a value from a future, use [`crate::use_resource()`] instead.
///
/// ## Example
///
/// ```rust
/// # use dioxus::prelude::*;
/// # use std::time::Duration;
/// fn app() -> Element {
/// let mut count = use_signal(|| 0);
/// let mut running = use_signal(|| true);
/// // `use_future` will spawn an infinitely running future that can be started and stopped
/// use_future(move || async move {
/// loop {
/// if running() {
/// count += 1;
/// }
/// tokio::time::sleep(Duration::from_millis(400)).await;
/// }
/// });
/// rsx! {
/// div {
/// h1 { "Current count: {count}" }
/// button { onclick: move |_| running.toggle(), "Start/Stop the count"}
/// button { onclick: move |_| count.set(0), "Reset the count" }
/// }
/// }
/// }
/// ```
#[doc = include_str!("../docs/rules_of_hooks.md")]
#[doc = include_str!("../docs/moving_state_around.md")]
#[doc(alias = "use_async")]
pub fn use_future<F>(mut future: impl FnMut() -> F + 'static) -> UseFuture
where
F: Future + 'static,
{
let mut state = use_signal(|| UseFutureState::Pending);
let callback = use_callback(move |_| {
let fut = future();
dioxus_core::spawn(async move {
state.set(UseFutureState::Pending);
fut.await;
state.set(UseFutureState::Ready);
})
});
// Create the task inside a CopyValue so we can reset it in-place later
let task = use_hook(|| CopyValue::new(callback(())));
// Early returns in dioxus have consequences for use_memo, use_resource, and use_future, etc
// We *don't* want futures to be running if the component early returns. It's a rather weird behavior to have
// use_memo running in the background even if the component isn't hitting those hooks anymore.
//
// React solves this by simply not having early returns interleave with hooks.
// However, since dioxus allows early returns (since we use them for suspense), we need to solve this problem
use_hook_did_run(move |did_run| match did_run {
true => task.peek().resume(),
false => task.peek().pause(),
});
UseFuture {
task,
state,
callback,
}
}
#[derive(Clone, Copy, PartialEq)]
pub struct UseFuture {
task: CopyValue<Task>,
state: Signal<UseFutureState>,
callback: Callback<(), Task>,
}
/// A signal that represents the state of a future
// we might add more states (panicked, etc)
#[derive(Clone, Copy, PartialEq, Hash, Eq, Debug)]
pub enum UseFutureState {
/// The future is still running
Pending,
/// The future has been forcefully stopped
Stopped,
/// The future has been paused, tempoarily
Paused,
/// The future has completed
Ready,
}
impl UseFuture {
/// Restart the future with new dependencies.
///
/// Will not cancel the previous future, but will ignore any values that it
/// generates.
pub fn restart(&mut self) {
self.task.write().cancel();
let new_task = self.callback.call(());
self.task.set(new_task);
}
/// Forcefully cancel a future
pub fn cancel(&mut self) {
self.state.set(UseFutureState::Stopped);
self.task.write().cancel();
}
/// Pause the future
pub fn pause(&mut self) {
self.state.set(UseFutureState::Paused);
self.task.write().pause();
}
/// Resume the future
pub fn resume(&mut self) {
if self.finished() {
return;
}
self.state.set(UseFutureState::Pending);
self.task.write().resume();
}
/// Get a handle to the inner task backing this future
/// Modify the task through this handle will cause inconsistent state
pub fn task(&self) -> Task {
self.task.cloned()
}
/// Is the future currently finished running?
///
/// Reading this does not subscribe to the future's state
pub fn finished(&self) -> bool {
matches!(
*self.state.peek(),
UseFutureState::Ready | UseFutureState::Stopped
)
}
/// Get the current state of the future.
pub fn state(&self) -> ReadSignal<UseFutureState> {
self.state.into()
}
}
impl From<UseFuture> for ReadSignal<UseFutureState> {
fn from(val: UseFuture) -> Self {
val.state.into()
}
}
impl Readable for UseFuture {
type Target = UseFutureState;
type Storage = UnsyncStorage;
#[track_caller]
fn try_read_unchecked(
&self,
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
self.state.try_read_unchecked()
}
#[track_caller]
fn try_peek_unchecked(
&self,
) -> 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
///
/// Currently only limited to copy types, though could probably specialize for string/arc/rc
impl Deref for UseFuture {
type Target = dyn Fn() -> UseFutureState;
fn deref(&self) -> &Self::Target {
unsafe { ReadableExt::deref_impl(self) }
}
}