sycamore_reactive/
context.rs

1//! Context values.
2
3use std::any::{type_name, Any};
4
5use slotmap::Key;
6
7use crate::{create_child_scope, NodeId, Root};
8
9/// Provide a context value in this scope.
10///
11/// # Panics
12/// This panics if a context value of the same type exists already in this scope. Note that it is
13/// allowed to have context values with the same type in _different_ scopes.
14#[cfg_attr(debug_assertions, track_caller)]
15pub fn provide_context<T: 'static>(value: T) {
16    let root = Root::global();
17    provide_context_in_node(root.current_node.get(), value);
18}
19
20/// Provide a context value in a new scope.
21///
22/// Since this creates a new scope, this function should never panic. If the context value already
23/// exists in the outer scope, it will be shadowed by the new value in the inner scope _only_.
24/// Outside of the new scope, the old context value will still be accessible.
25///
26/// # Example
27/// ```
28/// # use sycamore_reactive::*;
29/// # let _ = create_root(|| {
30/// provide_context(123);
31/// assert_eq!(use_context::<i32>(), 123);
32///
33/// provide_context_in_new_scope(456, || {
34///     assert_eq!(use_context::<i32>(), 456);
35/// });
36///
37/// assert_eq!(use_context::<i32>(), 123);
38/// # });
39/// ```
40pub fn provide_context_in_new_scope<T: 'static, U>(value: T, f: impl FnOnce() -> U) -> U {
41    let mut ret = None;
42    create_child_scope(|| {
43        provide_context(value);
44        ret = Some(f());
45    });
46    ret.unwrap()
47}
48
49/// Internal implementation for [`provide_context`].
50#[cfg_attr(debug_assertions, track_caller)]
51fn provide_context_in_node<T: 'static>(id: NodeId, value: T) {
52    let root = Root::global();
53    let mut nodes = root.nodes.borrow_mut();
54    let any: Box<dyn Any> = Box::new(value);
55
56    let node = &mut nodes[id];
57    if node
58        .context
59        .iter()
60        .any(|x| (**x).type_id() == (*any).type_id())
61    {
62        panic!(
63            "a context with type `{}` exists already in this scope",
64            type_name::<T>()
65        );
66    }
67    node.context.push(any);
68}
69
70/// Tries to get a context value of the given type. If no context is found, returns `None`.
71pub fn try_use_context<T: Clone + 'static>() -> Option<T> {
72    let root = Root::global();
73    let nodes = root.nodes.borrow();
74    // Walk up the scope stack until we find one with the context of the right type.
75    let mut current = Some(&nodes[root.current_node.get()]);
76    while let Some(next) = current {
77        for value in &next.context {
78            if let Some(value) = value.downcast_ref::<T>().cloned() {
79                return Some(value);
80            }
81        }
82        // No context of the right type found for this scope. Now check the parent scope.
83        if next.parent.is_null() {
84            current = None;
85        } else {
86            current = Some(&nodes[next.parent]);
87        }
88    }
89    None
90}
91
92/// Get a context with the given type. If no context is found, this panics.
93#[cfg_attr(debug_assertions, track_caller)]
94pub fn use_context<T: Clone + 'static>() -> T {
95    if let Some(value) = try_use_context() {
96        value
97    } else {
98        panic!("no context of type `{}` found", type_name::<T>())
99    }
100}
101
102/// Try to get a context with the given type. If no context is found, returns the value of the
103/// function and sets the value of the context in the current scope.
104pub fn use_context_or_else<T: Clone + 'static, F: FnOnce() -> T>(f: F) -> T {
105    try_use_context().unwrap_or_else(|| {
106        let value = f();
107        provide_context(value.clone());
108        value
109    })
110}
111
112/// Gets how deep the current scope is from the root/global scope. The value for the global scope
113/// itself is always `0`.
114pub fn use_scope_depth() -> u32 {
115    let root = Root::global();
116    let nodes = root.nodes.borrow();
117    let mut current = Some(&nodes[root.current_node.get()]);
118    let mut depth = 0;
119
120    while let Some(next) = current {
121        depth += 1;
122        if next.parent.is_null() {
123            current = None;
124        } else {
125            current = Some(&nodes[next.parent]);
126        }
127    }
128    depth
129}