1use dioxus_core::{
2 spawn, use_hook, Callback, IntoAttributeValue, IntoDynNode, ReactiveContext, Subscribers, Task,
3};
4use dioxus_core::{CapturedError, RenderError, Result, SuspendedFuture};
5use dioxus_hooks::{use_callback, use_signal};
6use dioxus_signals::{
7 read_impls, ReadSignal, Readable, ReadableBoxExt, ReadableExt, ReadableRef, Signal, Writable,
8 WritableExt, WriteLock,
9};
10use futures_util::{future, pin_mut, FutureExt, StreamExt};
11use generational_box::{BorrowResult, UnsyncStorage};
12use serde::{de::DeserializeOwned, Serialize};
13use std::future::Future;
14use std::{cell::Cell, ops::Deref, rc::Rc};
15
16#[allow(clippy::result_large_err)]
34#[track_caller]
35pub fn use_loader<F, T, E>(mut future: impl FnMut() -> F + 'static) -> Result<Loader<T>, Loading>
36where
37 F: Future<Output = Result<T, E>> + 'static,
38 T: 'static + std::cmp::PartialEq + Serialize + DeserializeOwned,
39 E: Into<dioxus_core::Error> + 'static,
40{
41 let location = std::panic::Location::caller();
42
43 let mut err = use_signal(|| None as Option<CapturedError>);
50 let mut value = use_signal(|| None as Option<T>);
51 let mut state = use_signal(|| LoaderState::Pending);
52 let (rc, changed) = use_hook(|| {
53 let (rc, changed) = ReactiveContext::new_with_origin(location);
54 (rc, Rc::new(Cell::new(Some(changed))))
55 });
56
57 let callback = use_callback(move |_| {
58 state.set(LoaderState::Pending);
60
61 let fut = rc.reset_and_run_in(&mut future);
63
64 spawn(async move {
66 let fut = fut;
68 pin_mut!(fut);
69
70 let res = future::poll_fn(|cx| {
73 rc.run_in(|| {
74 tracing::trace_span!("polling resource", location = %location)
75 .in_scope(|| fut.poll_unpin(cx))
76 })
77 })
78 .await;
79
80 let res: Result<T, CapturedError> = res.map_err(|e| {
83 let res: dioxus_core::Error = e.into();
84 res.into()
85 });
86
87 match res {
88 Ok(v) => {
89 err.set(None);
90 value.set(Some(v));
91 state.set(LoaderState::Ready);
92 }
93 Err(e) => {
94 err.set(Some(e));
95 state.set(LoaderState::Failed);
96 }
97 }
98 })
99 });
100
101 let mut task = use_hook(|| Signal::new(callback(())));
102
103 use_hook(|| {
104 let mut changed = changed.take().unwrap();
105 spawn(async move {
106 loop {
107 let _ = changed.next().await;
109
110 task.write().cancel();
112
113 task.set(callback(()));
115 }
116 })
117 });
118
119 let read_value = use_hook(|| value.map(|f| f.as_ref().unwrap()).boxed());
120
121 let handle = LoaderHandle {
122 task,
123 err,
124 callback,
125 state,
126 };
127
128 match &*state.read_unchecked() {
129 LoaderState::Pending => Err(Loading::Pending(handle)),
130 LoaderState::Failed => Err(Loading::Failed(handle)),
131 LoaderState::Ready => Ok(Loader {
132 real_value: value,
133 read_value,
134 error: err,
135 state,
136 task,
137 handle,
138 }),
139 }
140}
141
142pub struct Loader<T: 'static> {
149 read_value: ReadSignal<T>,
151
152 real_value: Signal<Option<T>>,
154 error: Signal<Option<CapturedError>>,
155 state: Signal<LoaderState>,
156 task: Signal<Task>,
157 handle: LoaderHandle,
158}
159
160impl<T: 'static> Loader<T> {
161 pub fn error(&self) -> Option<CapturedError> {
165 self.error.read().as_ref().cloned()
166 }
167
168 pub fn restart(&mut self) {
172 self.handle.restart();
173 }
174
175 pub fn is_error(&self) -> bool {
177 self.error.read().is_some() && matches!(*self.state.read(), LoaderState::Failed)
178 }
179
180 pub fn cancel(&self) {
182 self.task.read().cancel();
183 }
184}
185
186impl<T: 'static> Writable for Loader<T> {
187 type WriteMetadata = <Signal<Option<T>> as Writable>::WriteMetadata;
188
189 fn try_write_unchecked(
190 &self,
191 ) -> std::result::Result<
192 dioxus_signals::WritableRef<'static, Self>,
193 generational_box::BorrowMutError,
194 >
195 where
196 Self::Target: 'static,
197 {
198 let writer = self.real_value.try_write_unchecked()?;
199 Ok(WriteLock::map(writer, |f: &mut Option<T>| {
200 f.as_mut()
201 .expect("Loader value should be set if the `Loader<T>` exists")
202 }))
203 }
204}
205
206impl<T> Readable for Loader<T> {
207 type Target = T;
208 type Storage = UnsyncStorage;
209
210 #[track_caller]
211 fn try_read_unchecked(
212 &self,
213 ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>
214 where
215 T: 'static,
216 {
217 Ok(self.read_value.read_unchecked())
218 }
219
220 #[track_caller]
224 fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>
225 where
226 T: 'static,
227 {
228 Ok(self.peek_unchecked())
229 }
230
231 fn subscribers(&self) -> Subscribers
232 where
233 T: 'static,
234 {
235 self.read_value.subscribers()
236 }
237}
238
239impl<T> IntoAttributeValue for Loader<T>
240where
241 T: Clone + IntoAttributeValue + PartialEq + 'static,
242{
243 fn into_value(self) -> dioxus_core::AttributeValue {
244 self.with(|f| f.clone().into_value())
245 }
246}
247
248impl<T> IntoDynNode for Loader<T>
249where
250 T: Clone + IntoDynNode + PartialEq + 'static,
251{
252 fn into_dyn_node(self) -> dioxus_core::DynamicNode {
253 self().into_dyn_node()
254 }
255}
256
257impl<T: 'static> PartialEq for Loader<T> {
258 fn eq(&self, other: &Self) -> bool {
259 self.read_value == other.read_value
260 }
261}
262
263impl<T: Clone> Deref for Loader<T>
264where
265 T: PartialEq + 'static,
266{
267 type Target = dyn Fn() -> T;
268
269 fn deref(&self) -> &Self::Target {
270 unsafe { ReadableExt::deref_impl(self) }
271 }
272}
273
274read_impls!(Loader<T> where T: PartialEq);
275
276impl<T> Clone for Loader<T> {
277 fn clone(&self) -> Self {
278 *self
279 }
280}
281
282impl<T> Copy for Loader<T> {}
283
284#[derive(Clone, Copy, PartialEq, Hash, Eq, Debug)]
285pub enum LoaderState {
286 Pending,
288
289 Ready,
291
292 Failed,
294}
295
296#[derive(PartialEq)]
297pub struct LoaderHandle {
298 callback: Callback<(), Task>,
299 task: Signal<Task>,
300 err: Signal<Option<CapturedError>>,
301 state: Signal<LoaderState>,
302}
303
304impl LoaderHandle {
305 pub fn restart(&mut self) {
307 self.task.write().cancel();
308 let new_task = self.callback.call(());
309 self.task.set(new_task);
310 }
311
312 pub fn state(&self) -> LoaderState {
314 *self.state.read()
315 }
316}
317
318impl Clone for LoaderHandle {
319 fn clone(&self) -> Self {
320 *self
321 }
322}
323
324impl Copy for LoaderHandle {}
325
326#[derive(PartialEq)]
327pub enum Loading {
328 Pending(LoaderHandle),
330
331 Failed(LoaderHandle),
333}
334
335impl std::fmt::Debug for Loading {
336 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337 match self {
338 Loading::Pending(_) => write!(f, "Loading::Pending"),
339 Loading::Failed(_) => write!(f, "Loading::Failed"),
340 }
341 }
342}
343
344impl std::fmt::Display for Loading {
345 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
346 match self {
347 Loading::Pending(_) => write!(f, "Loading is still pending"),
348 Loading::Failed(_) => write!(f, "Loading has failed"),
349 }
350 }
351}
352
353impl From<Loading> for RenderError {
355 fn from(val: Loading) -> Self {
356 match val {
357 Loading::Pending(t) => RenderError::Suspended(SuspendedFuture::new(t.task.cloned())),
358 Loading::Failed(err) => RenderError::Error(
359 err.err
360 .cloned()
361 .expect("LoaderHandle in Failed state should always have an error"),
362 ),
363 }
364 }
365}