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, pin::Pin, prelude::rust_2024::Future, task::Poll};
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<Result<ReadSignal<O>, CapturedError>> {
86        if !matches!(
87            *self.state.read(),
88            ActionState::Ready | ActionState::Errored
89        ) {
90            return None;
91        }
92
93        if let Some(err) = self.error.cloned() {
94            return Some(Err(err));
95        }
96
97        if self.value.read().is_none() {
98            return None;
99        }
100
101        Some(Ok(self.reader))
102    }
103
104    pub fn pending(&self) -> bool {
105        *self.state.read() == ActionState::Pending
106    }
107
108    /// Clear the current value and error, setting the state to Reset
109    pub fn reset(&mut self) {
110        self.state.set(ActionState::Reset);
111        if let Some(t) = self.task.take() {
112            t.cancel()
113        }
114    }
115
116    pub fn cancel(&mut self) {
117        if let Some(t) = self.task.take() {
118            t.cancel()
119        }
120        self.state.set(ActionState::Reset);
121    }
122}
123
124impl<I, T> std::fmt::Debug for Action<I, T>
125where
126    T: std::fmt::Debug + 'static,
127{
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        if f.alternate() {
130            f.debug_struct("Action")
131                .field("state", &self.state.read())
132                .field("value", &self.value.read())
133                .field("error", &self.error.read())
134                .finish()
135        } else {
136            std::fmt::Debug::fmt(&self.value.read().as_ref(), f)
137        }
138    }
139}
140pub struct Dispatching<I> {
141    _phantom: PhantomData<*const I>,
142    receiver: Shared<Receiver<()>>,
143}
144
145impl<T> Dispatching<T> {
146    pub(crate) fn new(receiver: Shared<Receiver<()>>) -> Self {
147        Self {
148            _phantom: PhantomData,
149            receiver,
150        }
151    }
152}
153
154impl<T> std::future::Future for Dispatching<T> {
155    type Output = ();
156
157    fn poll(mut self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
158        match self.receiver.poll_unpin(_cx) {
159            Poll::Ready(_) => Poll::Ready(()),
160            Poll::Pending => Poll::Pending,
161        }
162    }
163}
164
165impl<I, T> Copy for Action<I, T> {}
166impl<I, T> Clone for Action<I, T> {
167    fn clone(&self) -> Self {
168        *self
169    }
170}
171
172/// The state of an action
173///
174/// We can never reset the state to Unset, only to Reset, otherwise the value reader would panic.
175#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
176enum ActionState {
177    Unset,
178    Pending,
179    Ready,
180    Errored,
181    Reset,
182}
183
184pub trait ActionCallback<M, E> {
185    type Input;
186    type Output;
187    fn call(
188        &mut self,
189        input: Self::Input,
190    ) -> impl Future<Output = Result<Self::Output, E>> + 'static;
191}
192
193impl<F, O, G, E> ActionCallback<(O,), E> for F
194where
195    F: FnMut() -> G,
196    G: Future<Output = Result<O, E>> + 'static,
197{
198    type Input = ();
199    type Output = O;
200    fn call(
201        &mut self,
202        _input: Self::Input,
203    ) -> impl Future<Output = Result<Self::Output, E>> + 'static {
204        (self)()
205    }
206}
207
208impl<F, O, A, G, E> ActionCallback<(A, O), E> for F
209where
210    F: FnMut(A) -> G,
211    G: Future<Output = Result<O, E>> + 'static,
212{
213    type Input = (A,);
214    type Output = O;
215    fn call(
216        &mut self,
217        input: Self::Input,
218    ) -> impl Future<Output = Result<Self::Output, E>> + 'static {
219        let (a,) = input;
220        (self)(a)
221    }
222}
223
224impl<O, A, B, F, G, E> ActionCallback<(A, B, O), E> for F
225where
226    F: FnMut(A, B) -> G,
227    G: Future<Output = Result<O, E>> + 'static,
228{
229    type Input = (A, B);
230    type Output = O;
231    fn call(
232        &mut self,
233        input: Self::Input,
234    ) -> impl Future<Output = Result<Self::Output, E>> + 'static {
235        let (a, b) = input;
236        (self)(a, b)
237    }
238}
239
240impl<O, A, B, C, F, G, E> ActionCallback<(A, B, C, O), E> for F
241where
242    F: FnMut(A, B, C) -> G,
243    G: Future<Output = Result<O, E>> + 'static,
244{
245    type Input = (A, B, C);
246    type Output = O;
247    fn call(
248        &mut self,
249        input: Self::Input,
250    ) -> impl Future<Output = Result<Self::Output, E>> + 'static {
251        let (a, b, c) = input;
252        (self)(a, b, c)
253    }
254}
255
256impl<O> Action<(), O> {
257    pub fn call(&mut self) -> Dispatching<()> {
258        Dispatching::new((self.callback).call(()))
259    }
260}
261
262impl<A: 'static, O> Action<(A,), O> {
263    pub fn call(&mut self, _a: A) -> Dispatching<()> {
264        Dispatching::new((self.callback).call((_a,)))
265    }
266}
267
268impl<A: 'static, B: 'static, O> Action<(A, B), O> {
269    pub fn call(&mut self, _a: A, _b: B) -> Dispatching<()> {
270        Dispatching::new((self.callback).call((_a, _b)))
271    }
272}
273
274impl<A: 'static, B: 'static, C: 'static, O> Action<(A, B, C), O> {
275    pub fn call(&mut self, _a: A, _b: B, _c: C) -> Dispatching<()> {
276        Dispatching::new((self.callback).call((_a, _b, _c)))
277    }
278}