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")
}
}