dioxus_hooks/
use_action.rs

1use crate::{use_callback, use_signal};
2use dioxus_core::{use_hook, Callback, CapturedError, Result, Task};
3use dioxus_signals::{ReadSignal, ReadableBoxExt, ReadableExt, Signal, WritableExt};
4use futures_channel::oneshot::Receiver;
5use futures_util::{future::Shared, FutureExt};
6use std::{marker::PhantomData, prelude::rust_2024::Future};
7
8pub fn use_action<E, C, M>(mut user_fn: C) -> Action<C::Input, C::Output>
9where
10    E: Into<CapturedError> + 'static,
11    C: ActionCallback<M, E>,
12    M: 'static,
13    C::Input: 'static,
14    C::Output: 'static,
15    C: 'static,
16{
17    let mut value = use_signal(|| None as Option<C::Output>);
18    let mut error = use_signal(|| None as Option<CapturedError>);
19    let mut task = use_signal(|| None as Option<Task>);
20    let mut state = use_signal(|| ActionState::Unset);
21    let callback = use_callback(move |input: C::Input| {
22        // Cancel any existing task
23        if let Some(task) = task.take() {
24            task.cancel();
25        }
26
27        let (tx, rx) = futures_channel::oneshot::channel();
28        let rx = rx.shared();
29
30        // Spawn a new task, and *then* fire off the async
31        let result = user_fn.call(input);
32        let new_task = dioxus_core::spawn(async move {
33            // Set the state to pending
34            state.set(ActionState::Pending);
35
36            // Create a new task
37            let result = result.await;
38            match result {
39                Ok(res) => {
40                    error.set(None);
41                    value.set(Some(res));
42                    state.set(ActionState::Ready);
43                }
44                Err(err) => {
45                    error.set(Some(err.into()));
46                    value.set(None);
47                    state.set(ActionState::Errored);
48                }
49            }
50
51            tx.send(()).ok();
52        });
53
54        task.set(Some(new_task));
55
56        rx
57    });
58
59    // Create a reader that maps the Option<T> to T, unwrapping the Option
60    // This should only be handed out if we know the value is Some. We never set the value back to None, only modify the state of the action
61    let reader = use_hook(|| value.boxed().map(|v| v.as_ref().unwrap()).boxed());
62
63    Action {
64        value,
65        error,
66        task,
67        callback,
68        reader,
69        _phantom: PhantomData,
70        state,
71    }
72}
73
74pub struct Action<I, T: 'static> {
75    reader: ReadSignal<T>,
76    error: Signal<Option<CapturedError>>,
77    value: Signal<Option<T>>,
78    task: Signal<Option<Task>>,
79    callback: Callback<I, Shared<Receiver<()>>>,
80    state: Signal<ActionState>,
81    _phantom: PhantomData<*const I>,
82}
83
84impl<I: 'static, O: 'static> Action<I, O> {
85    pub fn value(&self) -> Option<ReadSignal<O>> {
86        if self.value.read().is_none() {
87            return None;
88        }
89
90        Some(self.reader)
91    }
92
93    pub fn result(&self) -> Option<Result<ReadSignal<O>, CapturedError>> {
94        if !matches!(
95            *self.state.read(),
96            ActionState::Ready | ActionState::Errored
97        ) {
98            return None;
99        }
100
101        if let Some(err) = self.error.cloned() {
102            return Some(Err(err));
103        }
104
105        if self.value.read().is_none() {
106            return None;
107        }
108
109        Some(Ok(self.reader))
110    }
111
112    pub fn is_pending(&self) -> bool {
113        *self.state.read() == ActionState::Pending
114    }
115
116    /// Clear the current value and error, setting the state to Reset
117    pub fn reset(&mut self) {
118        self.state.set(ActionState::Reset);
119        if let Some(t) = self.task.take() {
120            t.cancel()
121        }
122    }
123
124    pub fn cancel(&mut self) {
125        if let Some(t) = self.task.take() {
126            t.cancel()
127        }
128        self.state.set(ActionState::Reset);
129    }
130}
131
132impl<I, T> std::fmt::Debug for Action<I, T>
133where
134    T: std::fmt::Debug + 'static,
135{
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        if f.alternate() {
138            f.debug_struct("Action")
139                .field("state", &self.state.read())
140                .field("value", &self.value.read())
141                .field("error", &self.error.read())
142                .finish()
143        } else {
144            std::fmt::Debug::fmt(&self.value.read().as_ref(), f)
145        }
146    }
147}
148pub struct Dispatching<I> {
149    _phantom: PhantomData<*const I>,
150    receiver: Shared<Receiver<()>>,
151}
152
153impl<T> Dispatching<T> {
154    pub(crate) fn new(receiver: Shared<Receiver<()>>) -> Self {
155        Self {
156            _phantom: PhantomData,
157            receiver,
158        }
159    }
160}
161
162impl<T> std::future::Future for Dispatching<T> {
163    type Output = ();
164
165    fn poll(
166        mut self: std::pin::Pin<&mut Self>,
167        _cx: &mut std::task::Context<'_>,
168    ) -> std::task::Poll<Self::Output> {
169        match self.receiver.poll_unpin(_cx) {
170            std::task::Poll::Ready(_) => std::task::Poll::Ready(()),
171            std::task::Poll::Pending => std::task::Poll::Pending,
172        }
173    }
174}
175
176impl<I, T> Copy for Action<I, T> {}
177impl<I, T> Clone for Action<I, T> {
178    fn clone(&self) -> Self {
179        *self
180    }
181}
182
183/// The state of an action
184///
185/// We can never reset the state to Unset, only to Reset, otherwise the value reader would panic.
186#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
187enum ActionState {
188    Unset,
189    Pending,
190    Ready,
191    Errored,
192    Reset,
193}
194
195pub trait ActionCallback<M, E> {
196    type Input;
197    type Output;
198    fn call(
199        &mut self,
200        input: Self::Input,
201    ) -> impl Future<Output = Result<Self::Output, E>> + 'static;
202}
203
204impl<F, O, G, E> ActionCallback<(O,), E> for F
205where
206    F: FnMut() -> G,
207    G: Future<Output = Result<O, E>> + 'static,
208{
209    type Input = ();
210    type Output = O;
211    fn call(
212        &mut self,
213        _input: Self::Input,
214    ) -> impl Future<Output = Result<Self::Output, E>> + 'static {
215        (self)()
216    }
217}
218
219impl<F, O, A, G, E> ActionCallback<(A, O), E> for F
220where
221    F: FnMut(A) -> G,
222    G: Future<Output = Result<O, E>> + 'static,
223{
224    type Input = (A,);
225    type Output = O;
226    fn call(
227        &mut self,
228        input: Self::Input,
229    ) -> impl Future<Output = Result<Self::Output, E>> + 'static {
230        let (a,) = input;
231        (self)(a)
232    }
233}
234
235impl<O, A, B, F, G, E> ActionCallback<(A, B, O), E> for F
236where
237    F: FnMut(A, B) -> G,
238    G: Future<Output = Result<O, E>> + 'static,
239{
240    type Input = (A, B);
241    type Output = O;
242    fn call(
243        &mut self,
244        input: Self::Input,
245    ) -> impl Future<Output = Result<Self::Output, E>> + 'static {
246        let (a, b) = input;
247        (self)(a, b)
248    }
249}
250
251impl<O, A, B, C, F, G, E> ActionCallback<(A, B, C, O), E> for F
252where
253    F: FnMut(A, B, C) -> G,
254    G: Future<Output = Result<O, E>> + 'static,
255{
256    type Input = (A, B, C);
257    type Output = O;
258    fn call(
259        &mut self,
260        input: Self::Input,
261    ) -> impl Future<Output = Result<Self::Output, E>> + 'static {
262        let (a, b, c) = input;
263        (self)(a, b, c)
264    }
265}
266
267impl<O> Action<(), O> {
268    pub fn call(&mut self) -> Dispatching<()> {
269        Dispatching::new((self.callback).call(()))
270    }
271}
272
273impl<A: 'static, O> Action<(A,), O> {
274    pub fn call(&mut self, _a: A) -> Dispatching<()> {
275        Dispatching::new((self.callback).call((_a,)))
276    }
277}
278
279impl<A: 'static, B: 'static, O> Action<(A, B), O> {
280    pub fn call(&mut self, _a: A, _b: B) -> Dispatching<()> {
281        Dispatching::new((self.callback).call((_a, _b)))
282    }
283}
284impl<A: 'static, B: 'static, C: 'static, O> Action<(A, B, C), O> {
285    pub fn call(&mut self, _a: A, _b: B, _c: C) -> Dispatching<()> {
286        Dispatching::new((self.callback).call((_a, _b, _c)))
287    }
288}