Browse Source

Feat:
- integrate subscription service into context.
- Update documentation

Jonathan Kelley 4 năm trước cách đây
mục cha
commit
204f0d9f16

+ 18 - 3
README.md

@@ -7,11 +7,26 @@
 
 # About
 
-Dioxus is a new approach for creating performant cross platform user experiences in Rust. In Dioxus, the UI is represented as a tree of Virtual Nodes not bound to any specific renderer. Instead, external renderers can leverage Dioxus' virtual DOM and event system as a source of truth for rendering to a medium of their choice. Developers experienced with building react-based experiences should feel comfortable with Dioxus.
+Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust.
 
-Dioxus was built in a way to facilitate powerful external renderers - especially designed for the web, servers, desktop, and hybrid approaches like Dioxus Liveview.
+```rust
+static Example: FC<()> = |ctx| {
+    let (val1, set_val1) = use_state(&ctx, || "___?");
+
+    ctx.view(html! {
+        <div>
+            <button onclick={move |_| set_val1("world")}> "world" </button>
+            <button onclick={move |_| set_val1("dioxus")}> "dioxus" </button>
+            <div>
+                <p> "Hello, {val1}" </p>
+            </div>
+        </div>
+    })
+};
+```
+The primary Dioxus crate is agnostic to platform and is meant to be configured with external renderers for getting content to the screen. We have built renderers for Dioxus to serve WebApps, Desktop Apps, static pages, liveview, Android, and iOS.
 
-Dioxus is supported by Dioxus Labs, a company providing end-to-end services for building, testing, deploying, and managing Dioxus apps on all supported platforms.
+Dioxus is supported by Dioxus Labs, a company providing end-to-end services for building, testing, deploying, and managing Dioxus apps on all supported platforms, designed especially for your next startup. 
 
 ## Features
 Dioxus' goal is to be the most advanced UI system for Rust, targeting isomorphism and hybrid approaches. Our goal is to eliminate context-switching for cross-platform development - both in UI patterns and programming language. Hooks and components should work *everywhere* without compromise.

+ 4 - 2
docs/posts/01-hello-world.md

@@ -1,6 +1,6 @@
 # Hello, World!
 
-Dioxus should look and feel just like writing functional React components. In Dioxus, there are no class components with lifecycles. All state management is done via hooks. This encourages logic resusability and lessens the burden on Dioxus to maintain a non-breaking lifecycle API.
+Dioxus should look and feel just like writing functional React components. In Dioxus, there are no class components with lifecycles. All state management is done via hooks. This encourages logic reusability and lessens the burden on Dioxus to maintain a non-breaking lifecycle API.
 
 ```rust
 #[derive(Properties, PartialEq)]
@@ -9,7 +9,9 @@ struct MyProps {
 }
 
 fn Example(ctx: Context<MyProps>) -> VNode {
-    html! { <div> "Hello {ctx.props().name}!" </div> }
+    ctx.view(html! { 
+        <div> "Hello {ctx.props().name}!" </div> 
+    })
 }
 ```
 

+ 1 - 1
packages/core/.vscode/settings.json

@@ -1,3 +1,3 @@
 {
-    "rust-analyzer.inlayHints.enable": true
+    "rust-analyzer.inlayHints.enable": false
 }

+ 125 - 0
packages/core/examples/listener.rs

