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, ImplicitClone, 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 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            // always render children on the server side.
51            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                // we use tokio spawn local here.
90                spawn_local(async move {
91                    // we use tokio sleep here.
92                    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}