Files
dioxus/packages/hooks/src/use_future.rs
T
Evan AlmloffandJonathan Kelley 58c41f8561 Stores: Externally tracked state and reactive collections (#4483)
* 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

* writable is child owned

* box selector

* read and write to builtins

* re-use the proc macro in the derive macro

* Switch to a extension trait based approach

* switch to extension traits in the derive macro

* transposed method

* derive enums

* Expose Store::new

* remove stores from the prelude

* More consistent naming scheme for SelectorScope methods

* document hashmap methods

* document index extension

* document option ext

* document result ext

* document slice extension

* document the vec extension

* make more methods borrow the store mutably

* Move to impls to have higher priority over the builtin write extension traits

* fix deadlock

* finish stores documentation

* Document the store macro

* cleanup docs and organization

* finish derive docs

* fix typo

* fix clippy

* fix doc tests

* fix derive macro with generics

* fix clippy

* test and fix empty structs

* clean up todomvc store example

* default to a boxed store writer to make it easier to type when providing to the context api

* fix cargo doc

* fix clippy in store example

* relax generational box lifetime bounds

* relax bounds

* relax signal bounds

* fix compilation

* read store

* relax bounds on default store impls

* fix enum derive

* Convert to readsignal and writesignal automatically

* simplify type matching logic

* add write store

* unsized stores

* shrink the path vec type

* use more concrete types

* remove opaque types

* remove copy bounds

* use lens as generic name everywhere

* more consistent borrow semantics

* use a concrete type for the signal metadata and mention references in the map docs

* improve todomvc store docs

* document store derive macro internals

* refactor macro

* improve selector node documentation

* fix clippy

* add btreemap

* fix doc tests

* more option methods

* fix doc

* fix clippy

* add a bunch more result methods

* add a bunch of bounds to the iterators

* fix clippy

* remove option from subscribers

* add store macro to generate extension traits

* fix store example

* fix deadlock

* fix docs

* fix doc tests

* fix clippy

* add stores to public api

* add store macro, and then update todomvc_store example

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
2025-08-05 11:30:21 -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) -> 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) }
}
}