wasm_react/context.rs
1use crate::{
2 create_element, hooks::RefContainerValue, props::Props, react_bindings,
3 Component, VNode,
4};
5use js_sys::Reflect;
6use std::{marker::PhantomData, rc::Rc, thread::LocalKey};
7use wasm_bindgen::{intern, JsValue, UnwrapThrowExt};
8
9/// Represents a [React context][context] that can hold a global state.
10///
11/// See [`create_context()`] for usage.
12///
13/// [context]: https://react.dev/learn/passing-data-deeply-with-context
14#[derive(Debug)]
15pub struct Context<T> {
16 js_context: JsValue,
17 phantom: PhantomData<T>,
18}
19
20impl<T> AsRef<JsValue> for Context<T> {
21 fn as_ref(&self) -> &JsValue {
22 &self.js_context
23 }
24}
25
26impl<T> From<Context<T>> for JsValue {
27 fn from(value: Context<T>) -> Self {
28 value.js_context
29 }
30}
31
32/// Creates a new [React context][context] that can hold a global state.
33///
34/// Use [`ContextProvider`] to make the context available for its subtrees and
35/// [`use_context()`](crate::hooks::use_context()) to get access to the context
36/// value.
37///
38/// [context]: https://react.dev/learn/passing-data-deeply-with-context
39///
40/// # Example
41///
42/// ```
43/// # use wasm_react::{*, hooks::*, props::*};
44/// # pub enum Theme { DarkMode, LightMode }
45/// #
46/// thread_local! {
47/// // Pass in a default value for the context.
48/// static THEME_CONTEXT: Context<Theme> = create_context(Theme::LightMode.into());
49/// }
50///
51/// struct App;
52///
53/// impl Component for App {
54/// fn render(&self) -> VNode {
55/// // Use a `ContextProvider` to pass the context value to the trees below.
56/// // In this example, we are passing down `Theme::DarkMode`.
57///
58/// ContextProvider::from(&THEME_CONTEXT)
59/// .value(Some(Theme::DarkMode.into()))
60/// .build(
61/// Toolbar.build(),
62/// )
63/// }
64/// }
65///
66/// struct Toolbar;
67///
68/// impl Component for Toolbar {
69/// fn render(&self) -> VNode {
70/// // Theme context does not have to be passed down explicitly.
71/// h!(div).build(Button.build())
72/// }
73/// }
74///
75/// struct Button;
76///
77/// impl Component for Button {
78/// fn render(&self) -> VNode {
79/// // Use the `use_context` hook to get access to the context value.
80/// let theme = use_context(&THEME_CONTEXT);
81///
82/// h!(button)
83/// .style(
84/// &Style::new()
85/// .background_color(match *theme {
86/// Theme::LightMode => "white",
87/// Theme::DarkMode => "black",
88/// })
89/// )
90/// .build("Button")
91/// }
92/// }
93/// ```
94pub fn create_context<T: 'static>(init: Rc<T>) -> Context<T> {
95 Context {
96 js_context: react_bindings::create_context(RefContainerValue(init)),
97 phantom: PhantomData,
98 }
99}
100
101/// A component that can make the given context available for its subtrees.
102///
103/// See [`create_context()`] for usage.
104#[derive(Debug, Clone)]
105pub struct ContextProvider<T: 'static> {
106 context: &'static LocalKey<Context<T>>,
107 value: Option<Rc<T>>,
108 children: VNode,
109}
110
111impl<T: 'static> ContextProvider<T> {
112 /// Creates a new [`ContextProvider`] from the given context.
113 pub fn from(context: &'static LocalKey<Context<T>>) -> Self {
114 Self {
115 context,
116 value: None,
117 children: ().into(),
118 }
119 }
120
121 /// Sets the value of the context to be passed down.
122 pub fn value(mut self, value: Option<Rc<T>>) -> Self {
123 self.value = value;
124 self
125 }
126
127 /// Returns a [`VNode`] to be included in a render function.
128 pub fn build(mut self, children: impl Into<VNode>) -> VNode {
129 self.children = children.into();
130 Component::build(self)
131 }
132}
133
134impl<T: 'static> Component for ContextProvider<T> {
135 fn render(&self) -> VNode {
136 self.context.with(|context| {
137 create_element(
138 &Reflect::get(context.as_ref(), &intern("Provider").into())
139 .expect_throw("cannot read from context object"),
140 &{
141 let mut props = Props::new();
142
143 if let Some(value) = self.value.as_ref() {
144 props = props.insert(
145 intern("value"),
146 &RefContainerValue(value.clone()).into(),
147 );
148 }
149
150 props
151 },
152 self.children.clone(),
153 )
154 })
155 }
156}