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