Jonathan Kelley пре 4 година
родитељ
комит
ce33031

+ 3 - 3
Cargo.toml

@@ -4,16 +4,16 @@ members = [
     "packages/core-macro",
     "packages/core",
     "packages/web",
-    "packages/liveview",
-    "packages/3d",
     "packages/docsite",
+    "packages/ssr",
 ]
+# "packages/liveview",
+# "packages/3d",
 
 # Built-in
 # "packages/webview/client",
 # "packages/webview",
 # "packages/router",
-# "packages/ssr",
 # "packages/webview",
 # "packages/livehost",
 # "packages/vscode-ext",

+ 1 - 1
README.md

@@ -38,7 +38,7 @@ Dioxus can be used to deliver webapps, desktop apps, static pages, liveview apps
 ## Get Started with...
 <table style="width:100%" align="center">
     <tr >
-        <th><a href="http://github.com/jkelleyrtp/dioxus">WebApps</a></th>
+        <th><a href="http://github.com/jkelleyrtp/dioxus">Web</a></th>
         <th><a href="http://github.com/jkelleyrtp/dioxus">Desktop</a></th>
         <th><a href="http://github.com/jkelleyrtp/dioxus">Mobile</a></th>
         <th><a href="http://github.com/jkelleyrtp/dioxus">State Management</a></th>

+ 73 - 44
notes/CHANGELOG.md

@@ -1,63 +1,92 @@
+# Dioxus v0.1.0
+Welcome to the first iteration of the Dioxus Virtual DOM! This release brings support for:
+- Web via WASM
+- Desktop via webview integration
+- Server-rendering with custom Display Impl
+- Liveview (experimental)
+- Mobile (experimental)
+- State management
+- Build CLI
+----
+## Project: Initial VDOM support (TBD)
+> Get the initial VDom + Event System + Patching + Diffing + Component framework up and running
+> Get a demo working using just the web
+- [x] (Core) Migrate virtual node into new VNode type
+- [x] (Core) Arena allocate VNodes
+- [x] (Core) Allow VNodes to borrow arena contents
+- [x] (Core) Introduce the VDOM and patch API for 3rd party renderers
+- [x] (Core) Implement lifecycle
+- [x] (Core) Implement an event system 
+- [x] (Core) Implement child nodes, scope creation
+- [ ] (Core) Implement dirty tagging and compression
 
+## Project: QOL 
+> Make it easier to write components
+- [x] (Macro) Tweak event syntax to not be dependent on wasm32 target (just return regular closures which get boxed/alloced)
+- [x] (Macro) Tweak component syntax to accept a new custom element 
+- [ ] (Macro) Allow components to specify their props as function args  (not going to do)
 
+## Project: Hooks + Context + Subscriptions (TBD)
+> Implement the foundations for state management
+- [x] Implement context object
+- [x] Implement use_state (rewrite to use the use_reducer api like rei)
+- [x] Implement use_ref
+- [x] Implement use_context (only the API, not the state management solution)
+- [ ] Implement use_reducer (WIP)
 
-# Project: Live-View 🤲 🍨
-> Combine the server and client into a single file :) 
+## Project: String Render (TBD)
+> Implement a light-weight string renderer with basic caching 
+- [x] (Macro) Make VText nodes automatically capture and format IE allow "Text is {blah}"
+- [x] (SSR) Implement stateful 3rd party string renderer
 
+## Project: Web_sys renderer (TBD)
+- [x] WebSys edit interpreter
+- [x] Event system using async channels
+- [ ] Implement conversion of all event types into synthetic events
 
-# Project: Sanitization (TBD)
-> Improve code health
-- [ ] (Macro) Clippy sanity for html macro
-- [ ] (Macro) Error sanitization
+## Project: Web-View 🤲 🍨
+> Proof of concept: stream render edits from server to client
+- [x] Prove that the diffing and patching framework can support patch streaming
 
-# Project: Examples
+## Project: Examples
 > Get *all* the examples
 - [ ] (Examples) Tide example with templating
 
-# Project: State management 
-> Get some global state management installed with the hooks API
+## Project: State management 
+> Get some global state management installed with the hooks + context API
 
 
-# Project: Concurrency (TBD)
+## Project: Concurrency (TBD)
 > Ensure the concurrency model works well, play with lifetimes to check if it can be multithreaded + halted
+?
 
 
-# Project: Web-View 🤲 🍨
-> Proof of concept: stream render edits from server to client
-- [x] Prove that the diffing and patching framework can support patch streaming
+## Project: Mobile exploration
 
-# Project: Web_sys renderer (TBD)
-- [x] WebSys edit interpreter
-- [x] Event system using async channels
-- [ ] Implement conversion of all event types into synthetic events
-# Project: String Render (TBD)
-> Implement a light-weight string renderer with basic caching 
-- [x] (Macro) Make VText nodes automatically capture and format IE allow "Text is {blah}"
-- [ ] (SSR) Implement stateful 3rd party string renderer
 
-# Project: Hooks + Context + Subscriptions (TBD)
-> Implement the foundations for state management
-- [x] Implement context object
-- [x] Implement use_state (rewrite to use the use_reducer api like rei)
-- [x] Implement use_ref
-- [x] Implement use_context (only the API, not the state management solution)
-- [ ] Implement use_reducer (WIP)
+## Project: Live-View 🤲 🍨
+> Combine the server and client into a single file :) 
 
