dioxus_core/
scopes.rs

1use crate::{
2    any_props::BoxedAnyProps, reactive_context::ReactiveContext, scope_context::Scope, Element,
3    RenderError, Runtime, VNode,
4};
5use std::{cell::Ref, rc::Rc};
6
7/// A component's unique identifier.
8///
9/// `ScopeId` is a `usize` that acts a key for the internal slab of Scopes. This means that the key is not unique across
10/// time. We do try and guarantee that between calls to `wait_for_work`, no ScopeIds will be recycled in order to give
11/// time for any logic that relies on these IDs to properly update.
12#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
13#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
14pub struct ScopeId(pub usize);
15
16impl std::fmt::Debug for ScopeId {
17    #[allow(unused_mut)]
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        let mut builder = f.debug_tuple("ScopeId");
20        let mut builder = builder.field(&self.0);
21        #[cfg(debug_assertions)]
22        {
23            if let Some(scope) = Runtime::try_current()
24                .as_ref()
25                .and_then(|r| r.try_get_state(*self))
26            {
27                builder = builder.field(&scope.name);
28            }
29        }
30        builder.finish()
31    }
32}
33
34impl ScopeId {
35    /// The ScopeId of the main scope passed into [`crate::VirtualDom::new`].
36    ///
37    /// This scope will last for the entire duration of your app, making it convenient for long-lived state
38    /// that is created dynamically somewhere down the component tree.
39    ///
40    /// # Example
41    ///
42    /// ```rust, no_run
43    /// use dioxus::prelude::*;
44    /// let my_persistent_state = Signal::new_in_scope(String::new(), ScopeId::APP);
45    /// ```
46    // ScopeId(0) is the root scope wrapper
47    // ScopeId(1) is the default suspense boundary
48    // ScopeId(2) is the default error boundary
49    // ScopeId(3) is the users root scope
50    pub const APP: ScopeId = ScopeId(3);
51
52    /// The ScopeId of the topmost error boundary in the tree.
53    pub const ROOT_ERROR_BOUNDARY: ScopeId = ScopeId(2);
54
55    /// The ScopeId of the topmost suspense boundary in the tree.
56    pub const ROOT_SUSPENSE_BOUNDARY: ScopeId = ScopeId(1);
57
58    /// The ScopeId of the topmost scope in the tree.
59    /// This will be higher up in the tree than [`ScopeId::APP`] because dioxus inserts a default [`crate::SuspenseBoundary`] and [`crate::ErrorBoundary`] at the root of the tree.
60    // ScopeId(0) is the root scope wrapper
61    pub const ROOT: ScopeId = ScopeId(0);
62
63    pub(crate) const PLACEHOLDER: ScopeId = ScopeId(usize::MAX);
64
65    pub(crate) fn is_placeholder(&self) -> bool {
66        *self == Self::PLACEHOLDER
67    }
68}
69
70/// A component's rendered state.
71///
72/// This state erases the type of the component's props. It is used to store the state of a component in the runtime.
73pub struct ScopeState {
74    pub(crate) runtime: Rc<Runtime>,
75    pub(crate) context_id: ScopeId,
76    /// The last node that has been rendered for this component. This node may not ben mounted
77    /// During suspense, this component can be rendered in the background multiple times
78    pub(crate) last_rendered_node: Option<LastRenderedNode>,
79    pub(crate) props: BoxedAnyProps,
80    pub(crate) reactive_context: ReactiveContext,
81}
82
83impl ScopeState {
84    /// Get a handle to the currently active head node arena for this Scope
85    ///
86    /// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.
87    ///
88    /// Panics if the tree has not been built yet.
89    pub fn root_node(&self) -> &VNode {
90        self.try_root_node()
91            .expect("The tree has not been built yet. Make sure to call rebuild on the tree before accessing its nodes.")
92    }
93
94    /// Try to get a handle to the currently active head node arena for this Scope
95    ///
96    /// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.
97    ///
98    /// Returns [`None`] if the tree has not been built yet.
99    pub fn try_root_node(&self) -> Option<&VNode> {
100        match &self.last_rendered_node {
101            Some(LastRenderedNode::Real(vnode)) => Some(vnode),
102            Some(LastRenderedNode::Placeholder(vnode, _)) => Some(vnode),
103            None => None,
104        }
105    }
106
107    /// Returns the scope id of this [`ScopeState`].
108    pub fn id(&self) -> ScopeId {
109        self.context_id
110    }
111
112    pub(crate) fn state(&self) -> Ref<'_, Scope> {
113        self.runtime.get_state(self.context_id)
114    }
115
116    /// Returns the height of this scope in the tree.
117    pub fn height(&self) -> u32 {
118        self.state().height()
119    }
120}
121
122#[derive(Clone, PartialEq, Debug)]
123pub enum LastRenderedNode {
124    Real(VNode),
125    Placeholder(VNode, RenderError),
126}
127
128impl std::ops::Deref for LastRenderedNode {
129    type Target = VNode;
130
131    fn deref(&self) -> &Self::Target {
132        match self {
133            LastRenderedNode::Real(vnode) => vnode,
134            LastRenderedNode::Placeholder(vnode, _err) => vnode,
135        }
136    }
137}
138
139impl LastRenderedNode {
140    pub fn new(node: Element) -> Self {
141        match node {
142            Ok(vnode) => LastRenderedNode::Real(vnode),
143            Err(err) => LastRenderedNode::Placeholder(VNode::placeholder(), err),
144        }
145    }
146
147    pub fn as_vnode(&self) -> &VNode {
148        match self {
149            LastRenderedNode::Real(vnode) => vnode,
150            LastRenderedNode::Placeholder(vnode, _err) => vnode,
151        }
152    }
153}
154
155impl Drop for ScopeState {
156    fn drop(&mut self) {
157        self.runtime.remove_scope(self.context_id);
158    }
159}