1#[cfg(feature = "cache")]
2use crate::CachableRequest;
3use crate::Request;
4
5use yew::prelude::*;
6use yew::suspense::SuspensionResult;
7#[cfg(feature = "cache")]
8use yewdux::prelude::use_store_value;
9
10#[derive(Clone, Debug)]
15pub struct Options<R, D>
16where
17 R: Request + 'static,
18 D: Clone + PartialEq + 'static,
19{
20 pub deps: Option<D>,
21 pub handler: Option<Callback<Result<R::Output, R::Error>, ()>>,
22}
23
24impl<R, D> Default for Options<R, D>
25where
26 R: Request + 'static,
27 D: Clone + PartialEq + 'static,
28{
29 fn default() -> Self {
30 Self {
31 deps: None,
32 handler: None,
33 }
34 }
35}
36
37#[hook]
40pub fn use_api<R: Request + 'static>(request: R) -> SuspensionResult<Result<R::Output, R::Error>> {
41 use_api_with_options::<R, ()>(request, Default::default())
42}
43
44#[hook]
49pub fn use_api_with_options<R: Request + 'static, D: Clone + PartialEq + 'static>(
50 request: R,
51 options: Options<R, D>,
52) -> SuspensionResult<Result<R::Output, R::Error>> {
53 let deps = (request, options.deps);
54
55 let result = inner::use_future_with_deps(
56 |deps| async move {
57 let result = deps.0.run().await;
58
59 if let Some(ref handler) = options.handler {
60 handler.emit(result.to_owned());
61 }
62
63 if let Ok(ref data) = result {
64 R::store(data.to_owned());
65 }
66
67 result
68 },
69 deps,
70 )?;
71
72 Ok((*result).to_owned())
73}
74
75pub struct LazyResponse<R: Request + 'static> {
77 pub run: Callback<(), ()>,
78 pub data: Option<SuspensionResult<Result<R::Output, R::Error>>>,
79}
80
81#[hook]
84pub fn use_api_lazy<R: Request + 'static>(request: R) -> LazyResponse<R> {
85 use_api_lazy_with_options::<R, ()>(request, Default::default())
86}
87
88#[hook]
91pub fn use_api_lazy_with_options<R: Request + 'static, D: Clone + PartialEq + 'static>(
92 request: R,
93 options: Options<R, D>,
94) -> LazyResponse<R> {
95 let DynLazyResponse { run, data } = use_api_dynamic_with_options::<R, D>(options);
96
97 let run = Callback::from(move |_| {
98 run.emit(request.clone());
99 });
100
101 LazyResponse { run, data }
102}
103
104pub struct DynLazyResponse<R: Request + 'static> {
105 pub run: Callback<R, ()>,
106 pub data: Option<SuspensionResult<Result<R::Output, R::Error>>>,
107}
108
109#[hook]
114pub fn use_api_dynamic<R: Request + 'static>() -> DynLazyResponse<R> {
115 use_api_dynamic_with_options::<R, ()>(Default::default())
116}
117
118#[hook]
123pub fn use_api_dynamic_with_options<R: Request + 'static, D: Clone + PartialEq + 'static>(
124 options: Options<R, D>,
125) -> DynLazyResponse<R> {
126 let request = use_state(|| Option::<R>::None);
127
128 let deps = ((*request).clone(), options.deps);
129
130 let (run, result) = inner::use_future_callback(
131 |request| async move {
132 let Some(ref request) = request.0 else {
133 return None;
134 };
135
136 let result = request.run().await;
137
138 if let Some(ref handler) = options.handler {
139 handler.emit(result.to_owned());
140 }
141
142 if let Ok(ref data) = result {
143 R::store(data.to_owned());
144 }
145
146 Some(result)
147 },
148 deps,
149 );
150
151 let run = Callback::from(move |r| {
152 request.set(Some(r));
153 run.emit(());
154 });
155
156 if let Some(Ok(false)) = result.as_ref().map(|o| o.as_ref().map(|sr| sr.is_some())) {
157 return DynLazyResponse { run, data: None };
158 }
159
160 let data = result.map(|res| res.map(|res| (*res).clone().unwrap()));
161
162 DynLazyResponse { run, data }
163}
164
165#[cfg(feature = "cache")]
167#[hook]
168pub fn use_cachable_api<R: Request + CachableRequest + 'static>(
169 request: R,
170) -> SuspensionResult<Result<R::Output, R::Error>> {
171 use_cachable_api_with_options::<R, ()>(request, Default::default())
172}
173
174#[cfg(feature = "cache")]
176#[hook]
177pub fn use_cachable_api_with_options<
178 R: Request + CachableRequest + 'static,
179 D: Clone + PartialEq + 'static,
180>(
181 request: R,
182 options: Options<R, D>,
183) -> SuspensionResult<Result<R::Output, R::Error>> {
184 let store = use_store_value::<R::Store>();
185 let deps = (request, options.deps);
186 let result = inner::use_future_with_deps(
187 |deps| async move {
188 if let Some(cache) = deps.0.load(store) {
189 return Ok(cache);
190 }
191
192 let result = deps.0.run().await;
193
194 if let Some(ref handler) = options.handler {
195 handler.emit(result.to_owned());
196 }
197
198 if let Ok(ref data) = result {
199 R::store(data.to_owned());
200 }
201
202 result
203 },
204 deps,
205 )?;
206
207 Ok((*result).to_owned())
208}
209
210#[cfg(feature = "cache")]
213#[hook]
214pub fn use_cachable_api_lazy<R: Request + CachableRequest + 'static>(
215 request: R,
216) -> LazyResponse<R> {
217 use_cachable_api_lazy_with_options::<R, ()>(request, Default::default())
218}
219
220#[cfg(feature = "cache")]
223#[hook]
224pub fn use_cachable_api_lazy_with_options<
225 R: Request + CachableRequest + 'static,
226 D: Clone + PartialEq + 'static,
227>(
228 request: R,
229 options: Options<R, D>,
230) -> LazyResponse<R> {
231 let DynLazyResponse { run, data } = use_cachable_api_dynamic_with_options::<R, D>(options);
232
233 let run = Callback::from(move |_| {
234 run.emit(request.clone());
235 });
236
237 LazyResponse { run, data }
238}
239
240#[cfg(feature = "cache")]
241#[hook]
242pub fn use_cachable_api_dynamic<R: Request + CachableRequest + 'static>() -> DynLazyResponse<R> {
243 use_cachable_api_dynamic_with_options::<R, ()>(Default::default())
244}
245
246#[cfg(feature = "cache")]
251#[hook]
252pub fn use_cachable_api_dynamic_with_options<
253 R: Request + CachableRequest + 'static,
254 D: Clone + PartialEq + 'static,
255>(
256 options: Options<R, D>,
257) -> DynLazyResponse<R> {
258 let store = use_store_value::<R::Store>();
259 let request = use_state(|| Option::<R>::None);
260
261 let deps = (request.clone(), options.deps);
262
263 let (run, result) = inner::use_future_callback(
264 |deps| async move {
265 let Some(ref request) = *(deps.0) else {
266 return None;
267 };
268
269 if let Some(cache) = request.load(store) {
270 return Some(Ok(cache));
271 }
272
273 let result = request.run().await;
274
275 if let Some(ref handler) = options.handler {
276 handler.emit(result.to_owned());
277 }
278
279 if let Ok(ref data) = result {
280 R::store(data.to_owned());
281 }
282
283 Some(result)
284 },
285 deps,
286 );
287
288 let run = Callback::from(move |r| {
289 request.set(Some(r));
290 run.emit(());
291 });
292
293 if let Some(Ok(false)) = result.as_ref().map(|o| o.as_ref().map(|sr| sr.is_some())) {
294 return DynLazyResponse { run, data: None };
295 }
296
297 let data = result.map(|res| res.map(|res| (*res).clone().unwrap()));
298
299 DynLazyResponse { run, data }
300}
301
302mod inner {
304 use std::borrow::Borrow;
305 use std::cell::Cell;
306 use std::fmt;
307 use std::future::Future;
308 use std::ops::Deref;
309 use std::rc::Rc;
310
311 use yew::prelude::*;
312 use yew::suspense::{Suspension, SuspensionResult};
313
314 pub struct UseFutureHandle<O> {
315 inner: UseStateHandle<Option<O>>,
316 }
317
318 impl<O> Deref for UseFutureHandle<O> {
319 type Target = O;
320
321 fn deref(&self) -> &Self::Target {
322 self.inner.as_ref().unwrap()
323 }
324 }
325
326 impl<T: fmt::Debug> fmt::Debug for UseFutureHandle<T> {
327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328 f.debug_struct("UseFutureHandle")
329 .field("value", &format!("{:?}", self.inner))
330 .finish()
331 }
332 }
333
334 #[hook]
335 pub fn use_future<F, T, O>(init_f: F) -> SuspensionResult<UseFutureHandle<O>>
336 where
337 F: FnOnce() -> T,
338 T: Future<Output = O> + 'static,
339 O: 'static,
340 {
341 use_future_with_deps(move |_| init_f(), ())
342 }
343
344 #[hook]
345 pub fn use_future_with_deps<F, D, T, O>(f: F, deps: D) -> SuspensionResult<UseFutureHandle<O>>
346 where
347 F: FnOnce(Rc<D>) -> T,
348 T: Future<Output = O> + 'static,
349 O: 'static,
350 D: PartialEq + 'static,
351 {
352 let output = use_state(|| None);
353 let latest_id = use_state(|| Cell::new(0u32));
356
357 let suspension = {
358 let output = output.clone();
359
360 use_memo_base(
361 move |deps| {
362 let self_id = latest_id.get().wrapping_add(1);
363 (*latest_id).set(self_id);
365 let deps = Rc::new(deps);
366 let task = f(deps.clone());
367 let suspension = Suspension::from_future(async move {
368 let result = task.await;
369 if latest_id.get() == self_id {
370 output.set(Some(result));
371 }
372 });
373 (suspension, deps)
374 },
375 deps,
376 )
377 };
378
379 if suspension.resumed() {
380 Ok(UseFutureHandle { inner: output })
381 } else {
382 Err((*suspension).clone())
383 }
384 }
385
386 #[hook]
387 pub fn use_future_callback<F, D, T, O>(
388 f: F,
389 deps: D,
390 ) -> (
391 Callback<(), ()>,
392 Option<SuspensionResult<UseFutureHandle<O>>>,
393 )
394 where
395 F: FnOnce(Rc<D>) -> T,
396 T: Future<Output = O> + 'static,
397 O: 'static,
398 D: Clone + PartialEq + 'static,
399 {
400 let execution = use_state(|| false);
401 let execute: Callback<(), ()> = {
402 let execution = execution.clone();
403 use_callback(move |_, _| execution.set(true), ())
404 };
405
406 let output = use_state(|| None);
407 let latest_id = use_state(|| Cell::new(0u32));
410
411 let suspension = {
412 let output = output.clone();
413
414 let deps = (deps, execution.clone());
415 use_memo_base(
416 move |deps| {
417 if !(*execution) {
418 return (None, deps);
419 }
420
421 let self_id = latest_id.get().wrapping_add(1);
422 (*latest_id).set(self_id);
424 let task = f(Rc::new(deps.0.clone()));
425 let suspension = Suspension::from_future(async move {
426 let result = task.await;
427
428 if latest_id.get() == self_id {
429 output.set(Some(result));
430 }
431 execution.set(false);
432 });
433 (Some(suspension), (deps.0.to_owned(), deps.1))
434 },
435 deps,
436 )
437 };
438
439 if let Some(ref suspension) = *suspension {
440 if suspension.resumed() {
441 return (execute, Some(Ok(UseFutureHandle { inner: output })));
442 } else {
443 return (execute, Some(Err(suspension.clone())));
444 }
445 }
446
447 if output.is_some() {
448 return (execute, Some(Ok(UseFutureHandle { inner: output })));
449 }
450
451 (execute, None)
452 }
453
454 #[hook]
455 pub(crate) fn use_memo_base<T, F, D, K>(f: F, deps: D) -> Rc<T>
456 where
457 T: 'static,
458 F: FnOnce(D) -> (T, K),
459 K: 'static + Borrow<D>,
460 D: PartialEq,
461 {
462 struct MemoState<T, K> {
463 memo_key: K,
464 result: Rc<T>,
465 }
466 let state = use_mut_ref(|| -> Option<MemoState<T, K>> { None });
467
468 let mut state = state.borrow_mut();
469 match &*state {
470 Some(existing) if existing.memo_key.borrow() != &deps => {
471 *state = None;
473 }
474 _ => {}
475 };
476 let state = state.get_or_insert_with(|| {
477 let (result, memo_key) = f(deps);
478 let result = Rc::new(result);
479 MemoState { result, memo_key }
480 });
481 state.result.clone()
482 }
483}