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>
71where
72 E: Into<CapturedError> + 'static,
73 C: ActionCallback<M, E>,
74 M: 'static,
75 C::Input: 'static,
76 C::Output: 'static,
77 C: 'static,
78{
79 let mut value = use_signal(|| None as Option<C::Output>);
80 let mut error = use_signal(|| None as Option<CapturedError>);
81 let mut task = use_signal(|| None as Option<Task>);
82 let mut state = use_signal(|| ActionState::Unset);
83 let callback = use_callback(move |input: C::Input| {
84 if let Some(task) = task.take() {
86 task.cancel();
87 }
88
89 let (tx, rx) = futures_channel::oneshot::channel();
90 let rx = rx.shared();
91
92 let result = user_fn.call(input);
94 let new_task = dioxus_core::spawn(async move {
95 state.set(ActionState::Pending);
97
98 let result = result.await;
100 match result {
101 Ok(res) => {
102 error.set(None);
103 value.set(Some(res));
104 state.set(ActionState::Ready);
105 }
106 Err(err) => {
107 error.set(Some(err.into()));
108 value.set(None);
109 state.set(ActionState::Errored);
110 }
111 }
112
113 tx.send(()).ok();
114 });
115
116 task.set(Some(new_task));
117
118 rx
119 });
120
121 let reader = use_hook(|| value.boxed().map(|v| v.as_ref().unwrap()).boxed());
124
125 Action {
126 value,
127 error,
128 task,
129 callback,
130 reader,
131 _phantom: PhantomData,
132 state,
133 }
134}
135
136pub struct Action<I, T: 'static> {
142 reader: ReadSignal<T>,
143 error: Signal<Option<CapturedError>>,
144 value: Signal<Option<T>>,
145 task: Signal<Option<Task>>,
146 callback: Callback<I, Shared<Receiver<()>>>,
147 state: Signal<ActionState>,
148 _phantom: PhantomData<*const I>,
149}
150
151#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
155enum ActionState {
156 Unset,
157 Pending,
158 Ready,
159 Errored,
160 Reset,
161}
162
163impl<I: 'static, O: 'static> Action<I, O> {
164 pub fn value(&self) -> Option<Result<ReadSignal<O>, CapturedError>> {
171 if !matches!(
172 *self.state.read(),
173 ActionState::Ready | ActionState::Errored
174 ) {
175 return None;
176 }
177
178 if let Some(err) = self.error.cloned() {
179 return Some(Err(err));
180 }
181
182 if self.value.read().is_none() {
183 return None;
184 }
185
186 Some(Ok(self.reader))
187 }
188
189 pub fn pending(&self) -> bool {
191 *self.state.read() == ActionState::Pending
192 }
193
194 pub fn reset(&mut self) {
196 self.state.set(ActionState::Reset);
197 if let Some(t) = self.task.take() {
198 t.cancel()
199 }
200 }
201
202 pub fn cancel(&mut self) {
204 if let Some(t) = self.task.take() {
205 t.cancel()
206 }
207 self.state.set(ActionState::Reset);
208 }
209}
210
211impl<I, T> std::fmt::Debug for Action<I, T>
212where
213 T: std::fmt::Debug + 'static,
214{
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 if f.alternate() {
217 f.debug_struct("Action")
218 .field("state", &self.state.read())
219 .field("value", &self.value.read())
220 .field("error", &self.error.read())
221 .finish()
222 } else {
223 std::fmt::Debug::fmt(&self.value.read().as_ref(), f)
224 }
225 }
226}
227
228impl<I, T> PartialEq for Action<I, T> {
229 fn eq(&self, other: &Self) -> bool {
230 self.callback == other.callback
231 }
232}
233
234pub struct Dispatching<I> {
235 _phantom: PhantomData<*const I>,
236 receiver: Shared<Receiver<()>>,
237}
238
239impl<T> Dispatching<T> {
240 pub(crate) fn new(receiver: Shared<Receiver<()>>) -> Self {
241 Self {
242 _phantom: PhantomData,
243 receiver,
244 }
245 }
246}
247
248impl<T> std::future::Future for Dispatching<T> {
249 type Output = ();
250
251 fn poll(mut self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
252 match self.receiver.poll_unpin(_cx) {
253 Poll::Ready(_) => Poll::Ready(()),
254 Poll::Pending => Poll::Pending,
255 }
256 }
257}
258
259impl<I, T> Copy for Action<I, T> {}
260impl<I, T> Clone for Action<I, T> {
261 fn clone(&self) -> Self {
262 *self
263 }
264}
265
266pub trait ActionCallback<Marker, Err> {
267 type Input;
268 type Output;
269 fn call(
270 &mut self,
271 input: Self::Input,
272 ) -> impl Future<Output = Result<Self::Output, Err>> + 'static;
273}
274
275macro_rules! impl_action_callback {
276 () => {
278 impl<Func, Out, Fut, Err> ActionCallback<(Out,), Err> for Func
279 where
280 Func: FnMut() -> Fut,
281 Fut: Future<Output = Result<Out, Err>> + 'static,
282 {
283 type Input = ();
284 type Output = Out;
285 fn call(
286 &mut self,
287 _input: Self::Input,
288 ) -> impl Future<Output = Result<Self::Output, Err>> + 'static {
289 (self)()
290 }
291 }
292
293 impl<Out> Action<(), Out> {
294 pub fn call(&mut self) -> Dispatching<()> {
296 Dispatching::new((self.callback).call(()))
297 }
298 }
299 };
300
301 ($($arg:ident),+) => {
303 impl<Func, Out, $($arg,)+ Fut, Err> ActionCallback<($($arg,)+ Out), Err> for Func
304 where
305 Func: FnMut($($arg),+) -> Fut,
306 Fut: Future<Output = Result<Out, Err>> + 'static,
307 {
308 type Input = ($($arg,)+);
309 type Output = Out;
310 fn call(
311 &mut self,
312 input: Self::Input,
313 ) -> impl Future<Output = Result<Self::Output, Err>> + 'static {
314 #[allow(non_snake_case)]
315 let ($($arg,)+) = input;
316 (self)($($arg),+)
317 }
318 }
319
320 impl<$($arg: 'static,)+ Out> Action<($($arg,)+), Out> {
321 #[allow(non_snake_case)]
323 pub fn call(&mut self, $($arg: $arg),+) -> Dispatching<()> {
324 Dispatching::new((self.callback).call(($($arg,)+)))
325 }
326 }
327 };
328}
329
330impl_action_callback!();
331impl_action_callback!(A);
332impl_action_callback!(A, B);
333impl_action_callback!(A, B, C);
334impl_action_callback!(A, B, C, D);
335impl_action_callback!(A, B, C, D, E);
336impl_action_callback!(A, B, C, D, E, F);