123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- //! Built-in hooks
- //!
- //! This module contains all the low-level built-in hooks that require first 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, cell::RefCell, future::Future, ops::Deref, rc::Rc};
- /// Awaits the given task, forcing the component to re-render when the value is ready.
- ///
- /// Returns the handle to the task and the value (if it is ready, else None).
- ///
- /// ```
- /// static Example: FC<()> = |(cx, props)| {
- /// let (task, value) = use_task(|| async {
- /// timer::sleep(Duration::from_secs(1)).await;
- /// "Hello World"
- /// });
- ///
- /// match contents {
- /// Some(contents) => rsx!(cx, div { "{title}" }),
- /// None => rsx!(cx, div { "Loading..." }),
- /// }
- /// };
- /// ```
- pub fn use_task<'src, Out, Fut, Init>(
- cx: Context<'src>,
- 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.schedule_update_any();
- let originator = cx.scope.our_arena_idx;
- let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
- *slot.as_ref().borrow_mut() = Some(output);
- updater(originator);
- originator
- })));
- 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>(
- cx: Context<'src>,
- 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,
- {
- /*
- General strategy:
- - Create a slot for the future to dump its output into
- - Create a new future feeding off the user's future that feeds the output into that slot
- - Submit that future as a task
- - Take the task handle id and attach that to our suspended node
- - when the hook runs, check if the value exists
- - if it does, then we can render the node directly
- - if it doesn't, then we render a suspended node along with with the callback and task id
- */
- cx.use_hook(
- move |_| {
- let value = Rc::new(RefCell::new(None));
- let slot = value.clone();
- let originator = cx.scope.our_arena_idx;
- let handle = cx.submit_task(Box::pin(task_initializer().then(
- move |output| async move {
- *slot.borrow_mut() = Some(Box::new(output) as Box<dyn Any>);
- originator
- },
- )));
- SuspenseHook { handle, value }
- },
- move |hook| match hook.value.borrow().as_ref() {
- Some(value) => {
- let out = value.downcast_ref::<Out>().unwrap();
- let sus = SuspendedContext {
- inner: Context { scope: cx.scope },
- };
- user_callback(sus, out)
- }
- None => {
- let value = hook.value.clone();
- cx.render(LazyNodes::new(|f| {
- let bump = f.bump();
- use bumpalo::boxed::Box as BumpBox;
- let f: &mut dyn FnMut(SuspendedContext<'src>) -> DomTree<'src> =
- bump.alloc(move |sus| {
- let val = value.borrow();
- let out = val
- .as_ref()
- .unwrap()
- .as_ref()
- .downcast_ref::<Out>()
- .unwrap();
- user_callback(sus, out)
- });
- let callback = unsafe { BumpBox::from_raw(f) };
- VNode::Suspended(bump.alloc(VSuspended {
- dom_id: empty_cell(),
- task_id: hook.handle.our_id,
- callback: RefCell::new(Some(callback)),
- }))
- }))
- }
- },
- |_| {},
- )
- }
- pub(crate) struct SuspenseHook {
- pub handle: TaskHandle,
- pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
- }
- 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 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<Option<T>>);
- impl<'a, T> Deref for NodeRef<'a, T> {
- type Target = RefCell<Option<T>>;
- fn deref(&self) -> &Self::Target {
- self.0
- }
- }
- pub fn use_node_ref<T, P>(cx: Context) -> NodeRef<T> {
- cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f }, |_| {})
- }
|