From 0801c709e657873bb5a728d1191fbcb3b8bae4fa Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Thu, 14 Apr 2022 01:45:22 +0500 Subject: [PATCH 1/5] Add `use_suspending_future` hook to make consuming futures as suspense easier --- packages/yew/src/suspense/hooks.rs | 36 ++++++++++++++++++++++++++++++ packages/yew/src/suspense/mod.rs | 2 ++ 2 files changed, 38 insertions(+) create mode 100644 packages/yew/src/suspense/hooks.rs diff --git a/packages/yew/src/suspense/hooks.rs b/packages/yew/src/suspense/hooks.rs new file mode 100644 index 00000000000..01265154736 --- /dev/null +++ b/packages/yew/src/suspense/hooks.rs @@ -0,0 +1,36 @@ +#[cfg_attr(documenting, doc(cfg(any(target_arch = "wasm32", feature = "tokio"))))] +#[cfg(any(target_arch = "wasm32", feature = "tokio"))] +mod feat_futures { + use std::future::Future; + + 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. + #[hook] + pub fn use_suspending_future(f: F) -> SuspensionResult + where + T: Clone + 'static, + F: Future + 'static, + { + let output = use_state(|| None); + + let suspension = { + let output = output.clone(); + + use_state(move || Suspension::from_future(async move { output.set(Some(f.await)) })) + }; + + if suspension.resumed() { + Ok((*output).clone().unwrap()) + } else { + Err((*suspension).clone()) + } + } +} + +#[cfg(any(target_arch = "wasm32", feature = "tokio"))] +pub use feat_futures::*; diff --git a/packages/yew/src/suspense/mod.rs b/packages/yew/src/suspense/mod.rs index aec9ac0c93d..a377be6f16b 100644 --- a/packages/yew/src/suspense/mod.rs +++ b/packages/yew/src/suspense/mod.rs @@ -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}; From 39c98635094387d22fc4cce862920da420811b00 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Thu, 14 Apr 2022 02:00:06 +0500 Subject: [PATCH 2/5] Add test --- packages/yew/tests/suspense.rs | 48 +++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/yew/tests/suspense.rs b/packages/yew/tests/suspense.rs index 62fe40f9e36..5265c9e8cae 100644 --- a/packages/yew/tests/suspense.rs +++ b/packages/yew/tests/suspense.rs @@ -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::{Suspension, SuspensionResult, use_suspending_future}; #[wasm_bindgen_test] async fn suspense_works() { @@ -593,3 +593,49 @@ 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_suspending_future(async move { + TimeoutFuture::new(50).await; + })?; + + Ok(html! { +
+ {"Content"} +
+ }) + } + + #[function_component(App)] + fn app() -> Html { + let fallback = html! {
{"wait..."}
}; + + html! { +
+ + + +
+ } + } + + yew::Renderer::::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(), "
wait...
"); + + TimeoutFuture::new(50).await; + + let result = obtain_result(); + assert_eq!( + result.as_str(), + r#"
Content
"# + ); +} From d3c76e199c1bb564ba17d6966a4061cfa07ed512 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Thu, 14 Apr 2022 02:01:46 +0500 Subject: [PATCH 3/5] fmt --- packages/yew/src/suspense/hooks.rs | 6 +++--- packages/yew/tests/suspense.rs | 9 ++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/yew/src/suspense/hooks.rs b/packages/yew/src/suspense/hooks.rs index 01265154736..fbe1194f942 100644 --- a/packages/yew/src/suspense/hooks.rs +++ b/packages/yew/src/suspense/hooks.rs @@ -12,9 +12,9 @@ mod feat_futures { /// is the output of the suspension. #[hook] pub fn use_suspending_future(f: F) -> SuspensionResult - where - T: Clone + 'static, - F: Future + 'static, + where + T: Clone + 'static, + F: Future + 'static, { let output = use_state(|| None); diff --git a/packages/yew/tests/suspense.rs b/packages/yew/tests/suspense.rs index 5265c9e8cae..cc24856e86b 100644 --- a/packages/yew/tests/suspense.rs +++ b/packages/yew/tests/suspense.rs @@ -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_suspending_future}; +use yew::suspense::{use_suspending_future, Suspension, SuspensionResult}; #[wasm_bindgen_test] async fn suspense_works() { @@ -594,10 +594,8 @@ 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_suspending_future(async move { @@ -634,8 +632,5 @@ async fn use_suspending_future_works() { TimeoutFuture::new(50).await; let result = obtain_result(); - assert_eq!( - result.as_str(), - r#"
Content
"# - ); + assert_eq!(result.as_str(), r#"
Content
"#); } From 5fec29a863b844f5c066d3e95569d3d01bd748a2 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Thu, 14 Apr 2022 02:29:22 +0500 Subject: [PATCH 4/5] use_suspending_future -> use_future --- packages/yew/src/suspense/hooks.rs | 2 +- packages/yew/tests/suspense.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/yew/src/suspense/hooks.rs b/packages/yew/src/suspense/hooks.rs index fbe1194f942..5c52f0b3fcd 100644 --- a/packages/yew/src/suspense/hooks.rs +++ b/packages/yew/src/suspense/hooks.rs @@ -11,7 +11,7 @@ mod feat_futures { /// A [Suspension] is created from the passed future and the result of the future /// is the output of the suspension. #[hook] - pub fn use_suspending_future(f: F) -> SuspensionResult + pub fn use_future(f: F) -> SuspensionResult where T: Clone + 'static, F: Future + 'static, diff --git a/packages/yew/tests/suspense.rs b/packages/yew/tests/suspense.rs index cc24856e86b..e6a324dcfdb 100644 --- a/packages/yew/tests/suspense.rs +++ b/packages/yew/tests/suspense.rs @@ -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::{use_suspending_future, Suspension, SuspensionResult}; +use yew::suspense::{use_future, Suspension, SuspensionResult}; #[wasm_bindgen_test] async fn suspense_works() { @@ -598,7 +598,7 @@ async fn effects_not_run_when_suspended() { async fn use_suspending_future_works() { #[function_component(Content)] fn content() -> HtmlResult { - let _sleep_handle = use_suspending_future(async move { + let _sleep_handle = use_future(async move { TimeoutFuture::new(50).await; })?; From 48eb65fb1176948bf5946c29a7028881100f8634 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Thu, 14 Apr 2022 22:03:32 +0500 Subject: [PATCH 5/5] use_future takes a closure --- packages/yew/src/suspense/hooks.rs | 40 ++++++++++++++++++++++++++---- packages/yew/tests/suspense.rs | 2 +- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/packages/yew/src/suspense/hooks.rs b/packages/yew/src/suspense/hooks.rs index 5c52f0b3fcd..2240a0e21f3 100644 --- a/packages/yew/src/suspense/hooks.rs +++ b/packages/yew/src/suspense/hooks.rs @@ -1,7 +1,9 @@ #[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}; @@ -10,22 +12,50 @@ mod feat_futures { /// /// A [Suspension] is created from the passed future and the result of the future /// is the output of the suspension. + pub struct UseFutureHandle { + inner: UseStateHandle>, + } + + impl Deref for UseFutureHandle { + type Target = O; + + fn deref(&self) -> &Self::Target { + &*self.inner.as_ref().unwrap() + } + } + + impl fmt::Debug for UseFutureHandle { + 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: F) -> SuspensionResult + pub fn use_future(f: F) -> SuspensionResult> where - T: Clone + 'static, - F: Future + 'static, + F: FnOnce() -> T + 'static, + T: Future + 'static, + O: 'static, { let output = use_state(|| None); let suspension = { let output = output.clone(); - use_state(move || Suspension::from_future(async move { output.set(Some(f.await)) })) + use_memo( + move |_| { + Suspension::from_future(async move { + output.set(Some(f().await)); + }) + }, + (), + ) }; if suspension.resumed() { - Ok((*output).clone().unwrap()) + Ok(UseFutureHandle { inner: output }) } else { Err((*suspension).clone()) } diff --git a/packages/yew/tests/suspense.rs b/packages/yew/tests/suspense.rs index e6a324dcfdb..976f0270bf2 100644 --- a/packages/yew/tests/suspense.rs +++ b/packages/yew/tests/suspense.rs @@ -598,7 +598,7 @@ async fn effects_not_run_when_suspended() { async fn use_suspending_future_works() { #[function_component(Content)] fn content() -> HtmlResult { - let _sleep_handle = use_future(async move { + let _sleep_handle = use_future(|| async move { TimeoutFuture::new(50).await; })?;