-# Project: QOL 
-> Make it easier to write components
-- [x] (Macro) Tweak event syntax to not be dependent on wasm32 target (just return regular closures which get boxed/alloced)
-- [x] (Macro) Tweak component syntax to accept a new custom element 
-- [ ] (Macro) Allow components to specify their props as function args
 
-# Project: Initial VDOM support (TBD)
-> Get the initial VDom + Event System + Patching + Diffing + Component framework up and running
-> Get a demo working using just the web
-- [x] (Core) Migrate virtual node into new VNode type
-- [x] (Core) Arena allocate VNodes
-- [x] (Core) Allow VNodes to borrow arena contents
-- [x] (Core) Introduce the VDOM and patch API for 3rd party renderers
-- [x] (Core) Implement lifecycle
-- [x] (Core) Implement an event system 
-- [x] (Core) Implement child nodes, scope creation
-- [ ] (Core) Implement dirty tagging and compression
+## Project: Sanitization (TBD)
+> Improve code health
+- [ ] (Macro) Clippy sanity for html macro
+- [ ] (Macro) Error sanitization
+
 
+## Outstanding todos:
+> anything missed so far
+- dirty tagging, compression
+- fragments
+- make ssr follow HTML spec
+- code health
+- miri tests
+- todo mvc
+- fix
+- node refs (postpone for future release?)
+- styling built-in (future release?)
+- key handler?
+- FC macro
+- Documentation overhaul
+- Website
+- keys on components

+ 31 - 4
packages/core-macro/src/rsxt.rs

@@ -16,7 +16,24 @@ use {
 // ==============================================
 pub struct RsxRender {
     custom_context: Option<Ident>,
-    el: Element,
+    root: RootOption,
+}
+
+enum RootOption {
+    // for lists of components
+    Fragment(),
+    Element(Element),
+    Component(Component),
+}
+
+impl ToTokens for RootOption {
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        match self {
+            RootOption::Fragment() => todo!(),
+            RootOption::Element(el) => el.to_tokens(tokens),
+            RootOption::Component(comp) => comp.to_tokens(tokens),
+        }
+    }
 }
 
 impl Parse for RsxRender {
@@ -33,14 +50,24 @@ impl Parse for RsxRender {
             })
             .ok();
 
