mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Fix panic because of unwrapping when resuming suspension (#2902)
* Replace unwrap with if let to prevent panic * Add resume after unmount test * Change BaseSuspense methods to associated functions * Fix rustfmt issues
This commit is contained in:
parent
ee92b6d305
commit
32b3150cb3
@ -303,9 +303,7 @@ impl ComponentState {
|
|||||||
let comp_scope = self.inner.any_scope();
|
let comp_scope = self.inner.any_scope();
|
||||||
|
|
||||||
let suspense_scope = comp_scope.find_parent_scope::<BaseSuspense>().unwrap();
|
let suspense_scope = comp_scope.find_parent_scope::<BaseSuspense>().unwrap();
|
||||||
let suspense = suspense_scope.get_component().unwrap();
|
BaseSuspense::resume(&suspense_scope, m);
|
||||||
|
|
||||||
suspense.resume(m);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -462,7 +460,6 @@ impl ComponentState {
|
|||||||
let suspense_scope = comp_scope
|
let suspense_scope = comp_scope
|
||||||
.find_parent_scope::<BaseSuspense>()
|
.find_parent_scope::<BaseSuspense>()
|
||||||
.expect("To suspend rendering, a <Suspense /> component is required.");
|
.expect("To suspend rendering, a <Suspense /> component is required.");
|
||||||
let suspense = suspense_scope.get_component().unwrap();
|
|
||||||
|
|
||||||
let comp_id = self.comp_id;
|
let comp_id = self.comp_id;
|
||||||
let shared_state = shared_state.clone();
|
let shared_state = shared_state.clone();
|
||||||
@ -479,12 +476,12 @@ impl ComponentState {
|
|||||||
if let Some(ref last_suspension) = self.suspension {
|
if let Some(ref last_suspension) = self.suspension {
|
||||||
if &suspension != last_suspension {
|
if &suspension != last_suspension {
|
||||||
// We remove previous suspension from the suspense.
|
// We remove previous suspension from the suspense.
|
||||||
suspense.resume(last_suspension.clone());
|
BaseSuspense::resume(&suspense_scope, last_suspension.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.suspension = Some(suspension.clone());
|
self.suspension = Some(suspension.clone());
|
||||||
|
|
||||||
suspense.suspend(suspension);
|
BaseSuspense::suspend(&suspense_scope, suspension);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,6 @@ mod feat_csr_ssr {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct BaseSuspense {
|
pub(crate) struct BaseSuspense {
|
||||||
link: Scope<Self>,
|
|
||||||
suspensions: Vec<Suspension>,
|
suspensions: Vec<Suspension>,
|
||||||
#[cfg(feature = "hydration")]
|
#[cfg(feature = "hydration")]
|
||||||
hydration_handle: Option<SuspensionHandle>,
|
hydration_handle: Option<SuspensionHandle>,
|
||||||
@ -46,7 +45,7 @@ mod feat_csr_ssr {
|
|||||||
type Message = BaseSuspenseMsg;
|
type Message = BaseSuspenseMsg;
|
||||||
type Properties = BaseSuspenseProps;
|
type Properties = BaseSuspenseProps;
|
||||||
|
|
||||||
fn create(ctx: &Context<Self>) -> Self {
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
#[cfg(not(feature = "hydration"))]
|
#[cfg(not(feature = "hydration"))]
|
||||||
let suspensions = Vec::new();
|
let suspensions = Vec::new();
|
||||||
|
|
||||||
@ -56,9 +55,9 @@ mod feat_csr_ssr {
|
|||||||
use crate::callback::Callback;
|
use crate::callback::Callback;
|
||||||
use crate::html::RenderMode;
|
use crate::html::RenderMode;
|
||||||
|
|
||||||
match ctx.creation_mode() {
|
match _ctx.creation_mode() {
|
||||||
RenderMode::Hydration => {
|
RenderMode::Hydration => {
|
||||||
let link = ctx.link().clone();
|
let link = _ctx.link().clone();
|
||||||
let (s, handle) = Suspension::new();
|
let (s, handle) = Suspension::new();
|
||||||
s.listen(Callback::from(move |s| {
|
s.listen(Callback::from(move |s| {
|
||||||
link.send_message(BaseSuspenseMsg::Resume(s));
|
link.send_message(BaseSuspenseMsg::Resume(s));
|
||||||
@ -70,7 +69,6 @@ mod feat_csr_ssr {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
link: ctx.link().clone(),
|
|
||||||
suspensions,
|
suspensions,
|
||||||
#[cfg(feature = "hydration")]
|
#[cfg(feature = "hydration")]
|
||||||
hydration_handle,
|
hydration_handle,
|
||||||
@ -133,12 +131,12 @@ mod feat_csr_ssr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BaseSuspense {
|
impl BaseSuspense {
|
||||||
pub(crate) fn suspend(&self, s: Suspension) {
|
pub(crate) fn suspend(scope: &Scope<Self>, s: Suspension) {
|
||||||
self.link.send_message(BaseSuspenseMsg::Suspend(s));
|
scope.send_message(BaseSuspenseMsg::Suspend(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn resume(&self, s: Suspension) {
|
pub(crate) fn resume(scope: &Scope<Self>, s: Suspension) {
|
||||||
self.link.send_message(BaseSuspenseMsg::Resume(s));
|
scope.send_message(BaseSuspenseMsg::Resume(s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ use yew::platform::spawn_local;
|
|||||||
use yew::platform::time::sleep;
|
use yew::platform::time::sleep;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew::suspense::{use_future, use_future_with_deps, Suspension, SuspensionResult};
|
use yew::suspense::{use_future, use_future_with_deps, Suspension, SuspensionResult};
|
||||||
|
use yew::UseStateHandle;
|
||||||
|
|
||||||
wasm_bindgen_test_configure!(run_in_browser);
|
wasm_bindgen_test_configure!(run_in_browser);
|
||||||
|
|
||||||
@ -734,3 +735,54 @@ async fn test_suspend_forever() {
|
|||||||
let result = obtain_result();
|
let result = obtain_result();
|
||||||
assert_eq!(result.as_str(), r#"OK"#);
|
assert_eq!(result.as_str(), r#"OK"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
async fn resume_after_unmount() {
|
||||||
|
#[derive(Clone, Properties, PartialEq)]
|
||||||
|
struct ContentProps {
|
||||||
|
state: UseStateHandle<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(Content)]
|
||||||
|
fn content(ContentProps { state }: &ContentProps) -> HtmlResult {
|
||||||
|
let state = state.clone();
|
||||||
|
let _sleep_handle = use_future(|| async move {
|
||||||
|
sleep(Duration::from_millis(50)).await;
|
||||||
|
state.set(false);
|
||||||
|
sleep(Duration::from_millis(50)).await;
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(html! {
|
||||||
|
<div>{"Content"}</div>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(App)]
|
||||||
|
fn app() -> Html {
|
||||||
|
let fallback = html! {<div>{"wait..."}</div>};
|
||||||
|
let state = use_state(|| true);
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div id="result">
|
||||||
|
if *state {
|
||||||
|
<Suspense {fallback}>
|
||||||
|
<Content {state} />
|
||||||
|
</Suspense>
|
||||||
|
} else {
|
||||||
|
<div>{"Content replacement"}</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yew::Renderer::<App>::with_root(gloo::utils::document().get_element_by_id("output").unwrap())
|
||||||
|
.render();
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(25)).await;
|
||||||
|
let result = obtain_result();
|
||||||
|
assert_eq!(result.as_str(), "<div>wait...</div>");
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(50)).await;
|
||||||
|
let result = obtain_result();
|
||||||
|
assert_eq!(result.as_str(), "<div>Content replacement</div>");
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user