@@ -0,0 +1,125 @@
+#![allow(unused, non_upper_case_globals)]
+
+use dioxus_core::prelude::*;
+
+fn main() {}
+
+static Example: FC<()> = |ctx| {
+    let (val1, set_val1) = use_state(&ctx, || "b1");
+
+    ctx.view(html! {
+        <div>
+            <button onclick={move |_| set_val1("b1")}> "Set value to b1" </button>
+            <button onclick={move |_| set_val1("b2")}> "Set value to b2" </button>
+            <button onclick={move |_| set_val1("b3")}> "Set value to b3" </button>
+            <div>
+                <p> "Value is: {val1}" </p>
+            </div>
+        </div>
+    })
+};
+
+use use_state_def::use_state;
+mod use_state_def {
+    use dioxus_core::prelude::*;
+    use std::{borrow::BorrowMut, cell::RefCell, ops::DerefMut, rc::Rc};
+
+    struct UseState<T: 'static> {
+        new_val: Rc<RefCell<Option<T>>>,
+        current_val: T,
+        caller: Box<dyn Fn(T) + 'static>,
+    }
+
+    /// Store state between component renders!
+    /// When called, this hook retrives a stored value and provides a setter to update that value.
+    /// When the setter is called, the component is re-ran with the new value.
+    ///
+    /// This is behaves almost exactly the same way as React's "use_state".
+    ///
+    /// Usage:
+    /// ```ignore
+    /// static Example: FC<()> = |ctx| {
+    ///     let (counter, set_counter) = use_state(ctx, || 0);
+    ///     let increment = || set_couter(counter + 1);
+    ///     let decrement = || set_couter(counter + 1);
+    ///
+    ///     html! {
+    ///         <div>
+    ///             <h1>"Counter: {counter}" </h1>
+    ///             <button onclick={increment}> "Increment" </button>
+    ///             <button onclick={decrement}> "Decrement" </button>
+    ///         </div>  
+    ///     }
+    /// }
+    /// ```
+    pub fn use_state<'b, 'a, P: Properties + 'static, T: 'static, F: FnOnce() -> T + 'static>(
+        ctx: &'b Context<'a, P>,
+        initial_state_fn: F,
+    ) -> (&'a T, &'a impl Fn(T)) {
+        ctx.use_hook(
+            move || UseState {
+                new_val: Rc::new(RefCell::new(None)),
+                current_val: initial_state_fn(),
+                caller: Box::new(|_| println!("setter called!")),
+            },
+            move |hook| {
+                let inner = hook.new_val.clone();
+                let scheduled_update = ctx.schedule_update();
+
+                // get ownership of the new val and replace the current with the new
+                // -> as_ref -> borrow_mut -> deref_mut -> take
+                // -> rc     -> &RefCell   -> RefMut    -> &Option<T> -> T
+                if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() {
+                    hook.current_val = new_val;
+                }
+
+                // todo: swap out the caller with a subscription call and an internal update
+                hook.caller = Box::new(move |new_val| {
+                    // update the setter with the new value
+                    let mut new_inner = inner.as_ref().borrow_mut();
+                    *new_inner = Some(new_val);
+
+                    // Ensure the component gets updated
+                    scheduled_update();
+                });
+
+                // box gets derefed into a ref which is then taken as ref with the hook
+                (&hook.current_val, &hook.caller)
+            },
+            |_| {},
+        )
+    }
+}
+
+mod use_ref_def {
+    use dioxus_core::prelude::*;
+    use std::{borrow::BorrowMut, cell::RefCell, ops::DerefMut, rc::Rc};
+
+    pub struct UseRef<T: 'static> {
+        current: RefCell<T>,
+    }
+    impl<T: 'static> UseRef<T> {
+        fn new(val: T) -> Self {
+            Self {
+                current: RefCell::new(val),
+            }
+        }
+
+        fn modify(&self, modifier: impl FnOnce(&mut T)) {
+            let mut val = self.current.borrow_mut();
+            let val_as_ref = val.deref_mut();
+            modifier(val_as_ref);
+        }
+    }
+
+    /// Store a mutable value between renders!
+    /// To read the value, borrow the ref.
+    /// To change it, use modify.
+    /// Modifications to this value do not cause updates to the component
+    pub fn use_ref<'a, P, T: 'static>(
+        ctx: &'a Context<'a, P>,
+        initial_state_fn: impl FnOnce() -> T + 'static,
+    ) -> &'a UseRef<T> {
+        ctx.use_hook(|| UseRef::new(initial_state_fn()), |state| &*state, |_| {})
+    }
+}

+ 137 - 136
packages/core/examples/macrosrc.rs

