yew/functional/hooks/use_context.rs
1use std::cell::RefCell;
2use std::marker::PhantomData;
3use std::rc::Rc;
4
5use crate::callback::Callback;
6use crate::context::ContextHandle;
7use crate::functional::{Hook, HookContext};
8
9/// Hook for consuming context values in function components.
10/// The context of the type passed as `T` is returned. If there is no such context in scope, `None`
11/// is returned. A component which calls `use_context` will re-render when the data of the context
12/// changes.
13///
14/// More information about contexts and how to define and consume them can be found on [Yew Docs](https://yew.rs/docs/concepts/contexts).
15///
16/// # Example
17///
18/// ```rust
19/// use yew::{ContextProvider, function_component, html, use_context, use_state, Html};
20///
21///
22/// /// App theme
23/// #[derive(Clone, Debug, PartialEq)]
24/// struct Theme {
25/// foreground: String,
26/// background: String,
27/// }
28///
29/// /// Main component
30/// #[function_component]
31/// pub fn App() -> Html {
32/// let ctx = use_state(|| Theme {
33/// foreground: "#000000".to_owned(),
34/// background: "#eeeeee".to_owned(),
35/// });
36///
37/// html! {
38/// // `ctx` is type `Rc<UseStateHandle<Theme>>` while we need `Theme`
39/// // so we deref it.
40/// // It derefs to `&Theme`, hence the clone
41/// <ContextProvider<Theme> context={(*ctx).clone()}>
42/// // Every child here and their children will have access to this context.
43/// <Toolbar />
44/// </ContextProvider<Theme>>
45/// }
46/// }
47///
48/// /// The toolbar.
49/// /// This component has access to the context
50/// #[function_component]
51/// pub fn Toolbar() -> Html {
52/// html! {
53/// <div>
54/// <ThemedButton />
55/// </div>
56/// }
57/// }
58///
59/// /// Button placed in `Toolbar`.
60/// /// As this component is a child of `ThemeContextProvider` in the component tree, it also has access to the context.
61/// #[function_component]
62/// pub fn ThemedButton() -> Html {
63/// let theme = use_context::<Theme>().expect("no ctx found");
64///
65/// html! {
66/// <button style={format!("background: {}; color: {};", theme.background, theme.foreground)}>
67/// { "Click me!" }
68/// </button>
69/// }
70/// }
71/// ```
72pub fn use_context<T: Clone + PartialEq + 'static>() -> impl Hook<Output = Option<T>> {
73 struct HookProvider<T: Clone + PartialEq + 'static> {
74 _marker: PhantomData<T>,
75 }
76
77 struct UseContext<T: Clone + PartialEq + 'static> {
78 _handle: Option<ContextHandle<T>>,
79 value: Rc<RefCell<Option<T>>>,
80 }
81
82 impl<T> Hook for HookProvider<T>
83 where
84 T: Clone + PartialEq + 'static,
85 {
86 type Output = Option<T>;
87
88 fn run(self, ctx: &mut HookContext) -> Self::Output {
89 let scope = ctx.scope.clone();
90
91 let state = ctx.next_state(move |re_render| -> UseContext<T> {
92 let value_cell: Rc<RefCell<Option<T>>> = Rc::default();
93
94 let (init_value, handle) = {
95 let value_cell = value_cell.clone();
96
97 scope.context(Callback::from(move |m| {
98 *(value_cell.borrow_mut()) = Some(m);
99 re_render()
100 }))
101 }
102 .map(|(value, handle)| (Some(value), Some(handle)))
103 .unwrap_or((None, None));
104
105 *(value_cell.borrow_mut()) = init_value;
106
107 UseContext {
108 _handle: handle,
109 value: value_cell,
110 }
111 });
112
113 let value = state.value.borrow();
114 value.clone()
115 }
116 }
117
118 HookProvider {
119 _marker: PhantomData,
120 }
121}