sycamore_web/
components.rs

1//! Definition for the [`NoSsr`] and [`NoHydrate`] components.
2
3use sycamore_macro::{component, view, Props};
4
5use crate::*;
6
7/// Props for [`Show`].
8#[derive(Props)]
9pub struct ShowProps {
10    #[prop(setter(into))]
11    pub when: MaybeDyn<bool>,
12    pub children: Children,
13}
14
15/// An utility component that only renders its children when a condition is satisfied.
16#[component]
17pub fn Show(props: ShowProps) -> View {
18    let mut children = props.children.call();
19    let when = create_selector(move || props.when.get());
20
21    if is_ssr!() {
22        View::from_dynamic(move || {
23            if when.get() {
24                std::mem::take(&mut children)
25            } else {
26                view! {}
27            }
28        })
29    } else {
30        View::from_dynamic(move || {
31            let cloned = utils::clone_nodes_via_web_sys(&children);
32
33            if when.get() {
34                // Do not set `children` to the document fragment since the document fragment will
35                // be emptied when it is appended.
36                children = utils::unwrap_from_document_fragment(cloned);
37
38                utils::clone_nodes_via_web_sys(&children)
39            } else {
40                // Wrap children inside a document fragment so that it can still be dynamically
41                // updated even though it is not mounted.
42                children = utils::wrap_in_document_fragment(cloned);
43                view! {}
44            }
45        })
46    }
47}
48
49/// Component that is only renders its children on the client side.
50///
51/// This is useful when wrapping parts of your app that are not intended to be server-side
52/// rendered, e.g. highly interactive components such as graphs, etc...
53#[component(inline_props)]
54pub fn NoSsr(children: Children) -> View {
55    if is_ssr!() {
56        view! { no-ssr() }
57    } else {
58        let marker = create_node_ref();
59        let view = view! { no-ssr(r#ref=marker) };
60        on_mount(move || {
61            let marker = marker.get();
62            let parent = marker.parent_node().unwrap();
63
64            let children = children.call();
65            for node in children.as_web_sys() {
66                parent.insert_before(&node, Some(&marker)).unwrap();
67            }
68            parent.remove_child(&marker).unwrap();
69        });
70        view
71    }
72}
73
74/// Components that do not need, or should not be hydrated on the client side.
75///
76/// This is useful when large parts of your app do not require client-side interactivity such as
77/// static content.
78///
79/// However, this component will still be rendered on the client side if it is created after the
80/// initial hydration phase is over, e.g. navigating to a new page with a `NoHydrate` component.
81#[component(inline_props)]
82pub fn NoHydrate(children: Children) -> View {
83    if is_ssr!() {
84        let is_hydrating = IS_HYDRATING.replace(false);
85        let children = children.call();
86        IS_HYDRATING.set(is_hydrating);
87        children
88    } else if IS_HYDRATING.get() {
89        view! {}
90    } else {
91        children.call()
92    }
93}
94
95/// Generate a script element for bootstrapping hydration.
96///
97/// In general, prefer using [`HydrationScript`] instead.
98pub fn generate_hydration_script(mode: SsrMode) -> &'static str {
99    match mode {
100        SsrMode::Sync => "",
101        SsrMode::Blocking => "window.__sycamore_ssr_mode='blocking'",
102        SsrMode::Streaming => "window.__sycamore_ssr_mode='streaming'",
103    }
104}
105
106/// Component that creates a script element for bootstrapping hydration. Should be rendered into
107/// the `<head>` of the document.
108///
109/// This component is required if using SSR in blocking or streaming mode.
110///
111/// TODO: use this component to also capture and replay events. This requires synthetic event
112/// delegation: <https://github.com/sycamore-rs/sycamore/issues/176>
113#[component]
114pub fn HydrationScript() -> View {
115    is_ssr! {
116        let mode = use_context::<SsrMode>();
117        let script = generate_hydration_script(mode);
118        view! {
119            NoHydrate {
120                script(dangerously_set_inner_html=script)
121            }
122        }
123    }
124    is_not_ssr! {
125        view! {}
126    }
127}