@@ -1,136 +1,137 @@
-#![allow(unused, non_upper_case_globals, non_snake_case)]
-use bumpalo::Bump;
-use dioxus_core::nodebuilder::*;
-use dioxus_core::prelude::*;
-use std::{collections::HashMap, future::Future, marker::PhantomData};
-
-fn main() {
-    let mut vdom = VirtualDom::new_with_props(
-        component,
-        Props {
-            blah: false,
-            text: "blah".into(),
-        },
-    );
-
-    vdom.progress();
-
-    let somet = String::from("asd");
-    let text = somet.as_str();
-
-    /*
-    this could be auto-generated via the macro
-    this props is allocated in this
-    but the component and props would like need to be cached
-    we could box this fn, abstracting away the props requirement and just keep the entrance and allocator requirement
-    How do we keep cached things around?
-    Need some sort of caching mechanism
-
-    how do we enter into a childscope from a parent scope?
-
-    Problems:
-    1: Comp props need to be stored somewhere so we can re-evalute components when they receive updates
-    2: Trees are not evaluated
-
-    */
-    let example_caller = move |ctx: &Bump| {
-        todo!()
-        // let p = Props { blah: true, text };
-        // let c = Context { props: &p };
-        // let r = component(&c);
-    };
-
-    // check the edit list
-}
-
-// ~~~ Text shared between components via props can be done with lifetimes! ~~~
-// Super duper efficient :)
-struct Props {
-    blah: bool,
-    text: String,
-    // text: &'src str,
-}
-impl Properties for Props {
-    fn new() -> Self {
-        todo!()
-    }
-}
-
-fn component<'a>(ctx: Context<'a, Props>) -> VNode<'a> {
-    // Write asynchronous rendering code that immediately returns a "suspended" VNode
-    // The concurrent API will then progress this component when the future finishes
-    // You can suspend the entire component, or just parts of it
-    let product_list = ctx.suspend(async {
-        // Suspend the rendering that completes when the future is done
-        match fetch_data().await {
-            Ok(data) => html! { <div> "success!" </div>},
-            Err(_) => html! { <div> "failure :(" </div>},
-        }
-    });
-
-    // todo!()
-    ctx.view(html! {
-        <div>
-            <h1> "Products" </h1>
-            <button> "hello!" </button>
-            // Subnodes can even be suspended
-            // When completely rendered, they won't cause the component itself to re-render, just their slot
-
-            // <p> { product_list } </p>
-        </div>
-    })
-}
-
-fn BuilderComp<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> {
-    // VNodes can be constructed via a builder or the html! macro
-    // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
-    // We can "view" them with Context for ultimate speed while inside components
-    ctx.view(|bump| {
-        div(bump)
-            .attr("class", "edit")
-            .child(text("Hello"))
-            .child(text(ctx.props.text.as_ref()))
-            .finish()
-    })
-}
-
-#[fc]
-fn EffcComp(ctx: &Context, name: &str) -> VNode {
-    // VNodes can be constructed via a builder or the html! macro
-    // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
-    // We can "view" them with Context for ultimate speed while inside components
-    // use "phase" style allocation;
-
-    ctx.view(html! {
-        <div>
-            // your template goes here
-            // feel free to directly use "name"
-        </div>
-    })
-}
-
-fn FullySuspended<'a>(ctx: &'a Context<Props>) -> VNode<'a> {
-    ctx.suspend(async {
-        let i: i32 = 0;
-
-        // full suspended works great with just returning VNodes!
-        // Feel free to capture the html! macro directly
-        // Anything returned here is automatically viewed
-        let tex = match i {
-            1 => html! { <div> </div> },
-            2 => html! { <div> </div> },
-            _ => html! { <div> </div> },
-        };
-
-        if ctx.props.blah {
-            html! { <div> </div> }
-        } else {
-            tex
-        }
-    })
-}
-
-/// An example of a datafetching service
-async fn fetch_data() -> Result<String, ()> {
-    todo!()
-}
+// #![allow(unused, non_upper_case_globals, non_snake_case)]
+// use bumpalo::Bump;
+// use dioxus_core::nodebuilder::*;
+// use dioxus_core::prelude::*;
+// use std::{collections::HashMap, future::Future, marker::PhantomData};
+
+fn main() {}
+// fn main() {
+//     let mut vdom = VirtualDom::new_with_props(
+//         component,
+//         Props {
+//             blah: false,
+//             text: "blah".into(),
+//         },
+//     );
+
+//     vdom.progress();
+
+//     let somet = String::from("asd");
+//     let text = somet.as_str();
+
+//     /*
+//     this could be auto-generated via the macro
+//     this props is allocated in this
+//     but the component and props would like need to be cached
+//     we could box this fn, abstracting away the props requirement and just keep the entrance and allocator requirement
+//     How do we keep cached things around?
+//     Need some sort of caching mechanism
+
+//     how do we enter into a childscope from a parent scope?
+
+//     Problems:
+//     1: Comp props need to be stored somewhere so we can re-evalute components when they receive updates
+//     2: Trees are not evaluated
+
+//     */
+//     let example_caller = move |ctx: &Bump| {
+//         todo!()
+//         // let p = Props { blah: true, text };
+//         // let c = Context { props: &p };
+//         // let r = component(&c);
+//     };
+
+//     // check the edit list
+// }
+
+// // ~~~ Text shared between components via props can be done with lifetimes! ~~~
+// // Super duper efficient :)
+// struct Props {
+//     blah: bool,
+//     text: String,
+//     // text: &'src str,
+// }
+// impl Properties for Props {
+//     fn new() -> Self {
+//         todo!()
+//     }
+// }
+
+// fn component<'a>(ctx: Context<'a, Props>) -> VNode<'a> {
+//     // Write asynchronous rendering code that immediately returns a "suspended" VNode
+//     // The concurrent API will then progress this component when the future finishes
+//     // You can suspend the entire component, or just parts of it
+//     let product_list = ctx.suspend(async {
+//         // Suspend the rendering that completes when the future is done
+//         match fetch_data().await {
+//             Ok(data) => html! { <div> "success!" </div>},
+//             Err(_) => html! { <div> "failure :(" </div>},
+//         }
+//     });
+
+//     // todo!()
+//     ctx.view(html! {
+//         <div>
+//             <h1> "Products" </h1>
+//             <button> "hello!" </button>
+//             // Subnodes can even be suspended
+//             // When completely rendered, they won't cause the component itself to re-render, just their slot
+
+//             // <p> { product_list } </p>
+//         </div>
+//     })
+// }
+
+// fn BuilderComp<'a>(ctx: Context<'a, Props>) -> VNode<'a> {
+//     // VNodes can be constructed via a builder or the html! macro
+//     // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
+//     // We can "view" them with Context for ultimate speed while inside components
+//     ctx.view(|bump| {
+//         div(bump)
+//             .attr("class", "edit")
+//             .child(text("Hello"))
+//             .child(text(ctx.props.text.as_ref()))
+//             .finish()
+//     })
+// }
+
+// #[fc]
+// fn EffcComp(ctx: Context, name: &str) -> VNode {
+//     // VNodes can be constructed via a builder or the html! macro
+//     // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
+//     // We can "view" them with Context for ultimate speed while inside components
+//     // use "phase" style allocation;
+
+//     ctx.view(html! {
+//         <div>
+//             // your template goes here
+//             // feel free to directly use "name"
+//         </div>
+//     })
+// }
+
+// fn FullySuspended<'a>(ctx: &'a Context<Props>) -> VNode<'a> {
+//     ctx.suspend(async {
+//         let i: i32 = 0;
+
+//         // full suspended works great with just returning VNodes!
+//         // Feel free to capture the html! macro directly
+//         // Anything returned here is automatically viewed
+//         let tex = match i {
+//             1 => html! { <div> </div> },
+//             2 => html! { <div> </div> },
+//             _ => html! { <div> </div> },
+//         };
+
+//         if ctx.props.blah {
+//             html! { <div> </div> }
+//         } else {
+//             tex
+//         }
+//     })
+// }
+
+// /// An example of a datafetching service
+// async fn fetch_data() -> Result<String, ()> {
+//     todo!()
+// }

