Add use_future hook to make consuming futures as suspense easier (#2609)

* Add `use_suspending_future` hook to make consuming futures as suspense easier

* Add test

* fmt

* use_suspending_future -> use_future

* use_future takes a closure
This commit is contained in:
Muhammad Hamza 2022-04-14 23:10:32 +05:00 committed by GitHub
parent b580bd4c2f
commit edeb59d777
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 110 additions and 1 deletions

View File

@ -0,0 +1,66 @@
#[cfg_attr(documenting, doc(cfg(any(target_arch = "wasm32", feature = "tokio"))))]
#[cfg(any(target_arch = "wasm32", feature = "tokio"))]
mod feat_futures {
use std::fmt;
use std::future::Future;
use std::ops::Deref;
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> 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()
}
}
#[hook]
pub fn use_future<F, T, O>(f: F) -> SuspensionResult<UseFutureHandle<O>>
where
F: FnOnce() -> T + 'static,
T: Future<Output = O> + 'static,
O: 'static,
{
let output = use_state(|| None);
let suspension = {
let output = output.clone();
use_memo(
move |_| {
Suspension::from_future(async move {
output.set(Some(f().await));
})
},
(),
)
};
if suspension.resumed() {
Ok(UseFutureHandle { inner: output })
} else {
Err((*suspension).clone())
}
}
}
#[cfg(any(target_arch = "wasm32", feature = "tokio"))]
pub use feat_futures::*;

View File

@ -1,9 +1,11 @@
//! This module provides suspense support.
mod component;
mod hooks;
mod suspension;
#[cfg(any(feature = "csr", feature = "ssr"))]
pub(crate) use component::BaseSuspense;
pub use component::Suspense;
pub use hooks::*;
pub use suspension::{Suspension, SuspensionHandle, SuspensionResult};

View File

@ -15,7 +15,7 @@ use gloo::timers::future::TimeoutFuture;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::spawn_local;
use web_sys::{HtmlElement, HtmlTextAreaElement};
use yew::suspense::{Suspension, SuspensionResult};
use yew::suspense::{use_future, Suspension, SuspensionResult};
#[wasm_bindgen_test]
async fn suspense_works() {
@ -593,3 +593,44 @@ async fn effects_not_run_when_suspended() {
);
assert_eq!(*counter.borrow(), 4); // effects ran 4 times.
}
#[wasm_bindgen_test]
async fn use_suspending_future_works() {
#[function_component(Content)]
fn content() -> HtmlResult {
let _sleep_handle = use_future(|| async move {
TimeoutFuture::new(50).await;
})?;
Ok(html! {
<div>
{"Content"}
</div>
})
}
#[function_component(App)]
fn app() -> Html {
let fallback = html! {<div>{"wait..."}</div>};
html! {
<div id="result">
<Suspense {fallback}>
<Content />
</Suspense>
</div>
}
}
yew::Renderer::<App>::with_root(gloo_utils::document().get_element_by_id("output").unwrap())
.render();
TimeoutFuture::new(10).await;
let result = obtain_result();
assert_eq!(result.as_str(), "<div>wait...</div>");
TimeoutFuture::new(50).await;
let result = obtain_result();
assert_eq!(result.as_str(), r#"<div>Content</div>"#);
}