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

yew/virtual_dom/
vsuspense.rs

1use super::{Key, VNode};
2use crate::html::ImplicitClone;
3
4/// This struct represents a suspendable DOM fragment.
5#[derive(Clone, Debug, PartialEq)]
6pub struct VSuspense {
7    /// Child nodes.
8    pub(crate) children: VNode,
9    /// Fallback nodes when suspended.
10    pub(crate) fallback: VNode,
11    /// Whether the current status is suspended.
12    pub(crate) suspended: bool,
13    /// The Key.
14    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            // always render children on the server side.
53            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                // we use tokio spawn local here.
92                spawn_local(async move {
93                    // we use tokio sleep here.
94                    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}