wasm_react/hooks/use_ref.rs
1use crate::react_bindings;
2use std::{
3 any::Any,
4 cell::{Ref, RefCell, RefMut},
5 fmt::Debug,
6 rc::Rc,
7};
8use wasm_bindgen::prelude::*;
9
10#[doc(hidden)]
11#[wasm_bindgen(js_name = __WasmReact_RefContainerValue)]
12#[derive(Debug, Clone)]
13pub struct RefContainerValue(pub(crate) Rc<dyn Any>);
14
15impl RefContainerValue {
16 pub fn value<T: 'static>(&self) -> Result<Rc<T>, Rc<dyn Any>> {
17 Rc::downcast::<T>(self.0.clone())
18 }
19}
20
21/// Allows access to the underlying data persisted with [`use_ref()`].
22///
23/// # Panics
24///
25/// The rules of borrowing will be enforced at runtime through a [`RefCell`],
26/// therefore the methods [`RefContainer::current()`],
27/// [`RefContainer::current_mut()`], and [`RefContainer::set_current()`] may
28/// panic accordingly.
29#[derive(Debug)]
30pub struct RefContainer<T>(Rc<RefCell<T>>);
31
32impl<T: 'static> RefContainer<T> {
33 /// Returns a reference to the underlying data.
34 ///
35 /// # Panics
36 ///
37 /// Panics if the underlying data is currently mutably borrowed.
38 pub fn current(&self) -> Ref<'_, T> {
39 self.0.borrow()
40 }
41
42 /// Returns a mutable reference to the underlying data.
43 ///
44 /// # Panics
45 ///
46 /// Panics if the underlying data is currently borrowed.
47 pub fn current_mut(&mut self) -> RefMut<'_, T> {
48 self.0.borrow_mut()
49 }
50
51 /// Sets the underlying data to the given value.
52 ///
53 /// # Panics
54 ///
55 /// Panics if the underlying data is currently borrowed.
56 pub fn set_current(&mut self, value: T) {
57 *self.current_mut() = value;
58 }
59}
60
61impl<T> Clone for RefContainer<T> {
62 fn clone(&self) -> Self {
63 Self(self.0.clone())
64 }
65}
66
67/// This is the main hook for persisting Rust data through the entire lifetime
68/// of the component.
69///
70/// Whenever the component is unmounted by React, the data will also be dropped.
71/// Keep in mind that the inner value of [`use_ref()`] can only be accessed in
72/// Rust. If you need a ref to hold a DOM element (or a JS value in general),
73/// use [`use_js_ref()`](crate::hooks::use_js_ref()) instead.
74///
75/// The component will not rerender when you mutate the underlying data. If you
76/// want that, use [`use_state()`](crate::hooks::use_state()) instead.
77///
78/// # Example
79///
80/// ```
81/// # use wasm_react::{*, hooks::*};
82/// # struct MyData { value: &'static str };
83/// # struct MyComponent { value: &'static str };
84/// #
85/// impl Component for MyComponent {
86/// fn render(&self) -> VNode {
87/// let ref_container = use_ref(MyData {
88/// value: "Hello World!"
89/// });
90///
91/// use_effect({
92/// clones!(self.value, mut ref_container);
93///
94/// move || {
95/// ref_container.current_mut().value = value;
96/// }
97/// }, Deps::some(self.value));
98///
99/// let vnode = h!(div).build(
100/// ref_container.current().value
101/// );
102/// vnode
103/// }
104/// }
105/// ```
106pub fn use_ref<T: 'static>(init: T) -> RefContainer<T> {
107 let mut value = None;
108
109 react_bindings::use_rust_ref(
110 Closure::once(move || RefContainerValue(Rc::new(RefCell::new(init))))
111 .as_ref(),
112 &mut |ref_container_value| {
113 value = Some(
114 ref_container_value
115 .value::<RefCell<T>>()
116 .expect_throw("mismatched ref container type"),
117 );
118 },
119 );
120
121 RefContainer(value.expect_throw("callback was not called"))
122}