dioxus_hooks/use_future.rs
1#![allow(missing_docs)]
2use crate::{use_callback, use_hook_did_run, use_signal};
3use dioxus_core::prelude::*;
4use dioxus_signals::*;
5use std::future::Future;
6use std::ops::Deref;
7
8/// A hook that allows you to spawn a future the first time you render a component.
9///
10///
11/// This future will **not** run on the server. To run a future on the server, you should use [`spawn_isomorphic`] directly.
12///
13///
14/// `use_future` **won't return a value**. If you want to return a value from a future, use [`crate::use_resource()`] instead.
15///
16/// ## Example
17///
18/// ```rust
19/// # use dioxus::prelude::*;
20/// # use std::time::Duration;
21/// fn app() -> Element {
22/// let mut count = use_signal(|| 0);
23/// let mut running = use_signal(|| true);
24/// // `use_future` will spawn an infinitely running future that can be started and stopped
25/// use_future(move || async move {
26/// loop {
27/// if running() {
28/// count += 1;
29/// }
30/// tokio::time::sleep(Duration::from_millis(400)).await;
31/// }
32/// });
33/// rsx! {
34/// div {
35/// h1 { "Current count: {count}" }
36/// button { onclick: move |_| running.toggle(), "Start/Stop the count"}
37/// button { onclick: move |_| count.set(0), "Reset the count" }
38/// }
39/// }
40/// }
41/// ```
42#[doc = include_str!("../docs/rules_of_hooks.md")]
43#[doc = include_str!("../docs/moving_state_around.md")]
44#[doc(alias = "use_async")]
45pub fn use_future<F>(mut future: impl FnMut() -> F + 'static) -> UseFuture
46where
47 F: Future + 'static,
48{
49 let mut state = use_signal(|| UseFutureState::Pending);
50
51 let callback = use_callback(move |_| {
52 let fut = future();
53 spawn(async move {
54 state.set(UseFutureState::Pending);
55 fut.await;
56 state.set(UseFutureState::Ready);
57 })
58 });
59
60 // Create the task inside a CopyValue so we can reset it in-place later
61 let task = use_hook(|| CopyValue::new(callback(())));
62
63 // Early returns in dioxus have consequences for use_memo, use_resource, and use_future, etc
64 // We *don't* want futures to be running if the component early returns. It's a rather weird behavior to have
65 // use_memo running in the background even if the component isn't hitting those hooks anymore.
66 //
67 // React solves this by simply not having early returns interleave with hooks.
68 // However, since dioxus allows early returns (since we use them for suspense), we need to solve this problem
69 use_hook_did_run(move |did_run| match did_run {
70 true => task.peek().resume(),
71 false => task.peek().pause(),
72 });
73
74 UseFuture {
75 task,
76 state,
77 callback,
78 }
79}
80
81#[derive(Clone, Copy, PartialEq)]
82pub struct UseFuture {
83 task: CopyValue<Task>,
84 state: Signal<UseFutureState>,
85 callback: Callback<(), Task>,
86}
87
88/// A signal that represents the state of a future
89// we might add more states (panicked, etc)
90#[derive(Clone, Copy, PartialEq, Hash, Eq, Debug)]
91pub enum UseFutureState {
92 /// The future is still running
93 Pending,
94
95 /// The future has been forcefully stopped
96 Stopped,
97
98 /// The future has been paused, tempoarily
99 Paused,
100
101 /// The future has completed
102 Ready,
103}
104
105impl UseFuture {
106 /// Restart the future with new dependencies.
107 ///
108 /// Will not cancel the previous future, but will ignore any values that it
109 /// generates.
110 pub fn restart(&mut self) {
111 self.task.write().cancel();
112 let new_task = self.callback.call(());
113 self.task.set(new_task);
114 }
115
116 /// Forcefully cancel a future
117 pub fn cancel(&mut self) {
118 self.state.set(UseFutureState::Stopped);
119 self.task.write().cancel();
120 }
121
122 /// Pause the future
123 pub fn pause(&mut self) {
124 self.state.set(UseFutureState::Paused);
125 self.task.write().pause();
126 }
127
128 /// Resume the future
129 pub fn resume(&mut self) {
130 if self.finished() {
131 return;
132 }
133
134 self.state.set(UseFutureState::Pending);
135 self.task.write().resume();
136 }
137
138 /// Get a handle to the inner task backing this future
139 /// Modify the task through this handle will cause inconsistent state
140 pub fn task(&self) -> Task {
141 self.task.cloned()
142 }
143
144 /// Is the future currently finished running?
145 ///
146 /// Reading this does not subscribe to the future's state
147 pub fn finished(&self) -> bool {
148 matches!(
149 *self.state.peek(),
150 UseFutureState::Ready | UseFutureState::Stopped
151 )
152 }
153
154 /// Get the current state of the future.
155 pub fn state(&self) -> ReadOnlySignal<UseFutureState> {
156 self.state.into()
157 }
158}
159
160impl From<UseFuture> for ReadOnlySignal<UseFutureState> {
161 fn from(val: UseFuture) -> Self {
162 val.state.into()
163 }
164}
165
166impl Readable for UseFuture {
167 type Target = UseFutureState;
168 type Storage = UnsyncStorage;
169
170 #[track_caller]
171 fn try_read_unchecked(
172 &self,
173 ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
174 self.state.try_read_unchecked()
175 }
176
177 #[track_caller]
178 fn try_peek_unchecked(
179 &self,
180 ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError> {
181 self.state.try_peek_unchecked()
182 }
183}
184
185/// Allow calling a signal with signal() syntax
186///
187/// Currently only limited to copy types, though could probably specialize for string/arc/rc
188impl Deref for UseFuture {
189 type Target = dyn Fn() -> UseFutureState;
190
191 fn deref(&self) -> &Self::Target {
192 unsafe { Readable::deref_impl(self) }
193 }
194}