yew/suspense/
component.rs1use crate::html::{Html, Properties};
2
3#[derive(Properties, PartialEq, Debug, Clone)]
5pub struct SuspenseProps {
6 #[prop_or_default]
8 pub children: Html,
9
10 #[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 #[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 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 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 #[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 #[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::*;