1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
use crate::{callback::Void, react_bindings, Persisted, PersistedOrigin};
use js_sys::Reflect;
use std::{
  cell::{Ref, RefCell, RefMut},
  fmt::Debug,
  mem::ManuallyDrop,
  rc::Rc,
};
use wasm_bindgen::{prelude::Closure, JsValue, UnwrapThrowExt};

/// Allows access to the underlying data persisted with [`use_ref()`].
///
/// # Panics
///
/// The rules of borrowing will be enforced at runtime through a [`RefCell`],
/// therefore the methods [`RefContainer::current()`],
/// [`RefContainer::current_mut()`], and [`RefContainer::set_current()`] may
/// panic accordingly.
#[derive(Debug)]
pub struct RefContainer<T> {
  inner: Rc<RefCell<T>>,
  js_ref: JsValue,
}

impl<T: 'static> RefContainer<T> {
  /// Returns a reference to the underlying data.
  ///
  /// # Panics
  ///
  /// Panics if the underlying data is currently mutably borrowed.
  pub fn current(&self) -> Ref<'_, T> {
    self.inner.borrow()
  }

  /// Returns a mutable reference to the underlying data.
  ///
  /// # Panics
  ///
  /// Panics if the underlying data is currently borrowed.
  pub fn current_mut(&mut self) -> RefMut<'_, T> {
    self.inner.borrow_mut()
  }

  /// Sets the underlying data to the given value.
  ///
  /// # Panics
  ///
  /// Panics if the underlying data is currently borrowed.
  pub fn set_current(&mut self, value: T) {
    *self.current_mut() = value;
  }

  /// Converts a JS value into a [`RefContainer`].
  ///
  /// # Safety
  ///
  /// The following assumptions must hold:
  ///
  /// - The JS value has been obtained by creating a [`RefContainer`] using
  ///   [`use_ref()`] and converting it into [`JsValue`].
  /// - The React component owning the [`RefContainer`] hasn't been unmounted.
  ///
  /// Otherwise this might lead to memory issues.
  pub unsafe fn try_from_js_ref(
    js_value: &JsValue,
  ) -> Result<RefContainer<T>, JsValue> {
    let ptr =
      react_bindings::cast_to_usize(&Reflect::get(js_value, &"ptr".into())?)
        as *const RefCell<T>;

    // We're wrapping the value in `ManuallyDrop` since we do not want to drop
    // the inner value when this is goes out of scope.
    let inner = ManuallyDrop::new(Rc::from_raw(ptr));
    let result = RefContainer {
      inner: (*inner).clone(),
      js_ref: js_value.clone(),
    };

    Ok(result)
  }
}

impl<T: 'static> Persisted for RefContainer<T> {
  fn ptr(&self) -> PersistedOrigin {
    PersistedOrigin
  }
}

impl<T> Clone for RefContainer<T> {
  fn clone(&self) -> Self {
    Self {
      inner: self.inner.clone(),
      js_ref: self.js_ref.clone(),
    }
  }
}

impl<T> AsRef<JsValue> for RefContainer<T> {
  fn as_ref(&self) -> &JsValue {
    &self.js_ref
  }
}

impl<T> From<RefContainer<T>> for JsValue {
  fn from(value: RefContainer<T>) -> Self {
    value.js_ref
  }
}

/// This is the main hook for persisting Rust data through the entire lifetime
/// of the component.
///
/// Whenever the component is unmounted by React, the data will also be dropped.
/// Keep in mind that the inner value of [`use_ref()`] can only be accessed in
/// Rust. If you need a ref to hold a DOM element (or a JS value in general),
/// use [`use_js_ref()`](crate::hooks::use_js_ref()) instead.
///
/// The component will not rerender when you mutate the underlying data. If you
/// want that, use [`use_state()`](crate::hooks::use_state()) instead.
///
/// # Example
///
/// ```
/// # use wasm_react::{*, hooks::*};
/// # struct MyData { value: &'static str };
/// # struct MyComponent { value: &'static str };
/// #
/// impl Component for MyComponent {
///   fn render(&self) -> VNode {
///     let ref_container = use_ref(MyData {
///       value: "Hello World!"
///     });
///
///     use_effect({
///       let value = self.value;
///       let mut ref_container = ref_container.clone();
///
///       move || {
///         ref_container.current_mut().value = value;
///         || ()
///       }
///     }, Deps::some(self.value));
///
///     h!(div).build(c![
///       ref_container.current().value
///     ])
///   }
/// }
/// ```
pub fn use_ref<T: 'static>(init: T) -> RefContainer<T> {
  let js_ref = react_bindings::use_rust_ref(
    Closure::once(move |_: Void| Rc::into_raw(Rc::new(RefCell::new(init))))
      .as_ref(),
    &Closure::once_into_js(|unmounted: bool, ptr: usize| {
      if unmounted {
        let ptr = ptr as *const RefCell<T>;

        // A callback with `unmounted == true` can only be called once (look
        // at `react-bindings.js#useRustRef`), so a double-free cannot happen!
        drop(unsafe { Rc::from_raw(ptr) });
      }
    }),
  );

  unsafe {
    RefContainer::try_from_js_ref(&js_ref)
      .expect_throw("trying to operate invalid ref container")
  }
}