react/element/
use_render.rs

1use std::rc::Rc;
2
3use wasm_bindgen::{closure::Closure, JsValue, UnwrapThrowExt};
4
5use crate::IntoElement;
6
7pub type DynUseRenderFn = dyn Fn() -> Option<crate::Element>;
8
9fn impl_bridge_rust_only_component(js_props: crate::JsProps) -> JsValue {
10    #[cfg(debug_assertions)]
11    let children = js_props.children();
12    #[cfg(debug_assertions)]
13    if children.is_some() {
14        panic!("rust only component should not accept children from js");
15    }
16
17    let bridge = js_props.props_bridge().unwrap();
18
19    let ref_bridge = react_sys::use_ref_usize(bridge);
20
21    let old_bridge = ref_bridge.current();
22    if old_bridge != bridge {
23        let _valid = unsafe { forgotten::try_free_with_usize(old_bridge) };
24
25        #[cfg(debug_assertions)]
26        assert!(_valid, "invalid js props bridge: failed to free");
27
28        ref_bridge.set_current(bridge);
29    }
30
31    let render = unsafe { forgotten::try_get_with_usize::<Box<DynUseRenderFn>>(&bridge) };
32
33    let render = render.expect_throw("invalid js props bridge: failed to get");
34
35    let el = render();
36
37    if let Some(el) = el {
38        el.unsafe_into_js_element().into()
39    } else {
40        JsValue::NULL
41    }
42}
43
44type ClosureBridgeRustOnlyComponent = Closure<dyn Fn(crate::JsProps) -> JsValue>;
45
46fn closure_to_bridge_rust_only_component() -> ClosureBridgeRustOnlyComponent {
47    Closure::wrap(
48        Box::new(impl_bridge_rust_only_component) as Box<dyn Fn(crate::JsProps) -> JsValue>
49    )
50}
51
52pub struct UseRenderElement {
53    pub use_render: Rc<Box<DynUseRenderFn>>,
54    pub key: Option<crate::Key>,
55    pub debug_component_name: Option<JsValue>,
56    pub debug_props: Option<JsValue>,
57}
58
59impl UseRenderElement {
60    #[inline]
61    pub fn wrap_use_render<E: crate::IntoOptionalElement, F: 'static + Fn() -> E>(
62        use_render: F,
63        key: Option<crate::Key>,
64        debug_component_name: Option<JsValue>,
65        debug_props: Option<JsValue>,
66    ) -> Self {
67        let use_render = move || use_render().into_optional_element();
68        Self {
69            use_render: Rc::new(Box::new(use_render)),
70            key,
71            debug_component_name,
72            debug_props,
73        }
74    }
75}
76
77impl std::fmt::Debug for UseRenderElement {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        f.debug_struct("UseRenderElement")
80            .field("use_render", &"Rc<RustClosure>")
81            .field("key", &self.key)
82            .field("debug_component_name", &self.debug_component_name)
83            .field("debug_props", &self.debug_props)
84            .finish()
85    }
86}
87
88impl Clone for UseRenderElement {
89    #[inline]
90    fn clone(&self) -> Self {
91        Self {
92            use_render: self.use_render.clone(),
93            key: self.key.clone(),
94            debug_component_name: self.debug_component_name.clone(),
95            debug_props: self.debug_props.clone(),
96        }
97    }
98}
99
100impl UseRenderElement {
101    /// Note that the created element must not be cloned
102    /// or used multiple times as react element.
103    ///
104    /// `use_render` will be forgotten,
105    /// and it will be dropped automatically once changed
106    #[inline]
107    pub(crate) fn unsafe_create_element_js(self) -> react_sys::Element {
108        let Self {
109            use_render,
110            key,
111            debug_component_name,
112            debug_props,
113        } = self;
114
115        thread_local! {
116            static ADAPTER_FN: ClosureBridgeRustOnlyComponent = closure_to_bridge_rust_only_component();
117        }
118
119        ADAPTER_FN.with(|comp_fn| {
120            use wasm_bindgen::JsCast;
121
122            let obj = js_sys::Object::new();
123            let props: &crate::JsProps = obj.unchecked_ref();
124
125            props.set_key(key.map(Into::into).as_ref());
126
127            #[cfg(debug_assertions)]
128            if let Some(debug_component_name) = debug_component_name {
129                props.set_debug_component_name(&debug_component_name);
130            }
131
132            #[cfg(debug_assertions)]
133            if let Some(debug_props) = debug_props {
134                props.set_debug_props(&debug_props);
135            }
136
137            let k = forgotten::forget_rc(use_render);
138
139            let k = k.into_shared();
140            let k = k.as_usize();
141
142            props.set_props_bridge(Some(*k));
143
144            react_sys::create_element_no_children(comp_fn.as_ref(), props.as_ref())
145        })
146    }
147}
148
149impl crate::IntoElement for UseRenderElement {
150    #[inline]
151    fn into_element(self) -> crate::Element {
152        crate::Element::bridge_use_render_element(self)
153    }
154}
155
156impl Into<crate::Element> for UseRenderElement {
157    #[inline]
158    fn into(self) -> crate::Element {
159        self.into_element()
160    }
161}