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

yew/suspense/
component.rs

1use crate::html::{Html, Properties};
2
3/// Properties for [Suspense].
4#[derive(Properties, PartialEq, Debug, Clone)]
5pub struct SuspenseProps {
6    /// The Children of the current Suspense Component.
7    #[prop_or_default]
8    pub children: Html,
9
10    /// The Fallback UI of the current Suspense Component.
11    #[prop_or_default]
12    pub fallback: Html,
13}
14
15#[cfg(any(feature = "csr", feature = "ssr"))]
16mod feat_csr_ssr {
17    use super::*;
18    use crate::html::{Component, Context, Html, Scope};
19    use crate::suspense::Suspension;
20    #[cfg(feature = "hydration")]
21    use crate::suspense::SuspensionHandle;
22    use crate::virtual_dom::{VNode, VSuspense};
23    use crate::{function_component, html};
24
25    #[derive(Properties, PartialEq, Debug, Clone)]
26    pub(crate) struct BaseSuspenseProps {
27        pub children: Html,
28        #[prop_or(None)]
29        pub fallback: Option<Html>,
30    }
31
32    #[derive(Debug)]
33    pub(crate) enum BaseSuspenseMsg {
34        Suspend(Suspension),
35        Resume(Suspension),
36    }
37
38    #[derive(Debug)]
39    pub(crate) struct BaseSuspense {
40        suspensions: Vec<Suspension>,
41        #[cfg(feature = "hydration")]
42        hydration_handle: Option<SuspensionHandle>,
43    }
44
45    impl Component for BaseSuspense {
46        type Message = BaseSuspenseMsg;
47        type Properties = BaseSuspenseProps;
48
49        fn create(_ctx: &Context<Self>) -> Self {
50            #[cfg(not(feature = "hydration"))]
51            let suspensions = Vec::new();
52
53            // We create a suspension to block suspense until its rendered method is notified.
54            #[cfg(feature = "hydration")]
55            let (suspensions, hydration_handle) = {
56                use crate::callback::Callback;
57                use crate::html::RenderMode;
58
59                match _ctx.creation_mode() {
60                    RenderMode::Hydration => {
61                        let link = _ctx.link().clone();
62                        let (s, handle) = Suspension::new();
63                        s.listen(Callback::from(move |s| {
64                            link.send_message(BaseSuspenseMsg::Resume(s));
65                        }));
66                        (vec![s], Some(handle))
67                    }
68                    _ => (Vec::new(), None),
69                }
70            };
71
72            Self {
73                suspensions,
74                #[cfg(feature = "hydration")]
75                hydration_handle,
76            }
77        }
78
79        fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
80            match msg {
81                Self::Message::Suspend(m) => {
82                    assert!(
83                        ctx.props().fallback.is_some(),
84                        "You cannot suspend from a component rendered as a fallback."
85                    );
86
87                    if m.resumed() {
88                        return false;
89                    }
90
91                    // If a suspension already exists, ignore it.
92                    if self.suspensions.iter().any(|n| n == &m) {
93                        return false;
94                    }
95
96                    self.suspensions.push(m);
97
98                    true
99                }
100                Self::Message::Resume(ref m) => {
101                    let suspensions_len = self.suspensions.len();
102                    self.suspensions.retain(|n| m != n);
103
104                    suspensions_len != self.suspensions.len()
105                }
106            }
107        }
108
109        fn view(&self, ctx: &Context<Self>) -> Html {
110            let BaseSuspenseProps { children, fallback } = (*ctx.props()).clone();
111            let children = html! {<>{children}</>};
112
113            match fallback {
114                Some(fallback) => {
115                    let vsuspense = VSuspense::new(
116                        children,
117                        fallback,
118                        !self.suspensions.is_empty(),
119                        // We don't need to key this as the key will be applied to the component.
120                        None,
121                    );
122
123                    VNode::from(vsuspense)
124                }
125                None => children,
126            }
127        }
128
129        #[cfg(feature = "hydration")]
130        fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) {
131            if first_render {
132                if let Some(m) = self.hydration_handle.take() {
133                    m.resume();
134                }
135            }
136        }
137    }
138
139    impl BaseSuspense {
140        pub(crate) fn suspend(scope: &Scope<Self>, s: Suspension) {
141            scope.send_message(BaseSuspenseMsg::Suspend(s));
142        }
143
144        pub(crate) fn resume(scope: &Scope<Self>, s: Suspension) {
145            scope.send_message(BaseSuspenseMsg::Resume(s));
146        }
147    }
148
149    /// Suspend rendering and show a fallback UI until the underlying task completes.
150    #[function_component]
151    pub fn Suspense(props: &SuspenseProps) -> Html {
152        let SuspenseProps { children, fallback } = props.clone();
153
154        let fallback = html! {
155            <BaseSuspense>
156                {fallback}
157            </BaseSuspense>
158        };
159
160        html! {
161            <BaseSuspense {fallback}>
162                {children}
163            </BaseSuspense>
164        }
165    }
166}
167
168#[cfg(any(feature = "csr", feature = "ssr"))]
169pub use feat_csr_ssr::*;
170
171#[cfg(not(any(feature = "ssr", feature = "csr")))]
172mod feat_no_csr_ssr {
173    use super::*;
174    use crate::function_component;
175
176    /// Suspend rendering and show a fallback UI until the underlying task completes.
177    #[function_component]
178    pub fn Suspense(_props: &SuspenseProps) -> Html {
179        Html::default()
180    }
181}
182
183#[cfg(not(any(feature = "ssr", feature = "csr")))]
184pub use feat_no_csr_ssr::*;