yew/suspense/hooks.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
use std::cell::Cell;
use std::fmt;
use std::future::Future;
use std::ops::Deref;
use std::rc::Rc;
use yew::prelude::*;
use yew::suspense::{Suspension, SuspensionResult};
/// This hook is used to await a future in a suspending context.
///
/// A [Suspension] is created from the passed future and the result of the future
/// is the output of the suspension.
pub struct UseFutureHandle<O> {
inner: UseStateHandle<Option<O>>,
}
impl<O> Clone for UseFutureHandle<O> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<O> Deref for UseFutureHandle<O> {
type Target = O;
fn deref(&self) -> &Self::Target {
self.inner.as_ref().unwrap()
}
}
impl<T: fmt::Debug> fmt::Debug for UseFutureHandle<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UseFutureHandle")
.field("value", &format!("{:?}", self.inner))
.finish()
}
}
/// Use the result of an async computation, suspending while waiting.
///
/// Awaits the future returned from the first call to `init_f`, and returns
/// its result in a [`UseFutureHandle`]. Always suspends initially, even if
/// the future is immediately [ready].
///
/// [ready]: std::task::Poll::Ready
///
/// # Example
///
/// ```
/// # use yew::prelude::*;
/// # use yew::suspense::use_future;
/// use gloo::net::http::Request;
///
/// const URL: &str = "https://en.wikipedia.org/w/api.php?\
/// action=query&origin=*&format=json&generator=search&\
/// gsrnamespace=0&gsrlimit=5&gsrsearch='New_England_Patriots'";
///
/// #[function_component]
/// fn WikipediaSearch() -> HtmlResult {
/// let res = use_future(|| async { Request::get(URL).send().await?.text().await })?;
/// let result_html = match *res {
/// Ok(ref res) => html! { res },
/// Err(ref failure) => failure.to_string().into(),
/// };
/// Ok(html! {
/// <p>
/// {"Wikipedia search result: "}
/// {result_html}
/// </p>
/// })
/// }
/// ```
#[hook]
pub fn use_future<F, T, O>(init_f: F) -> SuspensionResult<UseFutureHandle<O>>
where
F: FnOnce() -> T,
T: Future<Output = O> + 'static,
O: 'static,
{
use_future_with((), move |_| init_f())
}
/// Use the result of an async computation with dependencies, suspending while waiting.
///
/// Awaits the future returned from `f` for the latest `deps`. Even if the future is immediately
/// [ready], the hook suspends at least once. If the dependencies
/// change while a future is still pending, the result is never used. This guarantees that your
/// component always sees up-to-date values while it is not suspended.
///
/// [ready]: std::task::Poll::Ready
#[hook]
pub fn use_future_with<F, D, T, O>(deps: D, f: F) -> SuspensionResult<UseFutureHandle<O>>
where
F: FnOnce(Rc<D>) -> T,
T: Future<Output = O> + 'static,
O: 'static,
D: PartialEq + 'static,
{
let output = use_state(|| None);
// We only commit a result if it comes from the latest spawned future. Otherwise, this
// might trigger pointless updates or even override newer state.
let latest_id = use_ref(|| Cell::new(0u32));
let suspension = {
let output = output.clone();
use_memo_base(
move |deps| {
let self_id = latest_id.get().wrapping_add(1);
// As long as less than 2**32 futures are in flight wrapping_add is fine
(*latest_id).set(self_id);
let deps = Rc::new(deps);
let task = f(deps.clone());
let suspension = Suspension::from_future(async move {
let result = task.await;
if latest_id.get() == self_id {
output.set(Some(result));
}
});
(suspension, deps)
},
deps,
)
};
if suspension.resumed() {
Ok(UseFutureHandle { inner: output })
} else {
Err((*suspension).clone())
}
}