move ScopeId methods to the runtime (#4723)

* move `ScopeId` methods to runtime methods

* cargo check unused

* wip...

* wip...

* fix consume context

* fix test

* guard hoisted

* fix doc comment
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2025-10-04 01:55:52 -07:00
1 parent c56bac04f4
commit c29cef4ea3
40 files changed
+466 -697

No files matched your search

+33 -283
View File
@@ -1,6 +1,5 @@
use crate::{
innerlude::{CapturedError, SchedulerMsg, SuspenseContext},
runtime::RuntimeError,
innerlude::{SchedulerMsg, SuspenseContext},
Runtime, ScopeId, Task,
};
use generational_box::{AnyStorage, Owner};
@@ -96,7 +95,7 @@ impl Scope {
}
fn sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
Runtime::with(|rt| rt.sender.clone()).unwrap_or_else(|e| panic!("{}", e))
Runtime::current().sender.clone()
}
/// Mount the scope and queue any pending effects if it is not already mounted
@@ -133,12 +132,12 @@ impl Scope {
}
/// Mark this scope as dirty, and schedule a render for it.
pub fn needs_update(&self) {
pub(crate) fn needs_update(&self) {
self.needs_update_any(self.id)
}
/// Mark this scope as dirty, and schedule a render for it.
pub fn needs_update_any(&self, id: ScopeId) {
pub(crate) fn needs_update_any(&self, id: ScopeId) {
self.sender()
.unbounded_send(SchedulerMsg::Immediate(id))
.expect("Scheduler to exist if scope exists");
@@ -151,7 +150,7 @@ impl Scope {
/// Note: The function returned by this method will schedule an update for the current component even if it has already updated between when `schedule_update` was called and when the returned function is called.
/// If the desired behavior is to invalidate the current rendering of the current component (and no-op if already invalidated)
/// [`subscribe`](crate::reactive_context::ReactiveContext::subscribe) to the [`current`](crate::reactive_context::ReactiveContext::current) [`ReactiveContext`](crate::reactive_context::ReactiveContext) instead.
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
pub(crate) fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
let (chan, id) = (self.sender(), self.id);
Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
}
@@ -164,7 +163,7 @@ impl Scope {
///
/// Note: It does not matter when `schedule_update_any` is called: the returned function will invalidate what ever generation of the specified component is current when returned function is called.
/// If the desired behavior is to schedule invalidation of the current rendering of a component, use [`ReactiveContext`](crate::reactive_context::ReactiveContext) instead.
pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
pub(crate) fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
let chan = self.sender();
Arc::new(move |id| {
_ = chan.unbounded_send(SchedulerMsg::Immediate(id));
@@ -172,7 +171,7 @@ impl Scope {
}
/// Get the owner for the current scope.
pub fn owner<S: AnyStorage>(&self) -> Owner<S> {
pub(crate) fn owner<S: AnyStorage>(&self) -> Owner<S> {
match self.has_context() {
Some(rt) => rt,
None => {
@@ -183,7 +182,7 @@ impl Scope {
}
/// Return any context of type T if it exists on this scope
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
pub(crate) fn has_context<T: 'static + Clone>(&self) -> Option<T> {
self.shared_contexts
.borrow()
.iter()
@@ -194,49 +193,23 @@ impl Scope {
/// Try to retrieve a shared state with type `T` from any parent scope.
///
/// Clones the state if it exists.
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
tracing::trace!(
"looking for context {} ({:?}) in {:?}",
std::any::type_name::<T>(),
std::any::TypeId::of::<T>(),
self.id
);
if let Some(this_ctx) = self.has_context() {
pub(crate) fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
if let Some(this_ctx) = self.has_context::<T>() {
return Some(this_ctx);
}
let mut search_parent = self.parent_id;
let cur_runtime = Runtime::with(|runtime| {
Runtime::with(|runtime| {
while let Some(parent_id) = search_parent {
let Some(parent) = runtime.get_state(parent_id) else {
tracing::error!("Parent scope {:?} not found", parent_id);
return None;
};
tracing::trace!(
"looking for context {} ({:?}) in {:?}",
std::any::type_name::<T>(),
std::any::TypeId::of::<T>(),
parent.id
);
if let Some(shared) = parent.has_context() {
let parent = runtime.try_get_state(parent_id)?;
if let Some(shared) = parent.has_context::<T>() {
return Some(shared);
}
search_parent = parent.parent_id;
}
None
});
match cur_runtime.ok().flatten() {
Some(ctx) => Some(ctx),
None => {
tracing::trace!(
"context {} ({:?}) not found",
std::any::type_name::<T>(),
std::any::TypeId::of::<T>()
);
None
}
}
})
}
/// Inject a `Box<dyn Any>` into the context of this scope
@@ -281,13 +254,7 @@ impl Scope {
/// rsx!(div { "hello {state.0}" })
/// }
/// ```
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
tracing::trace!(
"providing context {} ({:?}) in {:?}",
std::any::type_name::<T>(),
std::any::TypeId::of::<T>(),
self.id
);
pub(crate) fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
let mut contexts = self.shared_contexts.borrow_mut();
// If the context exists, swap it out for the new value
@@ -312,14 +279,8 @@ impl Scope {
///
/// Note that you should be checking if the context existed before trying to provide a new one. Providing a context
/// when a context already exists will swap the context out for the new one, which may not be what you want.
pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
Runtime::with(|runtime| {
runtime
.get_state(ScopeId::ROOT)
.unwrap()
.provide_context(context)
})
.expect("Runtime to exist")
pub(crate) fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
Runtime::with(|runtime| runtime.get_state(ScopeId::ROOT).provide_context(context))
}
/// Start a new future on the same thread as the rest of the VirtualDom.
@@ -348,108 +309,29 @@ impl Scope {
/// }
/// });
/// ```
pub fn spawn_isomorphic(&self, fut: impl Future<Output = ()> + 'static) -> Task {
let id = Runtime::with(|rt| rt.spawn_isomorphic(self.id, fut)).expect("Runtime to exist");
pub(crate) fn spawn_isomorphic(&self, fut: impl Future<Output = ()> + 'static) -> Task {
let id = Runtime::with(|rt| rt.spawn_isomorphic(self.id, fut));
self.spawned_tasks.borrow_mut().insert(id);
id
}
/// Spawns the future and returns the [`Task`]
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) -> Task {
let id = Runtime::with(|rt| rt.spawn(self.id, fut)).expect("Runtime to exist");
pub(crate) fn spawn(&self, fut: impl Future<Output = ()> + 'static) -> Task {
let id = Runtime::with(|rt| rt.spawn(self.id, fut));
self.spawned_tasks.borrow_mut().insert(id);
id
}
/// Queue an effect to run after the next render
pub fn queue_effect(&self, f: impl FnOnce() + 'static) {
Runtime::with(|rt| rt.queue_effect(self.id, f)).expect("Runtime to exist");
pub(crate) fn queue_effect(&self, f: impl FnOnce() + 'static) {
Runtime::with(|rt| rt.queue_effect(self.id, f));
}
/// Store a value between renders. The foundational hook for all other hooks.
///
/// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render).
/// `use_hook` will return a clone of the value on every render.
///
/// In order to clean up resources you would need to implement the [`Drop`] trait for an inner value stored in a RC or similar (Signals for instance),
/// as these only drop their inner value once all references have been dropped, which only happens when the component is dropped.
///
/// <div class="warning">
///
/// `use_hook` is not reactive. It just returns the value on every render. If you need state that will track changes, use [`use_signal`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_signal.html) instead.
///
/// ❌ Don't use `use_hook` with `Rc<RefCell<T>>` for state. It will not update the UI and other hooks when the state changes.
/// ```rust
/// use dioxus::prelude::*;
/// use std::rc::Rc;
/// use std::cell::RefCell;
///
/// pub fn Comp() -> Element {
/// let count = use_hook(|| Rc::new(RefCell::new(0)));
///
/// rsx! {
/// button {
/// onclick: move |_| *count.borrow_mut() += 1,
/// "{count.borrow()}"
/// }
/// }
/// }
/// ```
///
/// ✅ Use `use_signal` instead.
/// ```rust
/// use dioxus::prelude::*;
///
/// pub fn Comp() -> Element {
/// let mut count = use_signal(|| 0);
///
/// rsx! {
/// button {
/// onclick: move |_| count += 1,
/// "{count}"
/// }
/// }
/// }
/// ```
///
/// </div>
///
/// # Example
///
/// ```rust
/// use dioxus::prelude::*;
///
/// // prints a greeting on the initial render
/// pub fn use_hello_world() {
/// use_hook(|| println!("Hello, world!"));
/// }
/// ```
///
/// # Custom Hook Example
///
/// ```rust
/// use dioxus::prelude::*;
///
/// pub struct InnerCustomState(usize);
///
/// impl Drop for InnerCustomState {
/// fn drop(&mut self){
/// println!("Component has been dropped.");
/// }
/// }
///
/// #[derive(Clone, Copy)]
/// pub struct CustomState {
/// inner: Signal<InnerCustomState>
/// }
///
/// pub fn use_custom_state() -> CustomState {
/// use_hook(|| CustomState {
/// inner: Signal::new(InnerCustomState(0))
/// })
/// }
/// ```
pub fn use_hook<State: Clone + 'static>(&self, initializer: impl FnOnce() -> State) -> State {
/// Store a value in the hook list, returning the value.
pub(crate) fn use_hook<State: Clone + 'static>(
&self,
initializer: impl FnOnce() -> State,
) -> State {
let cur_hook = self.hook_index.get();
// The hook list works by keeping track of the current hook index and pushing the index forward
@@ -513,155 +395,23 @@ impl Scope {
);
}
pub fn push_before_render(&self, f: impl FnMut() + 'static) {
pub(crate) fn push_before_render(&self, f: impl FnMut() + 'static) {
self.before_render.borrow_mut().push(Box::new(f));
}
pub fn push_after_render(&self, f: impl FnMut() + 'static) {
pub(crate) fn push_after_render(&self, f: impl FnMut() + 'static) {
self.after_render.borrow_mut().push(Box::new(f));
}
/// Get the current render since the inception of this component
///
/// This can be used as a helpful diagnostic when debugging hooks/renders, etc
pub fn generation(&self) -> usize {
pub(crate) fn generation(&self) -> usize {
self.render_count.get()
}
/// Get the height of this scope
pub fn height(&self) -> u32 {
pub(crate) fn height(&self) -> u32 {
self.height
}
}
impl ScopeId {
/// Get the current scope id
pub fn current_scope_id(self) -> Result<ScopeId, RuntimeError> {
Runtime::with(|rt| rt.current_scope_id().ok())
.ok()
.flatten()
.ok_or(RuntimeError::new())
}
/// Consume context from the current scope
pub fn consume_context<T: 'static + Clone>(self) -> Option<T> {
Runtime::with_scope(self, |cx| cx.consume_context::<T>()).expect("Runtime to exist")
}
/// Consume context from the current scope
pub fn consume_context_from_scope<T: 'static + Clone>(self, scope_id: ScopeId) -> Option<T> {
Runtime::with(|rt| {
rt.get_state(scope_id)
.and_then(|cx| cx.consume_context::<T>())
})
.ok()
.flatten()
}
/// Check if the current scope has a context
pub fn has_context<T: 'static + Clone>(self) -> Option<T> {
Runtime::with_scope(self, |cx| cx.has_context::<T>())
.ok()
.flatten()
}
/// Provide context to the current scope
pub fn provide_context<T: 'static + Clone>(self, value: T) -> T {
Runtime::with_scope(self, |cx| cx.provide_context(value)).unwrap()
}
/// Pushes the future onto the poll queue to be polled after the component renders.
pub fn push_future(self, fut: impl Future<Output = ()> + 'static) -> Option<Task> {
Runtime::with_scope(self, |cx| cx.spawn(fut)).ok()
}
/// Spawns the future but does not return the [`Task`]
pub fn spawn(self, fut: impl Future<Output = ()> + 'static) {
Runtime::with_scope(self, |cx| cx.spawn(fut)).unwrap();
}
/// Get the current render since the inception of this component
///
/// This can be used as a helpful diagnostic when debugging hooks/renders, etc
pub fn generation(self) -> Option<usize> {
Runtime::with_scope(self, |cx| Some(cx.generation())).unwrap()
}
/// Get the parent of the current scope if it exists
pub fn parent_scope(self) -> Option<ScopeId> {
Runtime::with_scope(self, |cx| cx.parent_id())
.ok()
.flatten()
}
/// Check if the current scope is a descendant of the given scope
pub fn is_descendant_of(self, other: ScopeId) -> bool {
let mut current = self;
while let Some(parent) = current.parent_scope() {
if parent == other {
return true;
}
current = parent;
}
false
}
/// Mark the current scope as dirty, causing it to re-render
pub fn needs_update(self) {
Runtime::with_scope(self, |cx| cx.needs_update()).unwrap();
}
/// Create a subscription that schedules a future render for the reference component. Unlike [`Self::needs_update`], this function will work outside of the dioxus runtime.
///
/// ## Notice: you should prefer using [`crate::schedule_update_any`]
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
Runtime::with_scope(*self, |cx| cx.schedule_update()).unwrap()
}
/// Get the height of the current scope
pub fn height(self) -> u32 {
Runtime::with_scope(self, |cx| cx.height()).unwrap()
}
/// Run a closure inside of scope's runtime
#[track_caller]
pub fn in_runtime<T>(self, f: impl FnOnce() -> T) -> T {
Runtime::current()
.unwrap_or_else(|e| panic!("{}", e))
.on_scope(self, f)
}
/// Throw a [`CapturedError`] into a scope. The error will bubble up to the nearest [`ErrorBoundary`](crate::ErrorBoundary) or the root of the app.
///
/// # Examples
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// fn Component() -> Element {
/// let request = spawn(async move {
/// match reqwest::get("https://api.example.com").await {
/// Ok(_) => unimplemented!(),
/// // You can explicitly throw an error into a scope with throw_error
/// Err(err) => ScopeId::APP.throw_error(err)
/// }
/// });
///
/// unimplemented!()
/// }
/// ```
pub fn throw_error(self, error: impl Into<CapturedError> + 'static) {
let error = error.into();
if let Some(cx) = self.consume_context::<crate::ErrorContext>() {
cx.insert_error(error)
} else {
tracing::error!(
"Tried to throw an error into an error boundary, but failed to locate a boundary: {:?}",
error
)
}
}
/// Get the suspense context the current scope is in
pub fn suspense_context(&self) -> Option<SuspenseContext> {
Runtime::with_scope(*self, |cx| cx.suspense_boundary.suspense_context().cloned()).unwrap()
}
}