Skip to main content

dioxus_core/
scopes.rs

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