sycamore_web/node/
dom_render.rs

1use super::*;
2
3/// Render a [`View`] into the DOM.
4/// Alias for [`render_to`] with `parent` being the `<body>` tag.
5pub fn render(view: impl FnOnce() -> View) {
6    render_to(view, &document().body().unwrap());
7}
8
9/// Render a [`View`] under a `parent` node.
10/// For rendering under the `<body>` tag, use [`render`] instead.
11pub fn render_to(view: impl FnOnce() -> View, parent: &web_sys::Node) {
12    // Do not call the destructor function, effectively leaking the scope.
13    let _ = create_root(|| render_in_scope(view, parent));
14}
15
16/// Render a [`View`] under a `parent` node, in a way that can be cleaned up.
17///
18/// This function is intended to be used for injecting an ephemeral sycamore view into a
19/// non-sycamore app (for example, a file upload modal where you want to cancel the upload if the
20/// modal is closed).
21///
22/// It is, however, preferable to have a single call to [`render`] or [`render_to`] at the top
23/// level of your app long-term. For rendering a view that will never be unmounted from the dom,
24/// use [`render_to`] instead. For rendering under the `<body>` tag, use [`render`] instead.
25///
26/// It is expected that this function will be called inside a reactive root, usually created using
27/// [`create_root`].
28pub fn render_in_scope(view: impl FnOnce() -> View, parent: &web_sys::Node) {
29    if is_ssr!() {
30        panic!("`render_in_scope` is not available in SSR mode");
31    } else {
32        IS_HYDRATING.set(false);
33        let nodes = view().nodes;
34        for node in nodes {
35            parent.append_child(node.as_web_sys()).unwrap();
36        }
37    }
38}
39
40/// Render a [`View`] under a `parent` node by reusing existing nodes (client side
41/// hydration).
42///
43/// Alias for [`hydrate_to`] with `parent` being the `<body>` tag.
44/// For rendering without hydration, use [`render`](super::render) instead.
45#[cfg(feature = "hydrate")]
46pub fn hydrate(view: impl FnOnce() -> View) {
47    hydrate_to(view, &document().body().unwrap());
48}
49
50/// Render a [`View`] under a `parent` node by reusing existing nodes (client side
51/// hydration).
52///
53/// For rendering under the `<body>` tag, use [`hydrate`] instead.
54/// For rendering without hydration, use [`render`](super::render) instead.
55#[cfg(feature = "hydrate")]
56pub fn hydrate_to(view: impl FnOnce() -> View, parent: &web_sys::Node) {
57    // Do not call the destructor function, effectively leaking the scope.
58    let _ = create_root(|| hydrate_in_scope(view, parent));
59}
60
61/// Render a [`View`] under a `parent` node, in a way that can be cleaned up.
62///
63/// This function is intended to be used for injecting an ephemeral sycamore view into a
64/// non-sycamore app (for example, a file upload modal where you want to cancel the upload if the
65/// modal is closed).
66///
67/// It is expected that this function will be called inside a reactive root, usually created using
68/// [`create_root`].
69#[cfg(feature = "hydrate")]
70pub fn hydrate_in_scope(view: impl FnOnce() -> View, parent: &web_sys::Node) {
71    is_ssr! {
72        let _ = view;
73        let _ = parent;
74        panic!("`hydrate_in_scope` is not available in SSR mode");
75    }
76    is_not_ssr! {
77        // Get mode from HydrationScript.
78        let mode = js_sys::Reflect::get(&window(), &"__sycamore_ssr_mode".into()).unwrap();
79        let mode = if mode.is_undefined() {
80            SsrMode::Sync
81        } else if mode == "blocking" {
82            SsrMode::Blocking
83        } else if mode == "streaming" {
84            SsrMode::Streaming
85        } else {
86            panic!("invalid SSR mode {mode:?}")
87        };
88
89        // Get all nodes with `data-hk` attribute.
90        let existing_nodes = parent
91            .unchecked_ref::<web_sys::Element>()
92            .query_selector_all("[data-hk]")
93            .unwrap();
94
95        HYDRATE_NODES.with(|nodes| {
96            let mut nodes = nodes.borrow_mut();
97            let len = existing_nodes.length();
98            for i in 0..len {
99                let node = existing_nodes.get(i).unwrap();
100                let hk = node.unchecked_ref::<web_sys::Element>().get_attribute("data-hk").unwrap();
101                let key = HydrationKey::parse(&hk).expect("could not parse hydration key");
102                let node = HydrateNode::from_web_sys(node);
103                nodes.insert(key, node);
104            }
105        });
106
107        IS_HYDRATING.set(true);
108        provide_context(mode);
109        provide_context(HydrationRegistry::new());
110        let nodes = view().nodes;
111        // We need to append `nodes` to the `parent` so that the top level nodes also get properly
112        // hydrated.
113        let mut parent = HydrateNode::from_web_sys(parent.clone());
114        for node in nodes {
115            parent.append_child(node);
116        }
117
118        // Wait until suspense is resolved before setting `IS_HYDRATING` to `false`.
119        #[cfg(not(feature = "suspense"))]
120        IS_HYDRATING.set(false);
121        #[cfg(feature = "suspense")]
122        create_effect(|| {
123            if IS_HYDRATING.get() && !sycamore_futures::use_is_loading_global() {
124                IS_HYDRATING.set(false);
125            }
126        });
127    }
128}