1use dioxus_core::{use_hook, IntoAttributeValue, IntoDynNode, Subscribers};
2use dioxus_core::{CapturedError, RenderError, Result, SuspendedFuture};
3use dioxus_hooks::{use_resource, use_signal, Resource};
4use dioxus_signals::{
5 read_impls, ReadSignal, Readable, ReadableBoxExt, ReadableExt, ReadableRef, Signal, Writable,
6 WritableExt, WriteLock,
7};
8use generational_box::{BorrowResult, UnsyncStorage};
9use serde::{de::DeserializeOwned, Serialize};
10use std::ops::Deref;
11use std::{cmp::PartialEq, future::Future};
12
13#[allow(clippy::result_large_err)]
31#[track_caller]
32pub fn use_loader<F, T, E>(mut future: impl FnMut() -> F + 'static) -> Result<Loader<T>, Loading>
33where
34 F: Future<Output = Result<T, E>> + 'static,
35 T: 'static + PartialEq + Serialize + DeserializeOwned,
36 E: Into<CapturedError> + 'static,
37{
38 let serialize_context = use_hook(crate::transport::serialize_context);
39
40 #[allow(unused)]
42 let storage_entry: crate::transport::SerializeContextEntry<Result<T, CapturedError>> =
43 use_hook(|| serialize_context.create_entry());
44
45 #[cfg(feature = "server")]
46 let caller = std::panic::Location::caller();
47
48 #[cfg(feature = "web")]
50 let initial_web_result =
51 use_hook(|| std::rc::Rc::new(std::cell::RefCell::new(Some(storage_entry.get()))));
52
53 let mut error = use_signal(|| None as Option<CapturedError>);
54 let mut value = use_signal(|| None as Option<T>);
55 let mut loader_state = use_signal(|| LoaderState::Pending);
56
57 let resource = use_resource(move || {
58 #[cfg(feature = "server")]
59 let storage_entry = storage_entry.clone();
60
61 let user_fut = future();
62
63 #[cfg(feature = "web")]
64 let initial_web_result = initial_web_result.clone();
65
66 #[allow(clippy::let_and_return)]
67 async move {
68 #[cfg(feature = "web")]
70 match initial_web_result.take() {
71 Some(Ok(o)) => {
73 match o {
74 Ok(v) => {
75 value.set(Some(v));
76 loader_state.set(LoaderState::Ready);
77 }
78 Err(e) => {
79 error.set(Some(e));
80 loader_state.set(LoaderState::Failed);
81 }
82 };
83 return;
84 }
85
86 Some(Err(crate::transport::TakeDataError::DataPending)) => {
88 std::future::pending::<()>().await
89 }
90
91 Some(Err(_)) => {}
93
94 None => {}
96 }
97
98 let out = user_fut.await;
100
101 let out = out.map_err(|e| {
104 let anyhow_err: CapturedError = e.into();
105 anyhow_err
106 });
107
108 #[cfg(feature = "server")]
110 storage_entry.insert(&out, caller);
111
112 match out {
113 Ok(v) => {
114 value.set(Some(v));
115 loader_state.set(LoaderState::Ready);
116 }
117 Err(e) => {
118 error.set(Some(e));
119 loader_state.set(LoaderState::Failed);
120 }
121 };
122 }
123 });
124
125 use_hook(|| {
127 let _ = resource.task().poll_now();
128 });
129
130 let read_value = use_hook(|| value.map(|f| f.as_ref().unwrap()).boxed());
131
132 let handle = LoaderHandle {
133 resource,
134 error,
135 state: loader_state,
136 _marker: std::marker::PhantomData,
137 };
138
139 match &*loader_state.read_unchecked() {
140 LoaderState::Pending => Err(Loading::Pending(handle)),
141 LoaderState::Failed => Err(Loading::Failed(handle)),
142 LoaderState::Ready => Ok(Loader {
143 real_value: value,
144 read_value,
145 error,
146 state: loader_state,
147 handle,
148 }),
149 }
150}
151
152pub struct Loader<T: 'static> {
159 read_value: ReadSignal<T>,
161
162 real_value: Signal<Option<T>>,
164 error: Signal<Option<CapturedError>>,
165 state: Signal<LoaderState>,
166 handle: LoaderHandle,
167}
168
169impl<T: 'static> Loader<T> {
170 pub fn error(&self) -> Option<CapturedError> {
174 self.error.read().as_ref().cloned()
175 }
176
177 pub fn restart(&mut self) {
181 self.handle.restart();
182 }
183
184 pub fn is_error(&self) -> bool {
186 self.error.read().is_some() && matches!(*self.state.read(), LoaderState::Failed)
187 }
188
189 pub fn cancel(&mut self) {
191 self.handle.resource.cancel();
192 }
193
194 pub fn loading(&self) -> bool {
195 !self.handle.resource.finished()
196 }
197}
198
199impl<T: 'static> Writable for Loader<T> {
200 type WriteMetadata = <Signal<Option<T>> as Writable>::WriteMetadata;
201
202 fn try_write_unchecked(
203 &self,
204 ) -> std::result::Result<
205 dioxus_signals::WritableRef<'static, Self>,
206 generational_box::BorrowMutError,
207 >
208 where
209 Self::Target: 'static,
210 {
211 let writer = self.real_value.try_write_unchecked()?;
212 Ok(WriteLock::map(writer, |f: &mut Option<T>| {
213 f.as_mut()
214 .expect("Loader value should be set if the `Loader<T>` exists")
215 }))
216 }
217}
218
219impl<T> Readable for Loader<T> {
220 type Target = T;
221 type Storage = UnsyncStorage;
222
223 #[track_caller]
224 fn try_read_unchecked(
225 &self,
226 ) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>
227 where
228 T: 'static,
229 {
230 Ok(self.read_value.read_unchecked())
231 }
232
233 #[track_caller]
237 fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>
238 where
239 T: 'static,
240 {
241 Ok(self.read_value.peek_unchecked())
242 }
243
244 fn subscribers(&self) -> Subscribers
245 where
246 T: 'static,
247 {
248 self.read_value.subscribers()
249 }
250}
251
252impl<T> IntoAttributeValue for Loader<T>
253where
254 T: Clone + IntoAttributeValue + PartialEq + 'static,
255{
256 fn into_value(self) -> dioxus_core::AttributeValue {
257 self.with(|f| f.clone().into_value())
258 }
259}
260
261impl<T> IntoDynNode for Loader<T>
262where
263 T: Clone + IntoDynNode + PartialEq + 'static,
264{
265 fn into_dyn_node(self) -> dioxus_core::DynamicNode {
266 let t: T = self();
267 t.into_dyn_node()
268 }
269}
270
271impl<T: 'static> PartialEq for Loader<T> {
272 fn eq(&self, other: &Self) -> bool {
273 self.read_value == other.read_value
274 }
275}
276
277impl<T: Clone> Deref for Loader<T>
278where
279 T: PartialEq + 'static,
280{
281 type Target = dyn Fn() -> T;
282
283 fn deref(&self) -> &Self::Target {
284 unsafe { ReadableExt::deref_impl(self) }
285 }
286}
287
288read_impls!(Loader<T> where T: PartialEq);
289
290impl<T> Clone for Loader<T> {
291 fn clone(&self) -> Self {
292 *self
293 }
294}
295
296impl<T> Copy for Loader<T> {}
297
298#[derive(Clone, Copy, PartialEq, Hash, Eq, Debug)]
299pub enum LoaderState {
300 Pending,
302
303 Ready,
305
306 Failed,
308}
309
310#[derive(PartialEq)]
311pub struct LoaderHandle<M = ()> {
312 resource: Resource<()>,
313 error: Signal<Option<CapturedError>>,
314 state: Signal<LoaderState>,
315 _marker: std::marker::PhantomData<M>,
316}
317
318impl LoaderHandle {
319 pub fn restart(&mut self) {
321 self.resource.restart();
322 }
323
324 pub fn state(&self) -> LoaderState {
326 *self.state.read()
327 }
328
329 pub fn error(&self) -> Option<CapturedError> {
330 self.error.read().as_ref().cloned()
331 }
332}
333
334impl Clone for LoaderHandle {
335 fn clone(&self) -> Self {
336 *self
337 }
338}
339
340impl Copy for LoaderHandle {}
341
342#[derive(PartialEq)]
343pub enum Loading {
344 Pending(LoaderHandle),
346
347 Failed(LoaderHandle),
349}
350
351impl std::fmt::Debug for Loading {
352 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
353 match self {
354 Loading::Pending(_) => write!(f, "Loading::Pending"),
355 Loading::Failed(_) => write!(f, "Loading::Failed"),
356 }
357 }
358}
359
360impl std::fmt::Display for Loading {
361 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
362 match self {
363 Loading::Pending(_) => write!(f, "Loading is still pending"),
364 Loading::Failed(_) => write!(f, "Loading has failed"),
365 }
366 }
367}
368
369impl From<Loading> for RenderError {
371 fn from(val: Loading) -> Self {
372 match val {
373 Loading::Pending(t) => RenderError::Suspended(SuspendedFuture::new(t.resource.task())),
374 Loading::Failed(err) => RenderError::Error(
375 err.error
376 .cloned()
377 .expect("LoaderHandle in Failed state should always have an error"),
378 ),
379 }
380 }
381}