+ 0 - 185
packages/core/examples/sketch.rs

@@ -1,185 +0,0 @@
-use bumpalo::Bump;
-use dioxus_core as dioxus;
-use dioxus_core::{
-    prelude::{html, Context, Properties, VElement, VNode, FC},
-    scope::Scope,
-};
-use std::{
-    any::Any,
-    borrow::BorrowMut,
-    cell::RefCell,
-    mem::swap,
-    ops::{Deref, DerefMut},
-    rc::Rc,
-};
-use std::{borrow::Borrow, sync::atomic::AtomicUsize};
-use typed_arena::Arena;
-
-fn main() {
-    let mut scope = Scope::new(component);
-
-    (0..5).for_each(|f| {
-        let ctx = scope.create_context();
-        component(ctx);
-    });
-}
-
-// we need to do something about props and context being borrowed from different sources....
-// kinda anooying
-/// use_ref creates a new value when the component is created and then borrows that value on every render
-fn component(ctx: Context<()>) -> VNode {
-    (0..10).for_each(|f| {
-        let r = use_ref(&ctx, move || f);
-    });
-    todo!()
-}
-
-pub struct UseRef<T: 'static> {
-    current: RefCell<T>,
-}
-impl<T: 'static> UseRef<T> {
-    fn new(val: T) -> Self {
-        Self {
-            current: RefCell::new(val),
-        }
-    }
-
-    fn modify(&self, modifier: impl FnOnce(&mut T)) {
-        let mut val = self.current.borrow_mut();
-        let val_as_ref = val.deref_mut();
-        modifier(val_as_ref);
-    }
-}
-
-/// Store a mutable value between renders!
-/// To read the value, borrow the ref.
-/// To change it, use modify.
-/// Modifications to this value do not cause updates to the component
-pub fn use_ref<'a, P, T: 'static>(
-    ctx: &'a Context<'a, P>,
-    initial_state_fn: impl FnOnce() -> T + 'static,
-) -> &'a UseRef<T> {
-    ctx.use_hook(
-        || UseRef::new(initial_state_fn()),
-        |state, _| &*state,
-        |_| {},
-    )
-}
-
-struct UseState<T> {
-    new_val: Rc<RefCell<Option<T>>>,
-    current_val: T,
-    caller: Box<dyn Fn(T) + 'static>,
-}
-
-/// Store state between component renders!
-/// When called, this hook retrives a stored value and provides a setter to update that value.
-/// When the setter is called, the component is re-ran with the new value.
-///
-/// This is behaves almost exactly the same way as React's "use_state".
-///
-/// Usage:
-/// ```ignore
-/// static Example: FC<()> = |ctx| {
-///     let (counter, set_counter) = use_state(ctx, || 0);
-///     let increment = || set_couter(counter + 1);
-///     let decrement = || set_couter(counter + 1);
-///
-///     html! {
-///         <div>
-///             <h1>"Counter: {counter}" </h1>
-///             <button onclick={increment}> "Increment" </button>
-///             <button onclick={decrement}> "Decrement" </button>
-///         </div>  
-///     }
-/// }
-/// ```
-pub fn use_state<'a, P: Properties + 'static, T: 'static, F: FnOnce() -> T + 'static>(
-    ctx: &'a Context<'a, P>,
-    initial_state_fn: F,
-) -> (&T, &impl Fn(T)) {
-    ctx.use_hook(
-        move || UseState {
-            new_val: Rc::new(RefCell::new(None)),
-            current_val: initial_state_fn(),
-            caller: Box::new(|_| println!("setter called!")),
-        },
-        move |hook, updater| {
-            let inner = hook.new_val.clone();
-
-            // todo: swap out the caller with a subscription call and an internal update
-            hook.caller = Box::new(move |new_val| {
-                // update the setter with the new value
-                let mut new_inner = inner.as_ref().borrow_mut();
-                *new_inner = Some(new_val);
-            });
-
-            // box gets derefed into a ref which is then taken as ref with the hook
-            (&hook.current_val, &hook.caller)
-        },
-        |_| {},
-    )
-}
-
-fn test_use_state(ctx: Context<()>) -> VNode {
-    let (val, set_val) = use_state(&ctx, || 10);
-
-    // gloriousness!
-    // closures are taken with ref to ctx :)
-    // Can freely use hooks
-    let handler_0: _ = || set_val(val + 1);
-    let handler_1: _ = || set_val(val + 10);
-    let handler_2: _ = || set_val(val + 100);
-
-    // these fns are captured, boxed into the bump arena, and then attached to the listeners
-    // the vnodes share the lifetime of these closures (and the hook data)
-    // whenever a listener wakes up, we take the reference directly from the bump arena and, with a small bit
-    // of unsafe code, execute the associated closure / listener function
-    // Those vnodes are then tossed out and new ones are installed, meaning and old references (potentially bad)
-    // are removed and UB is prevented from /affecting/ the program
-    {
-        VNode::text("blah")
-    }
-}
-
-#[allow(unused, non_upper_case_globals)]
-static UseStateBorrowedFc: FC<()> = |ctx| {
-    let (val, set_val) = use_state(&ctx, || 0);
-
-    let incr = || set_val(val + 1);
-    let decr = || set_val(val - 1);
-
-    ctx.view(html! {
-        <div>
-            <nav class="menu">
-                <button> "Increment" </button>
-                <button> "Decrement" </button>
-            </nav>
-            <p> "Current value: {val}" </p>
-        </div>
-    })
-};
-
-/// A useful component
-/// With some great documentation
-/// it practically speaks for itself.
-/// It's also only a server component :o
-fn use_state_borrowed(ctx: Context<()>) -> VNode {
-    let (val, set_val) = use_state(&ctx, || 0);
-
-    let incr = || set_val(val + 1);
-    let decr = || set_val(val - 1);
-
-    ctx.view(html! {
-        <div>
-            <nav class="menu">
-                <button> "Increment" </button>
-                <button> "Decrement" </button>
-            </nav>
-            <p> "Current value: {val}" </p>
-        </div>
-    })
-}
-
-// <button onclick=incr> { "Increment" } </button>
-// <button onclick=decr> { "Decrement" } </button>

