//! 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 crate::innerlude::*; 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 Scope, } 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 Scope; fn deref(&self) -> &Self::Target { &self.scope } } impl<'src> Context<'src> { /// Access the children elements passed into the component /// /// This enables patterns where a component is passed children from its parent. /// /// ## Details /// /// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions /// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure /// on the props that takes Context. /// /// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other /// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's /// props are valid for the static lifetime. /// /// ## Example /// /// ```rust /// const App: FC<()> = |cx, props|{ /// cx.render(rsx!{ /// CustomCard { /// h1 {} /// p {} /// } /// }) /// } /// /// const CustomCard: FC<()> = |cx, props|{ /// cx.render(rsx!{ /// div { /// h1 {"Title card"} /// {cx.children()} /// } /// }) /// } /// ``` /// /// ## Notes: /// /// This method returns a "ScopeChildren" object. This object is copy-able and preserve the correct lifetime. pub fn children(&self) -> ScopeChildren<'src> { self.scope.child_nodes() } /// 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() } /// 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 } /// 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) -> VNode<'src>>( self, lazy_nodes: LazyNodes<'src, F>, ) -> DomTree<'src> { let bump = &self.scope.frames.wip_frame().bump; Some(lazy_nodes.into_vnode(NodeFactory { bump })) } /// `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.use_provide_state(|| SharedState("world")); /// rsx!(cx, Child {}) /// } /// /// static Child: FC<()> = |cx, props|{ /// let state = cx.use_consume_state::(); /// rsx!(cx, div { "hello {state.0}" }) /// } /// ``` pub fn use_provide_state(self, init: F) -> &'src Rc where T: 'static, F: FnOnce() -> T, { let is_initialized = self.use_hook( |_| false, |s| { let i = *s; *s = true; i }, |_| {}, ); if !is_initialized { let existing = self .scope .shared_contexts .borrow_mut() .insert(TypeId::of::(), Rc::new(init())); if existing.is_some() { log::warn!( "A shared state was replaced with itself. \ This is does not result in a panic, but is probably not what you are trying to do" ); } } self.use_consume_state().unwrap() } /// Uses a context, storing the cached value around /// /// If a context is not found on the first search, then this call will be "dud", always returning "None" even if a /// context was added later. This allows using another hook as a fallback /// pub fn use_consume_state(self) -> Option<&'src Rc> { struct UseContextHook(Option>); self.use_hook( move |_| { let getter = &self.scope.shared.get_shared_context; let ty = TypeId::of::(); let idx = self.scope.our_arena_idx; UseContextHook(getter(idx, ty).map(|f| f.downcast().unwrap())) }, move |hook| hook.0.as_ref(), |_| {}, ) } /// 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(&mut State) + '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| cleanup(raw.downcast_mut::().unwrap())), ); } runner(self.scope.hooks.next::().expect(HOOK_ERR_MSG)) } } const HOOK_ERR_MSG: &str = r###" Unable to retrive the hook that was initialized in 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. Any function prefixed with "use" should not be called conditionally. "###;