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