Skip to main content
This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/suspense/
hooks.rs

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