diff --git a/packages/yew/src/dom_bundle/bsuspense.rs b/packages/yew/src/dom_bundle/bsuspense.rs index 55d4767a8fd..18d2a2b152f 100644 --- a/packages/yew/src/dom_bundle/bsuspense.rs +++ b/packages/yew/src/dom_bundle/bsuspense.rs @@ -4,6 +4,7 @@ use super::{BNode, Reconcilable, ReconcileTarget}; use crate::html::AnyScope; use crate::virtual_dom::{Key, VSuspense}; use crate::NodeRef; +use gloo::utils::document; use web_sys::Element; /// The bundle implementation to [VSuspense] @@ -56,11 +57,12 @@ impl Reconcilable for VSuspense { let VSuspense { children, fallback, - detached_parent, suspended, key, } = self; - let detached_parent = detached_parent.expect("no detached parent?"); + let detached_parent = document() + .create_element("div") + .expect("failed to create detached element"); // When it's suspended, we render children into an element that is detached from the dom // tree while rendering fallback UI into the original place where children resides in. @@ -100,10 +102,7 @@ impl Reconcilable for VSuspense { ) -> NodeRef { match bundle { // We only preserve the child state if they are the same suspense. - BNode::Suspense(m) - if m.key == self.key - && self.detached_parent.as_ref() == Some(&m.detached_parent) => - { + BNode::Suspense(m) if m.key == self.key => { self.reconcile(parent_scope, parent, next_sibling, m) } _ => self.replace(parent_scope, parent, next_sibling, bundle), @@ -120,11 +119,9 @@ impl Reconcilable for VSuspense { let VSuspense { children, fallback, - detached_parent, suspended, key: _, } = self; - let detached_parent = detached_parent.expect("no detached parent?"); let children_bundle = &mut suspense.children_bundle; // no need to update key & detached_parent @@ -136,7 +133,7 @@ impl Reconcilable for VSuspense { (true, Some(fallback_bundle)) => { children.reconcile_node( parent_scope, - &detached_parent, + &suspense.detached_parent, NodeRef::default(), children_bundle, ); @@ -149,11 +146,11 @@ impl Reconcilable for VSuspense { } // Freshly suspended. Shift children into the detached parent, then add fallback to the DOM (true, None) => { - children_bundle.shift(&detached_parent, NodeRef::default()); + children_bundle.shift(&suspense.detached_parent, NodeRef::default()); children.reconcile_node( parent_scope, - &detached_parent, + &suspense.detached_parent, NodeRef::default(), children_bundle, ); diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 245e1c55d21..fef2d3086e2 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -4,7 +4,7 @@ use super::scope::{AnyScope, Scope}; use super::BaseComponent; use crate::html::{Html, RenderError}; use crate::scheduler::{self, Runnable, Shared}; -use crate::suspense::{Suspense, Suspension}; +use crate::suspense::{BaseSuspense, Suspension}; use crate::{Callback, Context, HtmlResult}; use std::any::Any; use std::rc::Rc; @@ -387,7 +387,7 @@ impl RenderRunner { let comp_scope = state.inner.any_scope(); let suspense_scope = comp_scope - .find_parent_scope::() + .find_parent_scope::() .expect("To suspend rendering, a component is required."); let suspense = suspense_scope.get_component().unwrap(); @@ -419,7 +419,7 @@ impl RenderRunner { if let Some(m) = state.suspension.take() { let comp_scope = state.inner.any_scope(); - let suspense_scope = comp_scope.find_parent_scope::().unwrap(); + let suspense_scope = comp_scope.find_parent_scope::().unwrap(); let suspense = suspense_scope.get_component().unwrap(); suspense.resume(m); diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 22d47159c92..0cbbd3e1209 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -239,7 +239,7 @@ mod feat_ssr { } #[cfg(not(any(feature = "ssr", feature = "csr")))] -mod feat_no_render_ssr { +mod feat_no_csr_ssr { use super::*; // Skeleton code to provide public methods when no renderer are enabled. diff --git a/packages/yew/src/suspense/component.rs b/packages/yew/src/suspense/component.rs index e00897224dd..4bcdbe43664 100644 --- a/packages/yew/src/suspense/component.rs +++ b/packages/yew/src/suspense/component.rs @@ -1,9 +1,4 @@ -use crate::html::{Children, Component, Context, Html, Properties, Scope}; -use crate::virtual_dom::{Key, VList, VNode, VSuspense}; - -use web_sys::Element; - -use super::Suspension; +use crate::html::{Children, Html, Properties}; #[derive(Properties, PartialEq, Debug, Clone)] pub struct SuspenseProps { @@ -12,95 +7,139 @@ pub struct SuspenseProps { #[prop_or_default] pub fallback: Html, - - #[prop_or_default] - pub key: Option, } -#[derive(Debug)] -pub enum SuspenseMsg { - Suspend(Suspension), - Resume(Suspension), -} +#[cfg(any(feature = "csr", feature = "ssr"))] +mod feat_csr_ssr { + use super::*; -/// Suspend rendering and show a fallback UI until the underlying task completes. -#[derive(Debug)] -pub struct Suspense { - link: Scope, - suspensions: Vec, - detached_parent: Option, -} + use crate::html::{Children, Component, Context, Html, Scope}; + use crate::suspense::Suspension; + use crate::virtual_dom::{VNode, VSuspense}; + use crate::{function_component, html}; + + #[derive(Properties, PartialEq, Debug, Clone)] + pub(crate) struct BaseSuspenseProps { + pub children: Children, -impl Component for Suspense { - type Properties = SuspenseProps; - type Message = SuspenseMsg; + pub fallback: Option, + } + + #[derive(Debug)] + pub(crate) enum BaseSuspenseMsg { + Suspend(Suspension), + Resume(Suspension), + } - fn create(ctx: &Context) -> Self { - Self { - link: ctx.link().clone(), - suspensions: Vec::new(), + #[derive(Debug)] + pub(crate) struct BaseSuspense { + link: Scope, + suspensions: Vec, + } - #[cfg(target_arch = "wasm32")] - detached_parent: web_sys::window() - .and_then(|m| m.document()) - .and_then(|m| m.create_element("div").ok()), + impl Component for BaseSuspense { + type Properties = BaseSuspenseProps; + type Message = BaseSuspenseMsg; - #[cfg(not(target_arch = "wasm32"))] - detached_parent: None, + fn create(ctx: &Context) -> Self { + Self { + link: ctx.link().clone(), + suspensions: Vec::new(), + } } - } - fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { - match msg { - Self::Message::Suspend(m) => { - if m.resumed() { - return false; - } + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { + match msg { + Self::Message::Suspend(m) => { + assert!( + ctx.props().fallback.is_some(), + "You cannot suspend from a component rendered as a fallback." + ); + + if m.resumed() { + return false; + } - m.listen(self.link.callback(Self::Message::Resume)); + m.listen(self.link.callback(Self::Message::Resume)); - self.suspensions.push(m); + self.suspensions.push(m); - true + true + } + Self::Message::Resume(ref m) => { + let suspensions_len = self.suspensions.len(); + self.suspensions.retain(|n| m != n); + + suspensions_len != self.suspensions.len() + } } - Self::Message::Resume(ref m) => { - let suspensions_len = self.suspensions.len(); - self.suspensions.retain(|n| m != n); + } - suspensions_len != self.suspensions.len() + fn view(&self, ctx: &Context) -> Html { + let BaseSuspenseProps { children, fallback } = (*ctx.props()).clone(); + let children = html! {<>{children}}; + + match fallback { + Some(fallback) => { + let vsuspense = VSuspense::new( + children, + fallback, + !self.suspensions.is_empty(), + // We don't need to key this as the key will be applied to the component. + None, + ); + + VNode::from(vsuspense) + } + None => children, } } } - fn view(&self, ctx: &Context) -> Html { - let SuspenseProps { - children, - fallback: fallback_vnode, - key, - } = (*ctx.props()).clone(); - - let children_vnode = - VNode::from(VList::with_children(children.into_iter().collect(), None)); - - let vsuspense = VSuspense::new( - children_vnode, - fallback_vnode, - self.detached_parent.clone(), - !self.suspensions.is_empty(), - key, - ); - - VNode::from(vsuspense) + impl BaseSuspense { + pub(crate) fn suspend(&self, s: Suspension) { + self.link.send_message(BaseSuspenseMsg::Suspend(s)); + } + + pub(crate) fn resume(&self, s: Suspension) { + self.link.send_message(BaseSuspenseMsg::Resume(s)); + } + } + + /// Suspend rendering and show a fallback UI until the underlying task completes. + #[function_component] + pub fn Suspense(props: &SuspenseProps) -> Html { + let SuspenseProps { children, fallback } = props.clone(); + + let fallback = html! { + + {fallback} + + }; + + html! { + + {children} + + } } } #[cfg(any(feature = "csr", feature = "ssr"))] -impl Suspense { - pub(crate) fn suspend(&self, s: Suspension) { - self.link.send_message(SuspenseMsg::Suspend(s)); - } +pub use feat_csr_ssr::*; - pub(crate) fn resume(&self, s: Suspension) { - self.link.send_message(SuspenseMsg::Resume(s)); +#[cfg(not(any(feature = "ssr", feature = "csr")))] +mod feat_no_csr_ssr { + use super::*; + + use crate::function_component; + + /// Suspend rendering and show a fallback UI until the underlying task completes. + #[function_component] + pub fn Suspense(_props: &SuspenseProps) -> Html { + Html::default() } } + +#[cfg(not(any(feature = "ssr", feature = "csr")))] +pub use feat_no_csr_ssr::*; diff --git a/packages/yew/src/suspense/mod.rs b/packages/yew/src/suspense/mod.rs index 617c263775f..aec9ac0c93d 100644 --- a/packages/yew/src/suspense/mod.rs +++ b/packages/yew/src/suspense/mod.rs @@ -3,5 +3,7 @@ mod component; mod suspension; +#[cfg(any(feature = "csr", feature = "ssr"))] +pub(crate) use component::BaseSuspense; pub use component::Suspense; pub use suspension::{Suspension, SuspensionHandle, SuspensionResult}; diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index 690b6b94bee..e9448c5aa91 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -1,5 +1,4 @@ use super::{Key, VNode}; -use web_sys::Element; /// This struct represents a suspendable DOM fragment. #[derive(Clone, Debug, PartialEq)] @@ -8,8 +7,6 @@ pub struct VSuspense { pub(crate) children: Box, /// Fallback nodes when suspended. pub(crate) fallback: Box, - /// The element to attach to when children is not attached to DOM - pub(crate) detached_parent: Option, /// Whether the current status is suspended. pub(crate) suspended: bool, /// The Key. @@ -17,17 +14,10 @@ pub struct VSuspense { } impl VSuspense { - pub(crate) fn new( - children: VNode, - fallback: VNode, - detached_parent: Option, - suspended: bool, - key: Option, - ) -> Self { + pub fn new(children: VNode, fallback: VNode, suspended: bool, key: Option) -> Self { Self { children: children.into(), fallback: fallback.into(), - detached_parent, suspended, key, }