+ 17 - 14
packages/core/src/context.rs

@@ -1,6 +1,6 @@
-use crate::nodes::VNode;
 use crate::prelude::*;
 use crate::scope::Hook;
+use crate::{inner::Scope, nodes::VNode};
 use bumpalo::Bump;
 use std::{
     any::TypeId, cell::RefCell, future::Future, marker::PhantomData, sync::atomic::AtomicUsize,
@@ -26,21 +26,22 @@ use std::{
 /// ```
 // todo: force lifetime of source into T as a valid lifetime too
 // it's definitely possible, just needs some more messing around
-pub struct Context<'src, T> {
+pub struct Context<'src, PropType> {
     /// Direct access to the properties used to create this component.
-    pub props: T,
+    pub props: PropType,
     pub idx: AtomicUsize,
 
     // Borrowed from scope
     pub(crate) arena: &'src typed_arena::Arena<Hook>,
     pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
+    pub(crate) components: &'src generational_arena::Arena<Scope>,
 
     // holder for the src lifetime
     // todo @jon remove this
     pub _p: std::marker::PhantomData<&'src ()>,
 }
 
-impl<'a, T> Context<'a, T> {
+impl<'a, PropType> Context<'a, PropType> {
     /// Access the children elements passed into the component
     pub fn children(&self) -> Vec<VNode> {
         todo!("Children API not yet implemented for component Context")
@@ -52,13 +53,17 @@ impl<'a, T> Context<'a, T> {
     }
 
     /// Create a subscription that schedules a future render for the reference component
-    pub fn subscribe(&self) -> impl FnOnce() -> () {
+    pub fn schedule_update(&self) -> impl Fn() -> () {
         todo!("Subscription API is not ready yet");
         || {}
     }
 
     /// 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(ctx: Context<Props>) -> VNode {
     ///     // Lazy assemble the VNode tree
@@ -68,7 +73,7 @@ impl<'a, T> Context<'a, T> {
     ///     ctx.view(lazy_tree)
     /// }
     ///```
-    pub fn view(&self, v: impl FnOnce(&'a Bump) -> VNode<'a>) -> VNode<'a> {
+    pub fn view(self, v: impl FnOnce(&'a Bump) -> VNode<'a>) -> VNode<'a> {
         todo!()
     }
 
@@ -83,12 +88,13 @@ impl<'a, T> Context<'a, T> {
     }
 
     /// use_hook provides a way to store data between renders for functional components.
-    pub fn use_hook<'comp, InternalHookState: 'static, Output: 'comp>(
-        &'comp self,
+    /// todo @jon: ensure the hook arena is stable with pin or is stable by default
+    pub fn use_hook<'internal, 'scope, InternalHookState: 'static, Output: 'internal>(
+        &'scope self,
         // The closure that builds the hook state
         initializer: impl FnOnce() -> InternalHookState,
         // The closure that takes the hookstate and returns some value
-        runner: impl FnOnce(&'comp mut InternalHookState, ()) -> Output,
+        runner: impl FnOnce(&'internal mut InternalHookState) -> Output,
         // The closure that cleans up whatever mess is left when the component gets torn down
         // TODO: add this to the "clean up" group for when the component is dropped
         cleanup: impl FnOnce(InternalHookState),
@@ -132,13 +138,10 @@ impl<'a, T> Context<'a, T> {
         - We don't expose the raw hook pointer outside of the scope of use_hook
         - The reference is tied to context, meaning it can only be used while ctx is around to free it
         */
-        let borrowed_hook: &'comp mut _ = unsafe { raw_hook.as_mut().unwrap() };
+        let borrowed_hook: &'internal mut _ = unsafe { raw_hook.as_mut().unwrap() };
 
         let internal_state = borrowed_hook.0.downcast_mut::<InternalHookState>().unwrap();
 
-        // todo: set up an updater with the subscription API
-        let updater = ();
-
-        runner(internal_state, updater)
+        runner(internal_state)
     }
 }

+ 87 - 0
packages/core/src/contextapi.rs

@@ -0,0 +1,87 @@
+//! Context API
+//!
+//! The context API provides a mechanism for components to grab
+//!
+//!
+//!
+
+use std::marker::PhantomPinned;
+
+/// Any item that works with app
+pub trait AppContext {}
+
+#[derive(Copy, Clone)]
+pub struct ContextGuard<'a, T> {
+    inner: *mut T,
+    _p: std::marker::PhantomData<&'a ()>,
+}
+
+impl<'a, PropType> super::context::Context<'a, PropType> {
+    /// # SAFETY ALERT
+    ///
+    /// The underlying context mechanism relies on mutating &mut T while &T is held by components in the tree.
+    /// By definition, this is UB. Therefore, implementing use_context should be done with upmost care to invalidate and
+    /// prevent any code where &T is still being held after &mut T has been taken and T has been mutated.
+    ///
+    /// While mutating &mut T while &T is captured by listeners, we can do any of:
+    ///     1) Prevent those listeners from being called and avoid "producing" UB values
+    ///     2) Delete instances of closures where &T is captured before &mut T is taken
+    ///     3) Make clones of T to preserve the original &T.
+    ///
+    /// To guarantee safe usage of state management solutions, we provide Dioxus-Reducer and Dioxus-Dataflow built on the
+    /// SafeContext API. This should provide as an example of how to implement context safely for 3rd party state management.
+    ///
+    /// It's important to recognize that while safety is a top concern for Dioxus, ergonomics do take prescendence.
+    /// Contrasting with the JS ecosystem, Rust is faster, but actually "less safe". JS is, by default, a "safe" language.
+    /// However, it does not protect you against data races: the primary concern for 3rd party implementers of Context.
+    ///
+    /// We guarantee that any &T will remain consistent throughout the life of the Virtual Dom and that
+    /// &T is owned by components owned by the VirtualDom. Therefore, it is impossible for &T to:
+    /// - be dangling or unaligned
+    /// - produce an invalid value
+    /// - produce uninitialized memory
+    ///
+    /// The only UB that is left to the implementer to prevent are Data Races.
+    ///
+    /// Here's a strategy that is UB:
+    /// 1. &T is handed out via use_context
+    /// 2. an event is reduced against the state
+    /// 3. An &mut T is taken
+    /// 4. &mut T is mutated.
+    ///
+    /// Now, any closures that caputed &T are subject to a data race where they might have skipped checks and UB
+    /// *will* affect the program.
+    ///
+    /// Here's a strategy that's not UB (implemented by SafeContext):
+    /// 1. ContextGuard<T> is handed out via use_context.
+    /// 2. An event is reduced against the state.
+    /// 3. The state is cloned.
+    /// 4. All subfield selectors are evaluated and then diffed with the original.
+    /// 5. Fields that have changed have their ContextGuard poisoned, revoking their ability to take &T.a.
+    /// 6. The affected fields of Context are mutated.
+    /// 7. Scopes with poisoned guards are regenerated so they can take &T.a again, calling their lifecycle.
+    ///
+    /// In essence, we've built a "partial borrowing" mechanism for Context objects.
+    ///
+    /// =================
+    ///       nb
+    /// =================
+    /// If you want to build a state management API directly and deal with all the unsafe and UB, we provide
+    /// `use_context_unchecked` with all the stability with *no* guarantess of Data Race protection. You're on
+    /// your own to not affect user applications.
+    ///
+    /// - Dioxus reducer is built on the safe API and provides a useful but slightly limited API.
+    /// - Dioxus Dataflow is built on the unsafe API and provides an even snazzier API than Dioxus Reducer.
+    pub fn use_context<C: AppContext>(&'a self) -> C {
+        todo!()
+    }
+
+    pub unsafe fn use_context_unchecked<C: AppContext>() {}
+}
+
+struct SafeContext<T> {
+    value: T,
+
+    // This context is pinned
+    _pinned: PhantomPinned,
+}

+ 1 - 0
packages/core/src/lib.rs

@@ -67,6 +67,7 @@
 
 pub mod component;
 pub mod context;
+pub mod contextapi;
 pub mod debug_renderer;
 pub mod events;
 pub mod nodebuilder;

+ 26 - 26
packages/core/src/nodebuilder.rs

@@ -336,7 +336,7 @@ where
     #[inline]
     pub fn on<F>(mut self, event: &'a str, callback: F) -> Self
     where
-        F: 'static + Fn(),
+        F: Fn(()) + 'a,
     {
         self.listeners.push(Listener {
             event,
@@ -1052,28 +1052,28 @@ pub fn attr<'a>(name: &'a str, value: &'a str) -> Attribute<'a> {
     Attribute { name, value }
 }
 
-// /// Create an event listener.
-// ///
-// /// `event` is the type of event to listen for, e.g. `"click"`. The `callback`
-// /// is the function that will be invoked when the event occurs.
-// ///
-// /// # Example
-// ///
-// /// ```no_run
-// /// use dodrio::{builder::*, bumpalo::Bump};
-// ///
-// /// let b = Bump::new();
-// ///
-// /// let listener = on(&b, "click", |root, vdom, event| {
-// ///     // do something when a click happens...
-// /// });
-// /// ```
-// pub fn on<'a, F>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a>
-// where
-//     F: Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static,
-// {
-//     Listener {
-//         event,
-//         callback: bump.alloc(callback),
-//     }
-// }
+/// Create an event listener.
+///
+/// `event` is the type of event to listen for, e.g. `"click"`. The `callback`
+/// is the function that will be invoked when the event occurs.
+///
+/// # Example
+///
+/// ```no_run
+/// use dodrio::{builder::*, bumpalo::Bump};
+///
+/// let b = Bump::new();
+///
+/// let listener = on(&b, "click", |root, vdom, event| {
+///     // do something when a click happens...
+/// });
+/// ```
+pub fn on<'a, F>(bump: &'a Bump, event: &'a str, callback: F) -> Listener<'a>
+where
+    F: Fn(()) + 'a,
+{
+    Listener {
+        event,
+        callback: bump.alloc(callback),
+    }
+}

+ 1 - 1
packages/core/src/nodes.rs

@@ -178,7 +178,7 @@ mod velement {
         /// The type of event to listen for.
         pub(crate) event: &'a str,
         /// The callback to invoke when the event happens.
-        pub(crate) callback: &'a (dyn Fn()),
+        pub(crate) callback: &'a (dyn Fn(())),
     }
 
     /// The key for keyed children.

+ 17 - 8
packages/core/src/scope.rs

@@ -1,6 +1,7 @@
 use crate::nodes::VNode;
 use crate::prelude::*;
 use bumpalo::Bump;
+use generational_arena::Index;
 use std::{
     any::TypeId, cell::RefCell, future::Future, marker::PhantomData, sync::atomic::AtomicUsize,
 };
@@ -18,35 +19,43 @@ pub struct Scope {
     hooks: RefCell<Vec<*mut Hook>>,
     hook_arena: typed_arena::Arena<Hook>,
 
+    // Map to the parent
+    parent: Option<Index>,
+
     props_type: TypeId,
     caller: *const i32,
 }
 
 impl Scope {
     // create a new scope from a function
-    pub fn new<T: 'static>(f: FC<T>) -> Self {
+    pub fn new<T: 'static>(f: FC<T>, parent: Option<Index>) -> Self {
         // Capture the props type
         let props_type = TypeId::of::<T>();
-        let arena = typed_arena::Arena::new();
+        let hook_arena = typed_arena::Arena::new();
         let hooks = RefCell::new(Vec::new());
 
         let caller = f as *const i32;
 
         Self {
-            hook_arena: arena,
+            hook_arena,
             hooks,
             props_type,
             caller,
+            parent,
         }
     }
 
-    pub fn create_context<T: Properties>(&mut self) -> Context<T> {
+    pub fn create_context<'runner, T: Properties>(
+        &'runner mut self,
+        components: &'runner generational_arena::Arena<Scope>,
+    ) -> Context<T> {
         Context {
             _p: PhantomData {},
             arena: &self.hook_arena,
             hooks: &self.hooks,
             idx: 0.into(),
             props: T::new(),
+            components,
         }
     }
 
@@ -54,7 +63,7 @@ impl Scope {
     /// This function downcasts the function pointer based on the stored props_type
     fn run<T: 'static>(&self, f: FC<T>) {}
 
-    fn call<T: Properties + 'static>(&mut self, val: T) {
+    fn call<'a, T: Properties + 'static>(&'a mut self, val: T) {
         if self.props_type == TypeId::of::<T>() {
             /*
             SAFETY ALERT
@@ -66,9 +75,9 @@ impl Scope {
             This is safe because we check that the generic type matches before casting.
             */
             let caller = unsafe { std::mem::transmute::<*const i32, FC<T>>(self.caller) };
-            let ctx = self.create_context::<T>();
-            // TODO: do something with these nodes
-            let nodes = caller(ctx);
+        // let ctx = self.create_context::<T>();
+        // // TODO: do something with these nodes
+        // let nodes = caller(ctx);
         } else {
             panic!("Do not try to use `call` on Scopes with the wrong props type")
         }

+ 1 - 1
packages/core/src/virtual_dom.rs

@@ -80,7 +80,7 @@ impl<P: Properties + 'static> VirtualDom<P> {
         let mut components = Arena::new();
 
         // Create a reference to the component in the arena
-        let base_scope = components.insert(Scope::new(root));
+        let base_scope = components.insert(Scope::new(root, None));
 
         // Create a new mount event with no root container
         let first_event = LifecycleEvent::mount(base_scope, None, 0);