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

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())
    }
}