123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- //! Built-in hooks
- //!
- //! This module contains all the low-level built-in hooks that require 1st party support to work.
- //!
- //! Hooks:
- //! - use_hook
- //! - use_state_provider
- //! - use_state_consumer
- //! - use_task
- //! - use_suspense
- use crate::innerlude::*;
- use futures_util::FutureExt;
- use std::{
- any::{Any, TypeId},
- cell::{Cell, RefCell},
- future::Future,
- rc::Rc,
- };
- /// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
- ///
- /// This is a hook, so it may 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.
- ///
- ///
- ///
- pub fn use_provide_state<'src, Pr, T, F>(cx: Context<'src, Pr>, init: F) -> &'src Rc<T>
- where
- T: 'static,
- F: FnOnce() -> T,
- {
- let ty = TypeId::of::<T>();
- let contains_key = cx.scope.shared_contexts.borrow().contains_key(&ty);
- let is_initialized = cx.use_hook(
- |_| false,
- |s| {
- let i = s.clone();
- *s = true;
- i
- },
- |_| {},
- );
- match (is_initialized, contains_key) {
- // Do nothing, already initialized and already exists
- (true, true) => {}
- // Needs to be initialized
- (false, false) => {
- log::debug!("Initializing context...");
- cx.add_shared_state(init());
- log::info!(
- "There are now {} shared contexts for scope {:?}",
- cx.scope.shared_contexts.borrow().len(),
- cx.scope.our_arena_idx,
- );
- }
- _ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
- };
- use_consume_state::<T, _>(cx)
- }
- /// There are hooks going on here!
- pub fn use_consume_state<'src, T: 'static, P>(cx: Context<'src, P>) -> &'src Rc<T> {
- use_try_consume_state::<T, _>(cx).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_try_consume_state<'src, T: 'static, P>(cx: Context<'src, P>) -> Option<&'src Rc<T>> {
- struct UseContextHook<C>(Option<Rc<C>>);
- cx.use_hook(
- move |_| UseContextHook(cx.consume_shared_state::<T>()),
- move |hook| hook.0.as_ref(),
- |_| {},
- )
- }
- /// Awaits the given task, forcing the component to re-render when the value is ready.
- ///
- ///
- ///
- ///
- pub fn use_task<'src, Out, Fut, Init, P>(
- cx: Context<'src, P>,
- task_initializer: Init,
- ) -> (&'src TaskHandle, &'src Option<Out>)
- where
- Out: 'static,
- Fut: Future<Output = Out> + 'static,
- Init: FnOnce() -> Fut + 'src,
- {
- struct TaskHook<T> {
- handle: TaskHandle,
- task_dump: Rc<RefCell<Option<T>>>,
- value: Option<T>,
- }
- // whenever the task is complete, save it into th
- cx.use_hook(
- move |_| {
- let task_fut = task_initializer();
- let task_dump = Rc::new(RefCell::new(None));
- let slot = task_dump.clone();
- let updater = cx.prepare_update();
- let update_id = cx.get_scope_id();
- let originator = cx.scope.our_arena_idx.clone();
- let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
- *slot.as_ref().borrow_mut() = Some(output);
- updater(update_id);
- EventTrigger {
- event: VirtualEvent::AsyncEvent {
- should_rerender: false,
- },
- originator,
- priority: EventPriority::Low,
- real_node_id: None,
- }
- })));
- TaskHook {
- task_dump,
- value: None,
- handle,
- }
- },
- |hook| {
- if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
- hook.value = Some(val);
- }
- (&hook.handle, &hook.value)
- },
- |_| {},
- )
- }
- /// Asynchronously render new nodes once the given future has completed.
- ///
- /// # Easda
- ///
- ///
- ///
- ///
- /// # Example
- ///
- ///
- pub fn use_suspense<'src, Out, Fut, Cb, P>(
- cx: Context<'src, P>,
- task_initializer: impl FnOnce() -> Fut,
- user_callback: Cb,
- ) -> DomTree<'src>
- where
- Fut: Future<Output = Out> + 'static,
- Out: 'static,
- Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> DomTree<'a> + 'static,
- {
- cx.use_hook(
- move |hook_idx| {
- let value = Rc::new(RefCell::new(None));
- let dom_node_id = Rc::new(empty_cell());
- let domnode = dom_node_id.clone();
- let slot = value.clone();
- let callback: SuspendedCallback = Box::new(move |ctx: SuspendedContext| {
- let v: std::cell::Ref<Option<Box<dyn Any>>> = slot.as_ref().borrow();
- match v.as_ref() {
- Some(a) => {
- let v: &dyn Any = a.as_ref();
- let real_val = v.downcast_ref::<Out>().unwrap();
- user_callback(ctx, real_val)
- }
- None => {
- //
- Some(VNode {
- key: None,
- kind: VNodeKind::Suspended(VSuspended {
- node: domnode.clone(),
- }),
- })
- }
- }
- });
- let originator = cx.scope.our_arena_idx.clone();
- let task_fut = task_initializer();
- let domnode = dom_node_id.clone();
- let slot = value.clone();
- cx.submit_task(Box::pin(task_fut.then(move |output| async move {
- // When the new value arrives, set the hooks internal slot
- // Dioxus will call the user's callback to generate new nodes outside of the diffing system
- *slot.borrow_mut() = Some(Box::new(output) as Box<dyn Any>);
- EventTrigger {
- event: VirtualEvent::SuspenseEvent { hook_idx, domnode },
- originator,
- priority: EventPriority::Low,
- real_node_id: None,
- }
- })));
- SuspenseHook {
- value,
- callback,
- dom_node_id,
- }
- },
- move |hook| {
- let cx = Context {
- scope: &cx.scope,
- props: &(),
- };
- let csx = SuspendedContext { inner: cx };
- (&hook.callback)(csx)
- },
- |_| {},
- )
- }
- pub(crate) struct SuspenseHook {
- pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
- pub callback: SuspendedCallback,
- pub dom_node_id: Rc<Cell<Option<ElementId>>>,
- }
- type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> DomTree<'a>>;
- pub struct SuspendedContext<'a> {
- pub(crate) inner: Context<'a, ()>,
- }
- impl<'src> SuspendedContext<'src> {
- pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
- self,
- lazy_nodes: LazyNodes<'src, F>,
- ) -> DomTree<'src> {
- let scope_ref = self.inner.scope;
- let bump = &self.inner.scope.frames.wip_frame().bump;
- Some(lazy_nodes.into_vnode(NodeFactory { bump }))
- }
- }
- #[derive(Clone, Copy)]
- pub struct NodeRef<'src, T: 'static>(&'src RefCell<T>);
- // impl NodeRef<'src, T> {
- // fn set_ref(&self, new: Box<dyn Any>)
- // }
- // impl<'a, T> std::ops::Deref for NodeRef<'a, T> {
- // type Target = Option<&'a T>;
- // fn deref(&self) -> &Self::Target {
- // // &self.node
- // }
- // }
- pub fn use_node_ref<T, P>(cx: Context<P>) -> NodeRef<T> {
- cx.use_hook(
- |f| {},
- |f| {
- //
- todo!()
- // NodeRef {}
- },
- |f| {
- //
- },
- )
- }
|