-        let el: Element = input.parse()?;
-        Ok(Self { el, custom_context })
+        let forked = input.fork();
+        let name = forked.parse::<Ident>()?;
+
+        let root = match crate::util::is_valid_tag(&name.to_string()) {
+            true => input.parse::<Element>().map(|el| RootOption::Element(el)),
+            false => input.parse::<Component>().map(|c| RootOption::Component(c)),
+        }?;
+
+        Ok(Self {
+            root,
+            custom_context,
+        })
     }
 }
 
 impl ToTokens for RsxRender {
     fn to_tokens(&self, out_tokens: &mut TokenStream2) {
-        let new_toks = (&self.el).to_token_stream();
+        let new_toks = (&self.root).to_token_stream();
 
         // create a lazy tree that accepts a bump allocator
         let final_tokens = match &self.custom_context {

+ 2 - 5
packages/core/Cargo.toml

@@ -17,10 +17,7 @@ dioxus-core-macro = { path = "../core-macro", version = "0.1.1" }
 generational-arena = { version = "0.2.8", features = ["serde"] }
 
 # Bumpalo backs the VNode creation
-bumpalo = { version = "3.6.0", features = ["collections"] }
-
-# Backs hook data - might move away from this arena
-typed-arena = "2.0.1"
+bumpalo = { version = "3.6.0", features = ["collections", "boxed"] }
 
 # custom error type
 thiserror = "1.0.23"
@@ -34,7 +31,7 @@ longest-increasing-subsequence = "0.1.0"
 # internall used
 log = "0.4.14"
 
-serde = { version = "1.0.123", features = ["derive"], optional=true }
+serde = { version = "1.0.123", features = ["derive"], optional = true }
 
 [features]
 default = []

+ 2 - 2
packages/core/examples/borrowed.rs

@@ -22,7 +22,7 @@ struct ListItem {
 }
 
 fn app<'a>(ctx: Context<'a>, props: &Props) -> DomTree {
-    let val = use_state(&ctx, || 0);
+    let (val, set_val) = use_state(&ctx, || 0);
 
     ctx.render(dioxus::prelude::LazyNodes::new(move |c| {
         let mut root = builder::ElementBuilder::new(c, "div");
@@ -36,7 +36,7 @@ fn app<'a>(ctx: Context<'a>, props: &Props) -> DomTree {
                 // create the props with nothing but the fc<T>
                 fc_to_builder(ChildItem)
                     .item(child)
-                    .item_handler(val.setter())
+                    .item_handler(set_val)
                     .build(),
             ));
         }

+ 3 - 3
packages/core/examples/listener.rs

@@ -11,13 +11,13 @@ fn main() {
 }
 
 static Example: FC<()> = |ctx, props| {
-    let name = use_state(&ctx, || "...?");
+    let (name, set_name) = use_state(&ctx, || "...?");
 
     ctx.render(html! {
         <div>
             <h1> "Hello, {name}" </h1>
-            <button onclick={move |_| name.set("jack")}> "jack!" </button>
-            <button onclick={move |_| name.set("jill")}> "jill!" </button>
+            <button onclick={move |_| set_name("jack")}> "jack!" </button>
+            <button onclick={move |_| set_name("jill")}> "jill!" </button>
         </div>
     })
 };

+ 41 - 31
packages/core/src/context.rs

@@ -2,7 +2,7 @@ use crate::{nodebuilder::IntoDomTree, prelude::*};
 use crate::{nodebuilder::LazyNodes, nodes::VNode};
 use bumpalo::Bump;
 use hooks::Hook;
-use std::{cell::RefCell, future::Future, ops::Deref, rc::Rc, sync::atomic::AtomicUsize};
+use std::{cell::RefCell, future::Future, ops::Deref, pin::Pin, rc::Rc, sync::atomic::AtomicUsize};
 
 /// Components in Dioxus use the "Context" object to interact with their lifecycle.
 /// This lets components schedule updates, integrate hooks, and expose their context via the context api.
@@ -25,13 +25,14 @@ use std::{cell::RefCell, future::Future, ops::Deref, rc::Rc, sync::atomic::Atomi
 // 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> {
-    pub idx: AtomicUsize,
+    pub idx: RefCell<usize>,
 
     pub scope: ScopeIdx,
 
     // Borrowed from scope
-    pub(crate) arena: &'src typed_arena::Arena<Hook>,
-    pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
+    // pub(crate) arena: &'src typed_arena::Arena<Hook>,
+    pub(crate) hooks: &'src RefCell<Vec<Hook>>,
+    // pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
     pub(crate) bump: &'src Bump,
 
     pub listeners: &'src RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>,
@@ -114,14 +115,16 @@ impl<'a> Context<'a> {
 
 /// This module provides internal state management functionality for Dioxus components
 pub mod hooks {
+    use std::any::Any;
+
     use super::*;
 
     #[derive(Debug)]
-    pub struct Hook(pub Box<dyn std::any::Any>);
+    pub struct Hook(pub Pin<Box<dyn std::any::Any>>);
 
     impl Hook {
-        pub fn new(state: Box<dyn std::any::Any>) -> Self {
-            Self(state)
+        pub fn new<T: 'static>(state: T) -> Self {
+            Self(Box::pin(state))
         }
     }
 
@@ -139,28 +142,27 @@ pub mod hooks {
             // TODO: add this to the "clean up" group for when the component is dropped
             _cleanup: impl FnOnce(InternalHookState),
         ) -> Output {
-            let raw_hook = {
-                let idx = self.idx.load(std::sync::atomic::Ordering::Relaxed);
-
-                // Mutate hook list if necessary
-                let mut hooks = self.hooks.borrow_mut();
-
-                // Initialize the hook by allocating it in the typed arena.
-                // We get a reference from the arena which is owned by the component scope
-                // This is valid because "Context" is only valid while the scope is borrowed
-                if idx >= hooks.len() {
-                    let new_state = initializer();
-                    let boxed_state: Box<dyn std::any::Any> = Box::new(new_state);
-                    let hook = self.arena.alloc(Hook::new(boxed_state));
-
-                    // Push the raw pointer instead of the &mut
-                    // A "poor man's OwningRef"
-                    hooks.push(hook);
-                }
-                self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
+            let idx = *self.idx.borrow();
+
+            // Mutate hook list if necessary
+            let mut hooks = self.hooks.borrow_mut();
 
-                *hooks.get(idx).unwrap()
-            };
+            // Initialize the hook by allocating it in the typed arena.
+            // We get a reference from the arena which is owned by the component scope
+            // This is valid because "Context" is only valid while the scope is borrowed
+            if idx >= hooks.len() {
+                let new_state = initializer();
+                hooks.push(Hook::new(new_state));
+            }
+            *self.idx.borrow_mut() = 1;
+
+            let stable_ref = hooks.get_mut(idx).unwrap().0.as_mut();
+            let v = unsafe { Pin::get_unchecked_mut(stable_ref) };
+            let internal_state = v.downcast_mut::<InternalHookState>().unwrap();
+
+            // we extend the lifetime from borrowed in this scope to borrowed from self.
+            // This is okay because the hook is pinned
+            runner(unsafe { &mut *(internal_state as *mut _) })
 
             /*
             ** UNSAFETY ALERT **
@@ -178,11 +180,17 @@ pub mod hooks {
             - 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: &'a mut _ = unsafe { raw_hook.as_mut().unwrap() };
+            // let raw_hook: &'scope mut _ = unsafe { &mut *raw_hook };
+            // let p = raw_hook.0.downcast_mut::<InternalHookState>();
+            // let r = p.unwrap();
+            // let v = unsafe { Pin::get_unchecked_mut(raw_hook) };
+            // // let carreied_ref: &'scope mut dyn Any = unsafe { &mut *v };
+            // let internal_state = v.downcast_mut::<InternalHookState>().unwrap();
 
-            let internal_state = borrowed_hook.0.downcast_mut::<InternalHookState>().unwrap();
+            // let real_internal = unsafe { internal_state as *mut _ };
 
-            runner(internal_state)
+            // runner(unsafe { &mut *real_internal })
+            // runner(internal_state)
         }
     }
 }
@@ -247,6 +255,8 @@ Context should *never* be dangling!. If a Context is torn down, so should anythi
         pub fn use_context<I, O>(&'a self, _narrow: impl Fn(&'_ I) -> &'_ O) -> RemoteState<O> {
             todo!()
         }
+
+        pub fn create_context<T: 'static>(&self, creator: impl FnOnce() -> T) {}
     }
 
     /// # SAFETY ALERT

+ 99 - 3
packages/core/src/hooks.rs

@@ -5,6 +5,7 @@
 //! - [ ] use_reducer
 //! - [ ] use_effect
 
+pub use new_use_state_def::use_state_new;
 pub use use_reducer_def::use_reducer;
 pub use use_ref_def::use_ref;
 pub use use_state_def::use_state;
@@ -13,15 +14,94 @@ mod use_state_def {
     use crate::innerlude::*;
     use std::{
         cell::RefCell,
-        fmt::Display,
         ops::{Deref, 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<'a, 'c, T: 'static, F: FnOnce() -> T>(
+        ctx: &'c Context<'a>,
+        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| {
+                log::debug!("Use_state set called");
+                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 new_use_state_def {
+    use crate::innerlude::*;
+    use std::{
+        cell::RefCell,
+        fmt::{Debug, Display},
+        ops::{Deref, DerefMut},
+        pin::Pin,
+        rc::Rc,
+    };
+
     pub struct UseState<T: 'static> {
         modifier: Rc<RefCell<Option<Box<dyn FnOnce(&mut T)>>>>,
         current_val: T,
         update: Box<dyn Fn() + 'static>,
+        setter: Box<dyn Fn(T) + 'static>,
+        // setter: Box<dyn Fn(T) + 'static>,
     }
 
     // #[derive(Clone, Copy)]
@@ -33,8 +113,11 @@ mod use_state_def {
 
     impl<'a, T: 'static> UseState<T> {
         pub fn setter(&self) -> &dyn Fn(T) {
-            todo!()
+            &self.setter
+            // let r = self.setter.as_mut();
+            // unsafe { Pin::get_unchecked_mut(r) }
         }
+
         pub fn set(&self, new_val: T) {
             self.modify(|f| *f = new_val);
         }
@@ -84,7 +167,7 @@ mod use_state_def {
     ///     }
     /// }
     /// ```
-    pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
+    pub fn use_state_new<'a, 'c, T: 'static, F: FnOnce() -> T>(
         ctx: &'c Context<'a>,
         initial_state_fn: F,
     ) -> &'a UseState<T> {
@@ -93,19 +176,32 @@ mod use_state_def {
                 modifier: Rc::new(RefCell::new(None)),
                 current_val: initial_state_fn(),
                 update: Box::new(|| {}),
+                setter: Box::new(|_| {}),
             },
             move |hook| {
+                log::debug!("addr of hook: {:#?}", hook as *const _);
                 let scheduled_update = ctx.schedule_update();
 
+                // log::debug!("Checking if new value {:#?}", &hook.current_val);
                 // 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.modifier.as_ref().borrow_mut().deref_mut().take() {
+                    // log::debug!("setting prev {:#?}", &hook.current_val);
                     (new_val)(&mut hook.current_val);
+                    // log::debug!("setting new value {:#?}", &hook.current_val);
                 }
 
                 hook.update = Box::new(move || scheduled_update());
 
+                let modifier = hook.modifier.clone();
+                hook.setter = Box::new(move |new_val: T| {
+                    let mut slot = modifier.as_ref().borrow_mut();
+
+                    let slot2 = slot.deref_mut();
+                    *slot2 = Some(Box::new(move |old: &mut T| *old = new_val));
+                });
+
                 &*hook
             },
             |_| {},

+ 7 - 31
packages/core/src/nodebuilder.rs

@@ -566,6 +566,11 @@ where
         std::iter::once(self)
     }
 }
+impl<'a> IntoDomTree<'a> for () {
+    fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
+        VNode::Suspended
+    }
+}
 
 #[test]
 fn test_iterator_of_nodes<'b>() {
@@ -646,9 +651,6 @@ pub fn text2<'a>(contents: bumpalo::collections::String<'a>) -> VNode<'a> {
     let f: &'a str = contents.into_bump_str();
     VNode::text(f)
 }
-// pub fn text<'a>(contents: &'a str) -> VNode<'a> {
-//     VNode::text(contents)
-// }
 
 /// Construct an attribute for an element.
 ///
@@ -666,35 +668,9 @@ pub fn attr<'a>(name: &'static 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 dioxus::{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, 'b>(
-//     // pub fn on<'a, F: 'static>(
-//     bump: &'a Bump,
-//     event: &'static str,
-//     callback: impl Fn(VirtualEvent) + 'a,
-// ) -> Listener<'a> {
-//     Listener {
-//         event,
-//         callback: bump.alloc(callback),
-//     }
-// }
-
 pub fn virtual_child<'a, T: Properties + 'a>(ctx: &NodeCtx<'a>, f: FC<T>, p: T) -> VNode<'a> {
+    // currently concerned about if props have a custom drop implementation
+    // might override it with the props macro
     let propsd: &'a mut _ = ctx.bump.alloc(p);
     VNode::Component(crate::nodes::VComponent::new(f, propsd))
 }

+ 7 - 7
packages/core/src/scope.rs

@@ -36,9 +36,9 @@ pub struct Scope {
     // These two could be combined with "OwningRef" to remove unsafe usage
     // or we could dedicate a tiny bump arena just for them
     // could also use ourborous
-    pub hooks: RefCell<Vec<*mut Hook>>,
+    pub hooks: RefCell<Vec<Hook>>,
 
-    pub hook_arena: typed_arena::Arena<Hook>,
+    pub hook_arena: Vec<Hook>,
 
     // Unsafety:
     // - is self-refenrential and therefore needs to point into the bump
@@ -63,7 +63,7 @@ impl Scope {
 
         Self {
             caller: broken_caller,
-            hook_arena: typed_arena::Arena::new(),
+            hook_arena: Vec::new(),
             hooks: RefCell::new(Vec::new()),
             frames: ActiveFrame::new(),
             children: HashSet::new(),
@@ -92,7 +92,7 @@ impl Scope {
         };
 
         let ctx: Context<'b> = Context {
-            arena: &self.hook_arena,
+            // arena: &self.hook_arena,
             hooks: &self.hooks,
             bump: &frame.bump,
             idx: 0.into(),
@@ -145,7 +145,7 @@ impl Scope {
             // Run the callback with the user event
             log::debug!("Running listener");
             listener(source);
-            log::debug!("Running listener");
+            log::debug!("Listener finished");
 
             // drain all the event listeners
             // if we don't, then they'll stick around and become invalid
@@ -203,7 +203,7 @@ impl ActiveFrame {
         }
     }
 
-    fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
+    pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
         let raw_node = match *self.idx.borrow() & 1 == 0 {
             true => &self.frames[0],
             false => &self.frames[1],
@@ -217,7 +217,7 @@ impl ActiveFrame {
         }
     }
 
-    fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
+    pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
         let raw_node = match *self.idx.borrow() & 1 != 0 {
             true => &self.frames[0],
             false => &self.frames[1],

+ 25 - 4
packages/core/src/virtual_dom.rs

@@ -6,6 +6,8 @@ use generational_arena::Arena;
 use std::{
     any::TypeId,
     borrow::{Borrow, BorrowMut},
+    cell::RefCell,
+    collections::{BTreeMap, BTreeSet},
     rc::{Rc, Weak},
 };
 use thiserror::private::AsDynError;
@@ -19,11 +21,13 @@ pub(crate) type OpaqueComponent<'a> = dyn for<'b> Fn(Context<'b>) -> DomTree + '
 pub struct VirtualDom {
     /// All mounted components are arena allocated to make additions, removals, and references easy to work with
     /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
-    components: Arena<Scope>,
+    pub components: Arena<Scope>,
 
     /// The index of the root component.
     /// Will not be ready if the dom is fresh
-    base_scope: ScopeIdx,
+    pub base_scope: ScopeIdx,
+
+    pub(crate) update_schedule: Rc<RefCell<Vec<HierarchyMarker>>>,
 
     // a strong allocation to the "caller" for the original props
     #[doc(hidden)]
@@ -70,6 +74,7 @@ impl VirtualDom {
             components,
             _root_caller: root_caller,
             base_scope,
+            update_schedule: Rc::new(RefCell::new(Vec::new())),
             _root_prop_type: TypeId::of::<P>(),
         }
     }
@@ -85,13 +90,14 @@ impl VirtualDom {
         let mut diff_machine = DiffMachine::new();
 
         let very_unsafe_components = &mut self.components as *mut generational_arena::Arena<Scope>;
-        let mut component = self
+        let component = self
             .components
             .get_mut(self.base_scope)
             .ok_or_else(|| Error::FatalInternal("Acquring base component should never fail"))?;
         component.run_scope()?;
         diff_machine.diff_node(component.old_frame(), component.new_frame());
 
+        // log::debug!("New events generated: {:#?}", diff_machine.lifecycle_events);
         // chew down the the lifecycle events until all dirty nodes are computed
         while let Some(event) = diff_machine.lifecycle_events.pop_front() {
             match event {
@@ -119,6 +125,7 @@ impl VirtualDom {
                     }
                 }
                 LifeCycleEvent::PropsChanged { caller, id, scope } => {
+                    log::debug!("PROPS CHANGED");
                     let idx = scope.upgrade().unwrap().as_ref().borrow().unwrap();
                     unsafe {
                         let p = &mut *(very_unsafe_components);
@@ -131,6 +138,7 @@ impl VirtualDom {
                     // break 'render;
                 }
                 LifeCycleEvent::SameProps { caller, id, scope } => {
+                    log::debug!("SAME PROPS RECEIVED");
                     //
                     // break 'render;
                 }
@@ -181,7 +189,15 @@ impl VirtualDom {
             .expect("Borrowing should not fail");
 
         component.call_listener(event);
-        component.run_scope()?;
+
+        /*
+        -> call listener
+        -> sort through accumulated queue by the top
+        -> run each component, running its children
+        -> add new updates to the tree of updates (these are dirty)
+        ->
+        */
+        // component.run_scope()?;
 
         // let mut diff_machine = DiffMachine::new();
         // let mut diff_machine = DiffMachine::new(&self.diff_bump);
@@ -192,3 +208,8 @@ impl VirtualDom {
         self.rebuild()
     }
 }
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct HierarchyMarker {
+    height: u32,
+}

+ 102 - 0
packages/ssr/index.html

@@ -0,0 +1,102 @@
+<div title="About W3Schools">
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 0
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 1
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 2
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 3
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 4
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 5
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 6
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 7
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 8
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 9
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 10
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 11
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 12
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 13
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 14
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 15
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 16
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 17
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 18
+        </p>
+    </div>
+    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
+        <p title="About W3Schools">
+            Hello world!: 19
+        </p>
+    </div>
+</div>

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

@@ -20,6 +20,7 @@
 //!
 
 use dioxus_core::prelude::{VNode, FC};
+pub mod tostring;
 
 pub mod prelude {
     pub use dioxus_core::prelude::*;

+ 84 - 0
packages/ssr/src/tostring.rs

@@ -0,0 +1,84 @@
+use std::fmt::Display;
+
+use dioxus_core::prelude::*;
+use dioxus_core::{nodes::VNode, prelude::ScopeIdx, virtual_dom::VirtualDom};
+
+struct SsrRenderer {
+    dom: VirtualDom,
+}
+
+impl Display for SsrRenderer {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let node = self
+            .dom
+            .components
+            .get(self.dom.base_scope)
+            .unwrap()
+            .frames
+            .current_head_node();
+
+        html_render(&self.dom, node, f)
+    }
+}
+
+// recursively walk the tree
+fn html_render(
+    dom: &VirtualDom,
+    node: &VNode,
+    f: &mut std::fmt::Formatter<'_>,
+) -> std::fmt::Result {
+    match node {
+        VNode::Element(el) => {
+            write!(f, "<{}", el.tag_name)?;
+            for attr in el.attributes {
+                write!(f, " {}=\"{}\"", attr.name, attr.value)?;
+            }
+            write!(f, ">\n")?;
+            for child in el.children {
+                html_render(dom, child, f)?;
+            }
+            write!(f, "\n</{}>", el.tag_name)?;
+            Ok(())
+        }
+        VNode::Text(t) => write!(f, "{}", t.text),
+        VNode::Suspended => todo!(),
+        VNode::Component(vcomp) => {
+            let id = vcomp.ass_scope.as_ref().borrow().unwrap();
+            let new_node = dom.components.get(id).unwrap().frames.current_head_node();
+            html_render(&dom, new_node, f)
+        }
+    }
+}
+
+#[test]
+fn test_serialize() {
+    let mut dom = VirtualDom::new(|ctx, props| {
+        ctx.render(rsx! {
+            div {
+                title: "About W3Schools"
+                {(0..20).map(|f| rsx!{
+                    div {
+                        title: "About W3Schools"
+                        style: "color:blue;text-align:center"
+                        class: "About W3Schools"
+                        p {
+                            title: "About W3Schools"
+                            "Hello world!: {f}"
+                        }
+                    }
+                })}
+            }
+        })
+    });
+    dom.rebuild();
+    let renderer = SsrRenderer { dom };
+
+    use std::fs::File;
+    use std::io::prelude::*;
+
+    let mut file = File::create("index.html").unwrap();
+    let buf = renderer.to_string();
+    // dbg!(buf);
+    file.write(buf.as_bytes());
+    // dbg!(renderer.to_string());
+}

+ 4 - 0
packages/web/Cargo.toml

@@ -55,3 +55,7 @@ debug = true
 
 [lib]
 crate-type = ["cdylib", "rlib"]
+
+
+[dev-dependencies]
+uuid = { version = "0.8.2", features = ["v4"] }

+ 3 - 3
packages/web/examples/deep.rs

@@ -10,7 +10,7 @@ fn main() {
 use components::CustomB;
 
 fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
-    let val = use_state(&ctx, || "abcdef".to_string());
+    let (val, set_val) = use_state(&ctx, || "abcdef".to_string());
 
     ctx.render(rsx! {
         div {
@@ -18,11 +18,11 @@ fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
             "CustomA {val}"
             button {
                 "Upper"
-                onclick: move |_| val.set(val.to_ascii_uppercase())
+                onclick: move |_| set_val(val.to_ascii_uppercase())
             }
             button {
                 "Lower"
-                onclick: move |_| val.set(val.to_ascii_lowercase())
+                onclick: move |_| set_val(val.to_ascii_lowercase())
             }
             CustomB {
                 val: val.as_ref()

+ 2 - 2
packages/web/examples/infer.rs

@@ -12,10 +12,10 @@ fn main() {
 
 // this is a component
 static Example: FC<()> = |ctx, _props| {
-    let event = use_state(&ctx, || None);
+    let (event, set_event) = use_state(&ctx, || None);
 
     let handler = move |evt: MouseEvent| {
-        event.set(Some(evt));
+        set_event(Some(evt));
     };
 
     log::info!("hello world");

+ 4 - 4
packages/web/examples/jackjill.rs

@@ -10,7 +10,7 @@ fn main() {
 }
 
 static Example: FC<()> = |ctx, props| {
-    let name = use_state(&ctx, || "...?");
+    let (name, set_name) = use_state(&ctx, || "...?");
 
     log::debug!("Running component....");
 
@@ -32,14 +32,14 @@ static Example: FC<()> = |ctx, props| {
                         <div>
                             <button
                                 class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
-                                onclick={move |_| name.set("jack")}>
+                                onclick={move |_| set_name("jack")}>
                                 "Jack!"
                                 </button>
 
                                 <button
                                     class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
-                                    onclick={move |_| name.set("jill")}
-                                    onclick={move |_| name.set("jill")}>
+                                    onclick={move |_| set_name("jill")}
+                                    onclick={move |_| set_name("jill")}>
                                     "Jill!"
                                 </button>
                         </div>

+ 67 - 38
packages/web/examples/list.rs

@@ -1,6 +1,6 @@
-use std::collections::HashMap;
+use std::collections::{BTreeMap, BTreeSet, HashMap};
 
-use dioxus::prelude::*;
+use dioxus::{events::on::MouseEvent, prelude::*};
 use dioxus_core as dioxus;
 use dioxus_web::WebsysRenderer;
 
@@ -17,37 +17,38 @@ fn main() {
 
 use lazy_static::lazy_static;
 lazy_static! {
-    static ref DummyData: HashMap<String, String> = {
+    static ref DummyData: BTreeMap<usize, String> = {
         let vals = vec![
-            ("0 ", "abc123"),
-            ("1 ", "abc124"),
-            ("2 ", "abc125"),
-            ("3 ", "abc126"),
-            ("4 ", "abc127"),
-            ("5 ", "abc128"),
-            ("6 ", "abc129"),
-            ("7 ", "abc1210"),
-            ("8 ", "abc1211"),
-            ("9 ", "abc1212"),
-            ("10 ", "abc1213"),
-            ("11 ", "abc1214"),
-            ("12 ", "abc1215"),
-            ("13 ", "abc1216"),
-            ("14 ", "abc1217"),
-            ("15 ", "abc1218"),
-            ("16 ", "abc1219"),
-            ("17 ", "abc1220"),
-            ("18 ", "abc1221"),
-            ("19 ", "abc1222"),
+            "abc123", //
+            "abc124", //
+            "abc125", //
+            "abc126", //
+            "abc127", //
+            "abc128", //
+            "abc129", //
+            "abc1210", //
+            "abc1211", //
+            "abc1212", //
+            "abc1213", //
+            "abc1214", //
+            "abc1215", //
+            "abc1216", //
+            "abc1217", //
+            "abc1218", //
+            "abc1219", //
+            "abc1220", //
+            "abc1221", //
+            "abc1222", //
         ];
         vals.into_iter()
-            .map(|(a, b)| (a.to_string(), b.to_string()))
+            .map(ToString::to_string)
+            .enumerate()
             .collect()
     };
 }
 
 static App: FC<()> = |ctx, _| {
-    let items = use_state(&ctx, || DummyData.clone());
+    let items = use_state_new(&ctx, || DummyData.clone());
 
     // handle new elements
     let add_new = move |_| {
@@ -59,20 +60,18 @@ static App: FC<()> = |ctx, _| {
                 (_, 0) => "Buzz".to_string(),
                 _ => k.to_string(),
             };
-            m.insert(k.to_string(), v);
+            m.insert(k, v);
         })
     };
 
     let elements = items.iter().map(|(k, v)| {
         rsx! {
-            li {
-                span {"{k}: {v}"}
-                button {
-                    "Remove"
-                    onclick: move |_| {
-                        let key_to_remove = k.clone();
-                        items.modify(move |m| { m.remove(&key_to_remove); } )
-                    }
+            ListHelper {
+                name: k,
+                value: v
+                onclick: move |_| {
+                    let key = k.clone();
+                    items.modify(move |m| { m.remove(&key); } )
                 }
             }
         }
@@ -81,17 +80,47 @@ static App: FC<()> = |ctx, _| {
     ctx.render(rsx!(
         div {
             h1 {"Some list"}
-
-            // button  to add new item
+            button {
+                "Remove all"
+                onclick: move |_| items.set(BTreeMap::new())
+            }
             button {
                 "add new"
                 onclick: {add_new}
             }
-
-            // list elements
             ul {
                 {elements}
             }
         }
     ))
 };
+
+#[derive(Props)]
+struct ListProps<'a, F: Fn(MouseEvent) + 'a> {
+    name: &'a usize,
+    value: &'a str,
+    onclick: F,
+}
+
+impl<F: Fn(MouseEvent)> PartialEq for ListProps<'_, F> {
+    fn eq(&self, other: &Self) -> bool {
+        // no references are ever the same
+        false
+    }
+}
+
+fn ListHelper<F: Fn(MouseEvent)>(ctx: Context, props: &ListProps<F>) -> DomTree {
+    let k = props.name;
+    let v = props.value;
+    ctx.render(rsx! {
+        li {
+            class: "flex items-center text-xl"
+            key: "{k}"
+            span { "{k}: {v}" }
+            button {
+                "__ Remove"
+                onclick: {&props.onclick}
+            }
+        }
+    })
+}

+ 9 - 13
packages/web/examples/rsxt.rs

@@ -16,16 +16,13 @@ fn main() {
     });
 }
 
-
-
-
 #[derive(PartialEq, Props)]
 struct ExampleProps {
     initial_name: &'static str,
 }
 
 static Example: FC<ExampleProps> = |ctx, props| {
-    let name = use_state(&ctx, move || props.initial_name.to_string());
+    let name = use_state_new(&ctx, move || props.initial_name.to_string());
 
     ctx.render(rsx! {
         div { 
@@ -39,9 +36,9 @@ static Example: FC<ExampleProps> = |ctx, props| {
                 "Hello, {name}"
             }
             
-            CustomButton { name: "Jack!", set_name: name.setter() }
-            CustomButton { name: "Jill!", set_name: name.setter() }
-            CustomButton { name: "Bob!", set_name: name.setter() }
+            CustomButton { name: "Jack!", handler: move |evt| name.set("Jack".to_string()) }
+            CustomButton { name: "Jill!", handler: move |evt| name.set("Jill".to_string()) }
+            CustomButton { name: "Bob!", handler: move |evt| name.set("Bob".to_string())}
         }
     })
 };
@@ -49,23 +46,22 @@ static Example: FC<ExampleProps> = |ctx, props| {
 
 
 #[derive(Props)]
-struct ButtonProps<'src> {
+struct ButtonProps<'src, F: Fn(MouseEvent)> {
     name: &'src str,
-    set_name: &'src dyn Fn(String)
+    handler: F
 }
 
-fn CustomButton<'a>(ctx: Context<'a>, props: &'a ButtonProps<'a>) -> DomTree {
+fn CustomButton<'b, 'a, F: Fn(MouseEvent)>(ctx: Context<'a>, props: &'b ButtonProps<'b, F>) -> DomTree {
     ctx.render(rsx!{
         button {  
             class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
-            onmouseover: move |evt| (props.set_name)(props.name.to_string())
+            onmouseover: {&props.handler}
             "{props.name}"
         }
     })
 }
 
-
-impl PartialEq for ButtonProps<'_> {
+impl<F: Fn(MouseEvent)> PartialEq for ButtonProps<'_, F> {
     fn eq(&self, other: &Self) -> bool {
         false
     }

+ 178 - 0
packages/web/examples/todomvc.rs

@@ -0,0 +1,178 @@
+use std::{
+    collections::{BTreeMap, BTreeSet},
+    sync::atomic::AtomicUsize,
+};
+
+use dioxus_core::prelude::*;
+use dioxus_web::WebsysRenderer;
+use uuid::Uuid;
+
+// Entry point
+fn main() {
+    wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, props| {
+        ctx.create_context(|| model::TodoManager::new());
+
+        ctx.render(rsx! {
+            div {
+                TodoList {}
+                Footer {}
+            }
+        })
+    }))
+}
+
+static TodoList: FC<()> = |ctx, props| {
+    let todos = use_state_new(&ctx, || BTreeMap::<usize, model::TodoItem>::new());
+
+    let items = todos.iter().map(|(order, item)| {
+        rsx!(TodoItem {
+            // key: "{}",
+            todo: item
+        })
+    });
+
+    ctx.render(rsx! {
+        div {
+            {items}
+        }
+    })
+};
+
+#[derive(Debug, PartialEq, Props)]
+struct TodoItemsProp<'a> {
+    todo: &'a model::TodoItem,
+}
+
+fn TodoItem(ctx: Context, props: &TodoItemsProp) -> DomTree {
+    let (editing, set_editing) = use_state(&ctx, || false);
+
+    let id = props.todo.id;
+    ctx.render(rsx! (
+        li {
+            div {
+                "{id}"
+            }
+            // {input}
+        }
+    ))
+}
+
+static Footer: FC<()> = |ctx, props| {
+    ctx.render(html! {
+        <footer className="info">
+            <p>"Double-click to edit a todo"</p>
+            <p>
+                "Created by "<a href="http://github.com/jkelleyrtp/">"jkelleyrtp"</a>
+            </p>
+            <p>
+                "Part of "<a href="http://todomvc.com">"TodoMVC"</a>
+            </p>
+        </footer>
+    })
+};
+
+// The data model that the todo mvc uses
+mod model {
+    use std::{borrow::BorrowMut, future::Future};
+
+    use super::*;
+
+    #[derive(Debug, PartialEq, Clone)]
+    pub struct TodoItem {
+        pub id: Uuid,
+        pub checked: bool,
+        pub contents: String,
+    }
+
+    struct Dispatcher {}
+
+    struct AppContext<T: Clone> {
+        _t: std::rc::Rc<T>,
+    }
+
+    impl<T: Clone> AppContext<T> {
+        fn dispatch(&self, f: impl FnOnce(&mut T)) {}
+        fn async_dispatch(&self, f: impl Future<Output = ()>) {}
+        fn get<G>(&self, f: impl Fn(&T) -> &G) -> &G {
+            f(&self._t)
+        }
+        fn set(&self, orig: &mut std::borrow::Cow<T>) {
+            let r = orig.to_mut();
+        }
+    }
+
+    // use im-rc if your contexts are too large to clone!
+    // or, dangerously mutate and update subscriptions manually
+    #[derive(Clone)]
+    pub struct TodoManager {
+        items: Vec<u32>,
+    }
+
+    impl AppContext<TodoManager> {
+        fn remove_todo(&self, id: Uuid) {
+            self.dispatch(|f| {})
+        }
+
+        async fn push_todo(&self, todo: TodoItem) {
+            self.dispatch(|f| {
+                //
+                f.items.push(10);
+            });
+        }
+
+        fn add_todo(&self) {
+            // self.dispatch(|f| {});
+            // let items = self.get(|f| &f.items);
+        }
+    }
+
+    impl TodoManager {
+        pub fn new() -> Self {
+            todo!()
+        }
+
+        pub fn get_todo(&self) -> &TodoItem {
+            todo!()
+        }
+    }
+
+    pub struct TodoHandle {}
+    impl TodoHandle {
+        fn get_todo(&self, id: Uuid) -> &TodoItem {
+            todo!()
+        }
+
+        fn add_todo(&self, todo: TodoItem) {}
+    }
+
+    // use_reducer, but exposes the reducer and context to children
+    fn use_reducer_context() {}
+    fn use_context_selector() {}
+
+    fn use_context<'b, 'c, Root: 'static, Item: 'c>(
+        ctx: &'b Context<'c>,
+        f: impl Fn(Root) -> &'c Item,
+    ) -> &'c Item {
+        todo!()
+    }
+
+    pub fn use_todo_item<'b, 'c>(ctx: &'b Context<'c>, item: Uuid) -> &'c TodoItem {
+        todo!()
+        // ctx.use_hook(|| TodoManager::new(), |hook| {}, cleanup)
+    }
+    fn use_todos(ctx: &Context) -> TodoHandle {
+        todo!()
+    }
+
+    fn use_todo_context(ctx: &Context) -> AppContext<TodoManager> {
+        todo!()
+    }
+
+    fn test(ctx: Context) {
+        let todos = use_todos(&ctx);
+        let todo = todos.get_todo(Uuid::new_v4());
+
+        let c = use_todo_context(&ctx);
+        // todos.add_todo();
+    }
+}