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.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 self().into_dyn_node()
267 }
268}
269
270impl<T: 'static> PartialEq for Loader<T> {
271 fn eq(&self, other: &Self) -> bool {
272 self.read_value == other.read_value
273 }
274}
275
276impl<T: Clone> Deref for Loader<T>
277where
278 T: PartialEq + 'static,
279{
280 type Target = dyn Fn() -> T;
281
282 fn deref(&self) -> &Self::Target {
283 unsafe { ReadableExt::deref_impl(self) }
284 }
285}
286
287read_impls!(Loader<T> where T: PartialEq);
288
289impl<T> Clone for Loader<T> {
290 fn clone(&self) -> Self {
291 *self
292 }
293}
294
295impl<T> Copy for Loader<T> {}
296
297#[derive(Clone, Copy, PartialEq, Hash, Eq, Debug)]
298pub enum LoaderState {
299 Pending,
301
302 Ready,
304
305 Failed,
307}
308
309#[derive(PartialEq)]
310pub struct LoaderHandle<M = ()> {
311 resource: Resource<()>,
312 error: Signal<Option<CapturedError>>,
313 state: Signal<LoaderState>,
314 _marker: std::marker::PhantomData<M>,
315}
316
317impl LoaderHandle {
318 pub fn restart(&mut self) {
320 self.resource.restart();
321 }
322
323 pub fn state(&self) -> LoaderState {
325 *self.state.read()
326 }
327
328 pub fn error(&self) -> Option<CapturedError> {
329 self.error.read().as_ref().cloned()
330 }
331}
332
333impl Clone for LoaderHandle {
334 fn clone(&self) -> Self {
335 *self
336 }
337}
338
339impl Copy for LoaderHandle {}
340
341#[derive(PartialEq)]
342pub enum Loading {
343 Pending(LoaderHandle),
345
346 Failed(LoaderHandle),
348}
349
350impl std::fmt::Debug for Loading {
351 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352 match self {
353 Loading::Pending(_) => write!(f, "Loading::Pending"),
354 Loading::Failed(_) => write!(f, "Loading::Failed"),
355 }
356 }
357}
358
359impl std::fmt::Display for Loading {
360 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361 match self {
362 Loading::Pending(_) => write!(f, "Loading is still pending"),
363 Loading::Failed(_) => write!(f, "Loading has failed"),
364 }
365 }
366}
367
368impl From<Loading> for RenderError {
370 fn from(val: Loading) -> Self {
371 match val {
372 Loading::Pending(t) => RenderError::Suspended(SuspendedFuture::new(t.resource.task())),
373 Loading::Failed(err) => RenderError::Error(
374 err.error
375 .cloned()
376 .expect("LoaderHandle in Failed state should always have an error"),
377 ),
378 }
379 }
380}