wasm_react/props/
props.rs

1use crate::{
2  hooks::{use_tmp_ref, JsRefContainer},
3  Callback, KeyType,
4};
5use js_sys::{Object, Reflect};
6use wasm_bindgen::{
7  convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi},
8  intern, JsCast, JsValue, UnwrapThrowExt,
9};
10
11/// A convenience builder for JS objects. Mainly used for constructing props
12/// that are not controlled by Rust.
13///
14/// Use [`Style`](super::Style) to create style objects which also provides
15/// auto-completion.
16///
17/// # Example
18///
19/// ```
20/// # use wasm_react::{*, props::*};
21/// # use wasm_bindgen::prelude::*;
22/// #
23/// # fn f(handle_click: &Callback<Void>) -> Props {
24/// Props::new()
25///   .insert("id", &"app".into())
26///   .insert_callback("onClick", handle_click)
27/// # }
28/// ```
29#[derive(Debug, Default, Clone)]
30pub struct Props(Object);
31
32impl Props {
33  /// Creates a new, empty object.
34  pub fn new() -> Self {
35    Self::default()
36  }
37
38  /// Sets the [React key][key].
39  ///
40  /// [key]: https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key
41  pub fn key(self, value: Option<impl KeyType>) -> Self {
42    self.insert(
43      "key",
44      &value.map(|x| x.into()).unwrap_or(JsValue::UNDEFINED),
45    )
46  }
47
48  /// Sets the [React ref][ref] to the given ref container created with the
49  /// [`use_js_ref()`](crate::hooks::use_js_ref()) hook.
50  ///
51  /// [ref]: https://react.dev/learn/manipulating-the-dom-with-refs
52  pub fn ref_container<T>(self, ref_container: &JsRefContainer<T>) -> Self {
53    self.insert("ref", ref_container.as_ref())
54  }
55
56  /// Sets the [React ref][ref] to the given ref callback.
57  ///
58  /// [ref]: https://react.dev/learn/manipulating-the-dom-with-refs
59  pub fn ref_callback<T>(self, ref_callback: &Callback<Option<T>>) -> Self
60  where
61    T: OptionFromWasmAbi + 'static,
62  {
63    self.insert_callback("ref", ref_callback)
64  }
65
66  /// Equivalent to `props[key] = value;`.
67  pub fn insert(self, key: &str, value: &JsValue) -> Self {
68    self.ref_insert(key, value);
69    self
70  }
71
72  fn ref_insert(&self, key: &str, value: &JsValue) {
73    Reflect::set(&self.0, &intern(key).into(), value)
74      .expect_throw("cannot write into props object");
75  }
76
77  /// Equivalent to `props[key] = f;`.
78  pub fn insert_callback<T, U>(self, key: &str, f: &Callback<T, U>) -> Self
79  where
80    T: FromWasmAbi + 'static,
81    U: IntoWasmAbi + 'static,
82  {
83    use_tmp_ref(f.clone(), |f| {
84      self.ref_insert(key, &f.as_js());
85    });
86
87    self
88  }
89}
90
91impl AsRef<JsValue> for Props {
92  fn as_ref(&self) -> &JsValue {
93    &self.0
94  }
95}
96
97impl From<Props> for JsValue {
98  fn from(style: Props) -> Self {
99    style.0.into()
100  }
101}
102
103impl From<Object> for Props {
104  fn from(value: Object) -> Self {
105    Props(value)
106  }
107}
108
109impl TryFrom<JsValue> for Props {
110  type Error = JsValue;
111
112  fn try_from(value: JsValue) -> Result<Self, Self::Error> {
113    Ok(Props(value.dyn_into::<Object>()?))
114  }
115}