//! Public APIs for managing component state, tasks, and lifecycles. //! //! This module is separate from `Scope` to narrow what exactly is exposed to user code. //! //! We unsafely implement `send` for the VirtualDom, but those guarantees can only be use bumpalo::Bump; use crate::{innerlude::*, lazynodes::LazyNodes}; use std::{any::TypeId, ops::Deref, rc::Rc}; /// Components in Dioxus use the "Context" object to interact with their lifecycle. /// /// This lets components access props, schedule updates, integrate hooks, and expose shared state. /// /// Note: all of these methods are *imperative* - they do not act as hooks! They are meant to be used by hooks /// to provide complex behavior. For instance, calling "add_shared_state" on every render is considered a leak. This method /// exists for the `use_provide_state` hook to provide a shared state object. /// /// For the most part, the only method you should be using regularly is `render`. /// /// ## Example /// /// ```ignore /// #[derive(Properties)] /// struct Props { /// name: String /// } /// /// fn example(cx: Context) -> VNode { /// html! { ///
"Hello, {cx.name}"
/// } /// } /// ``` pub struct Context<'src> { pub scope: &'src ScopeInner, } impl<'src> Copy for Context<'src> {} impl<'src> Clone for Context<'src> { fn clone(&self) -> Self { Self { scope: self.scope } } } // We currently deref to props, but it might make more sense to deref to Scope? // This allows for code that takes cx.xyz instead of cx.props.xyz impl<'a> Deref for Context<'a> { type Target = &'a ScopeInner; fn deref(&self) -> &Self::Target { &self.scope } } impl<'src> Context<'src> { /// Create a subscription that schedules a future render for the reference component /// /// ## Notice: you should prefer using prepare_update and get_scope_id pub fn schedule_update(&self) -> Rc { self.scope.memoized_updater.clone() } pub fn needs_update(&self) { (self.scope.memoized_updater)() } /// Schedule an update for any component given its ScopeId. /// /// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method. /// /// This method should be used when you want to schedule an update for a component pub fn schedule_update_any(&self) -> Rc { self.scope.shared.schedule_any_immediate.clone() } /// Get the [`ScopeId`] of a mounted component. /// /// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted. pub fn scope_id(&self) -> ScopeId { self.scope.our_arena_idx } pub fn bump(&self) -> &'src Bump { let bump = &self.scope.frames.wip_frame().bump; bump } /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator. /// /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned. /// /// ## Example /// /// ```ignore /// fn Component(cx: Context<()>) -> VNode { /// // Lazy assemble the VNode tree /// let lazy_tree = html! {
"Hello World"
}; /// /// // Actually build the tree and allocate it /// cx.render(lazy_tree) /// } ///``` pub fn render( self, lazy_nodes: Option>, // lazy_nodes: Option) -> VNode<'src> + '_>>, ) -> Option> { let bump = &self.scope.frames.wip_frame().bump; let factory = NodeFactory { bump }; lazy_nodes.map(|f| f.call(factory)) } /// `submit_task` will submit the future to be polled. /// /// This is useful when you have some async task that needs to be progressed. /// /// This method takes ownership over the task you've provided, and must return (). This means any work that needs to /// happen must occur within the future or scheduled for after the future completes (through schedule_update ) /// /// ## Explanation /// Dioxus will step its internal event loop if the future returns if the future completes while waiting. /// /// Tasks can't return anything, but they can be controlled with the returned handle /// /// Tasks will only run until the component renders again. Because `submit_task` is valid for the &'src lifetime, it /// is considered "stable" /// /// /// pub fn submit_task(&self, task: FiberTask) -> TaskHandle { (self.scope.shared.submit_task)(task) } /// This hook enables the ability to expose state to children further down the VirtualDOM Tree. /// /// This is a hook, so it should not be called conditionally! /// /// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`) /// so don't put it in a conditional. /// /// When the component is dropped, so is the context. Be aware of this behavior when consuming /// the context via Rc/Weak. /// /// # Example /// /// ``` /// struct SharedState(&'static str); /// /// static App: FC<()> = |(cx, props)|{ /// cx.provide_state(SharedState("world")); /// rsx!(cx, Child {}) /// } /// /// static Child: FC<()> = |(cx, props)|{ /// let state = cx.consume_state::(); /// rsx!(cx, div { "hello {state.0}" }) /// } /// ``` pub fn provide_state(self, value: T) where T: 'static, { self.scope .shared_contexts .borrow_mut() .insert(TypeId::of::(), Rc::new(value)) .map(|f| f.downcast::().ok()) .flatten(); } /// Try to retrieve a SharedState with type T from the any parent Scope. pub fn consume_state(self) -> Option> { let getter = &self.scope.shared.get_shared_context; let ty = TypeId::of::(); let idx = self.scope.our_arena_idx; getter(idx, ty).map(|f| f.downcast().unwrap()) } /// Create a new subtree with this scope as the root of the subtree. /// /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route /// the mutations to the correct window/portal/subtree. /// /// This method /// /// # Example /// /// ```rust /// static App: FC<()> = |(cx, props)| { /// todo!(); /// rsx!(cx, div { "Subtree {id}"}) /// }; /// ``` pub fn create_subtree(self) -> Option { self.scope.new_subtree() } /// Get the subtree ID that this scope belongs to. /// /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route /// the mutations to the correct window/portal/subtree. /// /// # Example /// /// ```rust /// static App: FC<()> = |(cx, props)| { /// let id = cx.get_current_subtree(); /// rsx!(cx, div { "Subtree {id}"}) /// }; /// ``` pub fn get_current_subtree(self) -> u32 { self.scope.subtree() } /// Store a value between renders /// /// This is *the* foundational hook for all other hooks. /// /// - Initializer: closure used to create the initial hook state /// - Runner: closure used to output a value every time the hook is used /// - Cleanup: closure used to teardown the hook once the dom is cleaned up /// /// /// # Example /// /// ```ignore /// // use_ref is the simplest way of storing a value between renders /// fn use_ref(initial_value: impl FnOnce() -> T) -> &RefCell { /// use_hook( /// || Rc::new(RefCell::new(initial_value())), /// |state| state, /// |_| {}, /// ) /// } /// ``` pub fn use_hook( self, initializer: Init, runner: Run, cleanup: Cleanup, ) -> Output where State: 'static, Output: 'src, Init: FnOnce(usize) -> State, Run: FnOnce(&'src mut State) -> Output, Cleanup: FnOnce(Box) + 'static, { // If the idx is the same as the hook length, then we need to add the current hook if self.scope.hooks.at_end() { self.scope.hooks.push_hook( initializer(self.scope.hooks.len()), Box::new(|raw| { // let s = raw.downcast::().unwrap(); cleanup(s); }), ); } runner(self.scope.hooks.next::().expect(HOOK_ERR_MSG)) } } const HOOK_ERR_MSG: &str = r###" Unable to retrieve the hook that was initialized at this index. Consult the `rules of hooks` to understand how to use hooks properly. You likely used the hook in a conditional. Hooks rely on consistent ordering between renders. Functions prefixed with "use" should never be called conditionally. "###;