use std::ops::Deref;
use std::{future::Future, rc::Rc};
use wasm_bindgen_futures::spawn_local;
use yew::prelude::*;
use super::{use_mount, use_mut_latest};
#[derive(Default)]
pub struct UseAsyncOptions {
pub auto: bool,
}
impl UseAsyncOptions {
pub const fn enable_auto() -> Self {
Self { auto: true }
}
}
#[derive(PartialEq, Eq)]
pub struct UseAsyncState<T, E> {
pub loading: bool,
pub data: Option<T>,
pub error: Option<E>,
}
pub struct UseAsyncHandle<T, E> {
inner: UseStateHandle<UseAsyncState<T, E>>,
run: Rc<dyn Fn()>,
}
impl<T, E> UseAsyncHandle<T, E> {
pub fn run(&self) {
(self.run)();
}
pub fn update(&self, data: T) {
self.inner.set(UseAsyncState {
loading: false,
data: Some(data),
error: None,
});
}
}
impl<T, E> Deref for UseAsyncHandle<T, E> {
type Target = UseAsyncState<T, E>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T, E> Clone for UseAsyncHandle<T, E> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
run: self.run.clone(),
}
}
}
impl<T, E> PartialEq for UseAsyncHandle<T, E>
where
T: PartialEq,
E: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
*self.inner == *other.inner
}
}
#[hook]
pub fn use_async<F, T, E>(future: F) -> UseAsyncHandle<T, E>
where
F: Future<Output = Result<T, E>> + 'static,
T: Clone + 'static,
E: Clone + 'static,
{
use_async_with_options(future, UseAsyncOptions::default())
}
#[hook]
pub fn use_async_with_options<F, T, E>(future: F, options: UseAsyncOptions) -> UseAsyncHandle<T, E>
where
F: Future<Output = Result<T, E>> + 'static,
T: Clone + 'static,
E: Clone + 'static,
{
let inner = use_state(|| UseAsyncState {
loading: false,
data: None,
error: None,
});
let future_ref = use_mut_latest(Some(future));
let run = {
let inner = inner.clone();
Rc::new(move || {
let inner = inner.clone();
let future_ref = future_ref.clone();
spawn_local(async move {
let future_ref = future_ref.current();
let future = (*future_ref.borrow_mut()).take();
if let Some(future) = future {
inner.set(UseAsyncState {
loading: true,
data: inner.data.clone(),
error: inner.error.clone(),
});
match future.await {
Ok(data) => inner.set(UseAsyncState {
loading: false,
data: Some(data),
error: None,
}),
Err(error) => inner.set(UseAsyncState {
loading: false,
data: inner.data.clone(),
error: Some(error),
}),
}
}
});
})
};
{
let run = run.clone();
use_mount(move || {
if options.auto {
run();
}
});
}
UseAsyncHandle { inner, run }
}