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 #[cfg(feature = "csr")]
18 use std::cell::RefCell;
19
20 use super::*;
21 #[cfg(feature = "csr")]
22 use crate::html::PendingRendered;
23 use crate::html::{Component, Context, Html, Scope};
24 use crate::suspense::Suspension;
25 #[cfg(feature = "hydration")]
26 use crate::suspense::SuspensionHandle;
27 use crate::virtual_dom::{VNode, VSuspense};
28 use crate::{component, html};
29
30 #[derive(Properties, PartialEq, Debug, Clone)]
31 pub(crate) struct BaseSuspenseProps {
32 pub children: Html,
33 #[prop_or(None)]
34 pub fallback: Option<Html>,
35 }
36
37 #[derive(Debug)]
38 pub(crate) enum BaseSuspenseMsg {
39 Suspend(Suspension),
40 Resume(Suspension),
41 }
42
43 pub(crate) struct BaseSuspense {
44 suspensions: Vec<Suspension>,
45 #[cfg(feature = "hydration")]
46 hydration_handle: Option<SuspensionHandle>,
47 #[cfg(feature = "csr")]
57 pending_rendered: RefCell<Vec<(usize, PendingRendered)>>,
58 }
59
60 impl std::fmt::Debug for BaseSuspense {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.debug_struct("BaseSuspense")
63 .field("suspensions", &self.suspensions)
64 .finish()
65 }
66 }
67
68 impl Component for BaseSuspense {
69 type Message = BaseSuspenseMsg;
70 type Properties = BaseSuspenseProps;
71
72 fn create(_ctx: &Context<Self>) -> Self {
73 #[cfg(not(feature = "hydration"))]
74 let suspensions = Vec::new();
75
76 #[cfg(feature = "hydration")]
78 let (suspensions, hydration_handle) = {
79 use crate::callback::Callback;
80 use crate::html::RenderMode;
81
82 match _ctx.creation_mode() {
83 RenderMode::Hydration => {
84 let link = _ctx.link().clone();
85 let (s, handle) = Suspension::new();
86 s.listen(Callback::from(move |s| {
87 link.send_message(BaseSuspenseMsg::Resume(s));
88 }));
89 (vec![s], Some(handle))
90 }
91 _ => (Vec::new(), None),
92 }
93 };
94
95 Self {
96 suspensions,
97 #[cfg(feature = "hydration")]
98 hydration_handle,
99 #[cfg(feature = "csr")]
100 pending_rendered: RefCell::new(Vec::new()),
101 }
102 }
103
104 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
105 match msg {
106 Self::Message::Suspend(m) => {
107 assert!(
108 ctx.props().fallback.is_some(),
109 "You cannot suspend from a component rendered as a fallback."
110 );
111
112 if m.resumed() {
113 return false;
114 }
115
116 if self.suspensions.iter().any(|n| n == &m) {
118 return false;
119 }
120
121 self.suspensions.push(m);
122
123 true
124 }
125 Self::Message::Resume(ref m) => {
126 let suspensions_len = self.suspensions.len();
127 self.suspensions.retain(|n| m != n);
128
129 suspensions_len != self.suspensions.len()
130 }
131 }
132 }
133
134 fn view(&self, ctx: &Context<Self>) -> Html {
135 let BaseSuspenseProps { children, fallback } = (*ctx.props()).clone();
136 let children = VNode::VList(::std::rc::Rc::new(
137 crate::virtual_dom::VList::with_children(vec![children], None),
138 ));
139
140 match fallback {
141 Some(fallback) => {
142 let vsuspense = VSuspense::new(
143 children,
144 fallback,
145 !self.suspensions.is_empty(),
146 None,
148 );
149
150 VNode::from(vsuspense)
151 }
152 None => children,
153 }
154 }
155
156 fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) {
157 #[cfg(not(feature = "hydration"))]
158 let _ = first_render;
159 #[cfg(feature = "hydration")]
160 if first_render {
161 if let Some(m) = self.hydration_handle.take() {
162 m.resume();
163 }
164 }
165 #[cfg(feature = "csr")]
169 if self.suspensions.is_empty() {
170 let pending = std::mem::take(&mut *self.pending_rendered.borrow_mut());
171 for (comp_id, p) in pending {
172 p.schedule(comp_id);
173 }
174 }
175 }
176 }
177
178 impl BaseSuspense {
179 pub(crate) fn suspend(scope: &Scope<Self>, s: Suspension) {
180 scope.send_message(BaseSuspenseMsg::Suspend(s));
181 }
182
183 pub(crate) fn resume(scope: &Scope<Self>, s: Suspension) {
184 scope.send_message(BaseSuspenseMsg::Resume(s));
185 }
186
187 #[cfg(feature = "csr")]
193 pub(crate) fn defer_rendered(
194 scope: &Scope<Self>,
195 comp_id: usize,
196 pending: PendingRendered,
197 ) {
198 let Some(comp) = scope.get_component() else {
199 return;
200 };
201 let mut q = comp.pending_rendered.borrow_mut();
202 if let Some(slot) = q.iter_mut().find(|(id, _)| *id == comp_id) {
203 slot.1.absorb(pending);
204 } else {
205 q.push((comp_id, pending));
206 }
207 }
208 }
209
210 #[component]
212 pub fn Suspense(props: &SuspenseProps) -> Html {
213 let SuspenseProps { children, fallback } = props.clone();
214
215 let fallback = html! {
216 <BaseSuspense>
217 {fallback}
218 </BaseSuspense>
219 };
220
221 html! {
222 <BaseSuspense {fallback}>
223 {children}
224 </BaseSuspense>
225 }
226 }
227}
228
229#[cfg(any(feature = "csr", feature = "ssr"))]
230pub use feat_csr_ssr::*;
231
232#[cfg(not(any(feature = "ssr", feature = "csr")))]
233mod feat_no_csr_ssr {
234 use super::*;
235 use crate::component;
236
237 #[component]
239 pub fn Suspense(_props: &SuspenseProps) -> Html {
240 Html::default()
241 }
242}
243
244#[cfg(not(any(feature = "ssr", feature = "csr")))]
245pub use feat_no_csr_ssr::*;