yew/virtual_dom/
vsuspense.rs1use super::{Key, VNode};
2use crate::html::ImplicitClone;
3
4#[derive(Clone, ImplicitClone, Debug, PartialEq)]
6pub struct VSuspense {
7 pub(crate) children: VNode,
9 pub(crate) fallback: VNode,
11 pub(crate) suspended: bool,
13 pub(crate) key: Option<Key>,
15}
16
17impl VSuspense {
18 pub fn new(children: VNode, fallback: VNode, suspended: bool, key: Option<Key>) -> Self {
19 Self {
20 children,
21 fallback,
22 suspended,
23 key,
24 }
25 }
26}
27
28#[cfg(feature = "ssr")]
29mod feat_ssr {
30 use super::*;
31 use crate::feat_ssr::VTagKind;
32 use crate::html::AnyScope;
33 use crate::platform::fmt::BufWriter;
34 use crate::virtual_dom::Collectable;
35
36 impl VSuspense {
37 pub(crate) async fn render_into_stream(
38 &self,
39 w: &mut BufWriter,
40 parent_scope: &AnyScope,
41 hydratable: bool,
42 parent_vtag_kind: VTagKind,
43 ) {
44 let collectable = Collectable::Suspense;
45
46 if hydratable {
47 collectable.write_open_tag(w);
48 }
49
50 self.children
52 .render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
53 .await;
54
55 if hydratable {
56 collectable.write_close_tag(w);
57 }
58 }
59 }
60}
61
62#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
63#[cfg(feature = "ssr")]
64#[cfg(test)]
65mod ssr_tests {
66 use std::rc::Rc;
67 use std::time::Duration;
68
69 use tokio::task::{spawn_local, LocalSet};
70 use tokio::test;
71
72 use crate::platform::time::sleep;
73 use crate::prelude::*;
74 use crate::suspense::{Suspension, SuspensionResult};
75 use crate::ServerRenderer;
76
77 #[cfg(not(target_os = "wasi"))]
78 #[test(flavor = "multi_thread", worker_threads = 2)]
79 async fn test_suspense() {
80 #[derive(PartialEq)]
81 pub struct SleepState {
82 s: Suspension,
83 }
84
85 impl SleepState {
86 fn new() -> Self {
87 let (s, handle) = Suspension::new();
88
89 spawn_local(async move {
91 sleep(Duration::from_millis(50)).await;
93
94 handle.resume();
95 });
96
97 Self { s }
98 }
99 }
100
101 impl Reducible for SleepState {
102 type Action = ();
103
104 fn reduce(self: Rc<Self>, _action: Self::Action) -> Rc<Self> {
105 Self::new().into()
106 }
107 }
108
109 #[hook]
110 pub fn use_sleep() -> SuspensionResult<Rc<dyn Fn()>> {
111 let sleep_state = use_reducer(SleepState::new);
112
113 if sleep_state.s.resumed() {
114 Ok(Rc::new(move || sleep_state.dispatch(())))
115 } else {
116 Err(sleep_state.s.clone())
117 }
118 }
119
120 #[derive(PartialEq, Properties, Debug)]
121 struct ChildProps {
122 name: String,
123 }
124
125 #[component]
126 fn Child(props: &ChildProps) -> HtmlResult {
127 use_sleep()?;
128 Ok(html! { <div>{"Hello, "}{&props.name}{"!"}</div> })
129 }
130
131 #[component]
132 fn Comp() -> Html {
133 let fallback = html! {"loading..."};
134
135 html! {
136 <Suspense {fallback}>
137 <Child name="Jane" />
138 <Child name="John" />
139 <Child name="Josh" />
140 </Suspense>
141 }
142 }
143
144 let local = LocalSet::new();
145
146 let s = local
147 .run_until(async move {
148 ServerRenderer::<Comp>::new()
149 .hydratable(false)
150 .render()
151 .await
152 })
153 .await;
154
155 assert_eq!(
156 s,
157 "<div>Hello, Jane!</div><div>Hello, John!</div><div>Hello, Josh!</div>"
158 );
159 }
160}