Sfoglia il codice sorgente

wip: refactor a bit

Jonathan Kelley 4 anni fa
parent
commit
2eeb8f2

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

@@ -1,4 +1,4 @@
-use crate::{nodebuilder::IntoDomTree, prelude::*};
+use crate::{nodebuilder::IntoDomTree, prelude::*, scope::Scope};
 use crate::{nodebuilder::LazyNodes, nodes::VNode};
 use bumpalo::Bump;
 use hooks::Hook;
@@ -27,15 +27,12 @@ use std::{cell::RefCell, future::Future, ops::Deref, pin::Pin, rc::Rc, sync::ato
 pub struct Context<'src> {
     pub idx: RefCell<usize>,
 
-    pub scope: ScopeIdx,
-
-    // Borrowed from scope
-    // 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)>>,
+    // pub scope: ScopeIdx,
+    pub scope: &'src Scope,
+    // // Borrowed from scope
+    // pub(crate) hooks: &'src RefCell<Vec<Hook>>,
+    // pub(crate) bump: &'src Bump,
+    // pub listeners: &'src RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>,
 
     // holder for the src lifetime
     // todo @jon remove this
@@ -101,10 +98,10 @@ impl<'a> Context<'a> {
         lazy_nodes: LazyNodes<'a, F>,
     ) -> DomTree {
         let ctx = NodeCtx {
-            bump: self.bump,
-            scope: self.scope,
+            bump: &self.scope.cur_frame().bump,
+            scope: self.scope.myidx,
             idx: 0.into(),
-            listeners: self.listeners,
+            listeners: &self.scope.listeners,
         };
 
         let safe_nodes = lazy_nodes.into_vnode(&ctx);
@@ -145,7 +142,7 @@ pub mod hooks {
             let idx = *self.idx.borrow();
 
             // Mutate hook list if necessary
-            let mut hooks = self.hooks.borrow_mut();
+            let mut hooks = self.scope.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

+ 39 - 13
packages/core/src/scope.rs

@@ -44,7 +44,7 @@ pub struct Scope {
     // - is self-refenrential and therefore needs to point into the bump
     // Stores references into the listeners attached to the vnodes
     // NEEDS TO BE PRIVATE
-    listeners: RefCell<Vec<*const dyn Fn(VirtualEvent)>>,
+    pub(crate) listeners: RefCell<Vec<*const dyn Fn(VirtualEvent)>>,
 }
 
 impl Scope {
@@ -85,21 +85,26 @@ impl Scope {
     /// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
     pub fn run_scope<'b>(&'b mut self) -> Result<()> {
         // pub fn run_scope<'bump>(&'bump mut self) -> Result<()> {
-        let frame = {
+        // let frame = {
+        {
             let frame = self.frames.next();
             frame.bump.reset();
-            frame
-        };
+        }
+        //     frame
+        // };
+        // self.new_frame()
 
-        let ctx: Context<'b> = Context {
+        let ctx = Context {
             // arena: &self.hook_arena,
-            hooks: &self.hooks,
-            bump: &frame.bump,
+            // hooks: &self.hooks,
+            // bump: &frame.bump,
             idx: 0.into(),
             _p: PhantomData {},
-            scope: self.myidx,
-            listeners: &self.listeners,
+            scope: self,
+            // scope: self.myidx,
+            // listeners: &self.listeners,
         };
+
         let caller = self.caller.upgrade().expect("Failed to get caller");
 
         /*
@@ -114,12 +119,16 @@ impl Scope {
         - Public API cannot drop or destructure VNode
         */
 
-        frame.head_node = unsafe {
-            let caller2: Rc<OpaqueComponent<'b>> = std::mem::transmute(caller);
-            let tree = (caller2.as_ref())(ctx);
-            tree.root
+        let new_head = unsafe {
+            // frame.head_node = unsafe {
+            //     // use the same type, just manipulate the lifetime
+            type ComComp<'c> = Rc<OpaqueComponent<'c>>;
+            let caller = std::mem::transmute::<ComComp<'static>, ComComp<'b>>(caller);
+            let r: DomTree = (caller.as_ref())(ctx);
+            r
         };
 
+        self.frames.cur_frame_mut().head_node = new_head.root;
         Ok(())
     }
 
@@ -161,6 +170,10 @@ impl Scope {
     pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
         self.frames.prev_head_node()
     }
+
+    pub fn cur_frame(&self) -> &BumpFrame {
+        self.frames.cur_frame()
+    }
 }
 
 // ==========================
@@ -203,6 +216,19 @@ impl ActiveFrame {
         }
     }
 
+    fn cur_frame(&self) -> &BumpFrame {
+        match *self.idx.borrow() & 1 == 0 {
+            true => &self.frames[0],
+            false => &self.frames[1],
+        }
+    }
+    fn cur_frame_mut(&mut self) -> &mut BumpFrame {
+        match *self.idx.borrow() & 1 == 0 {
+            true => &mut self.frames[0],
+            false => &mut self.frames[1],
+        }
+    }
+
     pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
         let raw_node = match *self.idx.borrow() & 1 == 0 {
             true => &self.frames[0],

+ 19 - 3
packages/core/src/virtual_dom.rs

@@ -27,7 +27,7 @@ pub struct VirtualDom {
     /// Will not be ready if the dom is fresh
     pub base_scope: ScopeIdx,
 
-    pub(crate) update_schedule: Rc<RefCell<Vec<HierarchyMarker>>>,
+    pub(crate) update_schedule: UpdateFunnel,
 
     // a strong allocation to the "caller" for the original props
     #[doc(hidden)]
@@ -74,7 +74,7 @@ impl VirtualDom {
             components,
             _root_caller: root_caller,
             base_scope,
-            update_schedule: Rc::new(RefCell::new(Vec::new())),
+            update_schedule: UpdateFunnel::default(),
             _root_prop_type: TypeId::of::<P>(),
         }
     }
@@ -211,5 +211,21 @@ impl VirtualDom {
 
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub struct HierarchyMarker {
-    height: u32,
+    source: ScopeIdx,
+}
+
+#[derive(Debug, Default, PartialEq, Clone)]
+pub struct UpdateFunnel(Rc<RefCell<Vec<HierarchyMarker>>>);
+
+impl UpdateFunnel {
+    fn schedule_update(&self, source: ScopeIdx) -> impl Fn() {
+        let inner = self.clone();
+        move || {
+            inner
+                .0
+                .as_ref()
+                .borrow_mut()
+                .push(HierarchyMarker { source })
+        }
+    }
 }

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

@@ -1,218 +0,0 @@
-use dioxus_core::prelude::*;
-use dioxus_web::WebsysRenderer;
-use recoil::{use_recoil_callback, RecoilContext};
-use uuid::Uuid;
-
-static TODOS: AtomFamily<Uuid, TodoItem> = atom_family(|_| {});
-
-fn main() {
-    wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, props| {
-        let global_reducer = use_recoil_callback(|| ());
-
-        let todos = use_atom(TODOS).iter().map(|(order, item)| {
-            rsx!(TodoItem {
-                key: "{order}",
-                id: item.id,
-            })
-        });
-
-        ctx.render(rsx! {
-            div {
-                {todos}
-                Footer {}
-            }
-        })
-    }))
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct TodoItem {
-    pub id: Uuid,
-    pub checked: bool,
-    pub contents: String,
-}
-
-// build a global context for the app
-// as we scale the app, we can create separate, stateful impls
-impl RecoilContext<()> {
-    fn add_todo(&self) {}
-    fn remove_todo(&self) {}
-    fn select_all_todos(&self) {}
-}
-
-mod hooks {
-    use super::*;
-    fn use_keyboard_shortcuts(ctx: &Context) {}
-}
-
-// 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,
-    }
-
-    fn atom() {}
-
-    // struct Dispatcher {}
-
-    struct AppContext<T: Clone> {
-        _t: std::rc::Rc<T>,
-    }
-
-    // pub fn use_appcontext<T: Clone>(ctx: &Context, f: impl FnOnce() -> T) -> AppContext<T> {
-    //     todo!()
-    // }
-
-    // static TodoList: ContextFamily = context_family();
-
-    // struct TodoBoss<'a> {}
-
-    // fn use_recoil_todos() -> TodoBoss {}
-
-    // pub fn use_context_family(ctx: &Context) {}
-
-    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, Debug, PartialEq)]
-    // pub struct TodoManager {
-    //     items: Vec<u32>,
-    // }
-
-    // // App context is an ergonomic way of sharing data models through a tall tree
-    // // Because it holds onto the source data with Rc, it's cheap to clone through props and allows advanced memoization
-    // // It's particularly useful when moving through tall trees, or iterating through complex data models.
-    // // By wrapping the source type, we can forward any mutation through "dispatch", making it clear when clones occur.
-    // // This also enables traditional method-style
-    // impl AppContext<TodoManager> {
-    //     fn get_todos(&self, ctx: &Context) {}
-
-    //     fn remove_todo(&self, id: Uuid) {
-    //         self.dispatch(|f| {
-    //             // todos... remove
-    //         })
-    //     }
-
-    //     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);
-    //     }
-    // }
-
-    // pub enum TodoActions {}
-    // impl TodoManager {
-    //     pub fn reduce(s: &mut Rc<Self>, action: TodoActions) {
-    //         match action {
-    //             _ => {}
-    //         }
-    //     }
-
-    //     pub fn new() -> Rc<Self> {
-    //         todo!()
-    //     }
-
-    //     pub fn get_todo(&self, id: Uuid) -> &TodoItem {
-    //         todo!()
-    //     }
-
-    //     pub fn get_todos(&self) -> &BTreeMap<String, 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();
-    // }
-}
-
-mod recoil {
-
-    pub struct RecoilContext<T: 'static> {
-        _inner: T,
-    }
-
-    impl<T: 'static> RecoilContext<T> {
-        /// Get the value of an atom. Returns a reference to the underlying data.
-
-        pub fn get(&self) {}
-
-        /// Replace an existing value with a new value
-        ///
-        /// This does not replace the value instantly, and all calls to "get" within the current scope will return
-        pub fn set(&self) {}
-
-        // Modify lets you modify the value in place. However, because there's no previous value around to compare
-        // the new one with, we are unable to memoize the change. As such, all downsteam users of this Atom will
-        // be updated, causing all subsrcibed components to re-render.
-        //
-        // This is fine for most values, but might not be performant when dealing with collections. For collections,
-        // use the "Family" variants as these will stay memoized for inserts, removals, and modifications.
-        //
-        // Note - like "set" this won't propogate instantly. Once all "gets" are dropped, only then will we run the
-        pub fn modify(&self) {}
-    }
-
-    pub fn use_recoil_callback<G>(f: impl Fn() -> G) -> RecoilContext<G> {
-        todo!()
-    }
-}

+ 2 - 1
packages/web/examples/todomvc/recoil.rs

@@ -93,8 +93,9 @@ mod selectors {
     }
     impl<O> SelectorBuilder<O, false> {
         pub fn getter(self, f: impl Fn(()) -> O) -> SelectorBuilder<O, true> {
-            std::rc::Rc::pin(value)
             todo!()
+            // std::rc::Rc::pin(value)
+            // todo!()
         }
     }
     pub struct selector<O>(pub fn(SelectorBuilder<O, false>) -> SelectorBuilder<O, true>);

+ 17 - 25
packages/web/examples/todomvc/state.rs

@@ -1,13 +1,9 @@
 use crate::recoil::*;
-use dioxus_core::prelude::Context;
 
 pub static TODOS: AtomFamily<uuid::Uuid, TodoItem> = atom_family(|_| {});
 pub static FILTER: Atom<FilterState> = atom(|_| FilterState::All);
 pub static SHOW_ALL_TODOS: selector<bool> = selector(|g| g.getter(|f| false));
 
-// an atomfamily is just a HashMap<K, Pin<Rc<V>>> that pins the Rc and exposes the values by reference
-// we could do a more advanced management, but this is fine too
-
 #[derive(PartialEq)]
 pub enum FilterState {
     All,
@@ -22,30 +18,26 @@ pub struct TodoItem {
     pub contents: String,
 }
 
-pub fn add_todo(ctx: &Context, contents: String) {}
-
-pub fn remove_todo(ctx: &Context, id: &uuid::Uuid) {
-    TODOS.with(&ctx).remove(id)
-}
-
-pub fn select_all_todos(ctx: &Context) {}
+impl RecoilContext<()> {
+    pub fn add_todo(&self, contents: String) {}
 
-pub fn toggle_todo(ctx: &Context, id: &uuid::Uuid) {}
+    pub fn remove_todo(&self, id: &uuid::Uuid) {
+        // TODOS.with().remove(id)
+    }
 
-pub fn clear_completed(ctx: &Context) {
-    let (set, get) = (self.set, self.get);
+    pub fn select_all_todos(&self) {}
 
-    TOODS
-        .get(&ctx)
-        .iter()
-        .filter(|(k, v)| v.checked)
-        .map(|(k, v)| TODOS.remove(&ctx, k));
-}
+    pub fn toggle_todo(&self, id: &uuid::Uuid) {}
 
-pub fn set_filter(ctx: &Context, filter: &FilterState) {}
+    pub fn clear_completed(&self) {
+        // let (set, get) = (self.set, self.get);
 
-struct TodoManager<'a> {}
-fn use_todos(ctx: &Context) {}
+        // TOODS
+        //     .get(&ctx)
+        //     .iter()
+        //     .filter(|(k, v)| v.checked)
+        //     .map(|(k, v)| TODOS.remove(&ctx, k));
+    }
 
-#[test]
-fn test() {}
+    pub fn set_filter(&self, filter: &FilterState) {}
+}

+ 21 - 19
packages/web/examples/todomvc/todolist.rs

@@ -1,7 +1,9 @@
-use super::state::{FilterState, TodoItem, FILTER, TODOS};
-use crate::filtertoggles;
-use crate::recoil::use_atom;
-use crate::todoitem::TodoEntry;
+use crate::{
+    filtertoggles,
+    recoil::use_atom,
+    state::{FilterState, TodoItem, FILTER, TODOS},
+    todoitem::TodoEntry,
+};
 use dioxus_core::prelude::*;
 
 pub fn TodoList(ctx: Context, props: &()) -> DomTree {
@@ -9,20 +11,6 @@ pub fn TodoList(ctx: Context, props: &()) -> DomTree {
     let (todos, _) = use_state(&ctx, || Vec::<TodoItem>::new());
     let filter = use_atom(&ctx, &FILTER);
 
-    let list = todos
-        .iter()
-        .filter(|item| match filter {
-            FilterState::All => true,
-            FilterState::Active => !item.checked,
-            FilterState::Completed => item.checked,
-        })
-        .map(|item| {
-            rsx!(TodoEntry {
-                key: "{order}",
-                id: item.id,
-            })
-        });
-
     ctx.render(rsx! {
         div {
             header {
@@ -36,7 +24,21 @@ pub fn TodoList(ctx: Context, props: &()) -> DomTree {
                 }
             }
 
-            {list}
+            { // list
+                todos
+                .iter()
+                .filter(|item| match filter {
+                    FilterState::All => true,
+                    FilterState::Active => !item.checked,
+                    FilterState::Completed => item.checked,
+                })
+                .map(|item| {
+                    rsx!(TodoEntry {
+                        key: "{order}",
+                        id: item.id,
+                    })
+                })
+            }
 
             // filter toggle (show only if the list isn't empty)
             {(!todos.is_empty()).then(||

+ 159 - 0
packages/web/examples/todomvc_simple.rs

@@ -0,0 +1,159 @@
+use std::{collections::HashMap, rc::Rc};
+
+use dioxus_core::prelude::*;
+use dioxus_web::WebsysRenderer;
+
+static APP_STYLE: &'static str = include_str!("./todomvc/style.css");
+
+fn main() {
+    wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
+}
+
+#[derive(PartialEq)]
+pub enum FilterState {
+    All,
+    Active,
+    Completed,
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct TodoItem {
+    pub id: uuid::Uuid,
+    pub checked: bool,
+    pub contents: String,
+}
+
+// =======================
+// Components
+// =======================
+pub fn App(ctx: Context, props: &()) -> DomTree {
+    ctx.render(rsx! {
+        div {
+            id: "app"
+            style { "{APP_STYLE}" }
+
+            // list
+            TodoList {}
+
+            // footer
+            footer {
+                class: "info"
+                p {"Double-click to edit a todo"}
+                p {
+                    "Created by "
+                    a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" }
+                }
+                p {
+                    "Part of "
+                    a { "TodoMVC", href: "http://todomvc.com" }
+                }
+            }
+        }
+    })
+}
+
+pub fn TodoList(ctx: Context, props: &()) -> DomTree {
+    let (draft, set_draft) = use_state(&ctx, || "".to_string());
+    let (todos, set_todos) = use_state(&ctx, || HashMap::<uuid::Uuid, Rc<TodoItem>>::new());
+    let (filter, set_filter) = use_state(&ctx, || FilterState::All);
+
+    ctx.render(rsx! {
+        div {
+            header {
+                class: "header"
+                h1 {"todos"}
+                input {
+                    class: "new-todo"
+                    placeholder: "What needs to be done?"
+                    value: "{draft}"
+                    oninput: move |evt| set_draft(evt.value)
+                }
+            }
+
+            { // list
+                todos
+                .iter()
+                .filter(|(id, item)| match filter {
+                    FilterState::All => true,
+                    FilterState::Active => !item.checked,
+                    FilterState::Completed => item.checked,
+                })
+                .map(|(id, item)| {
+                    rsx!(TodoEntry {
+                        key: "{order}",
+                        item: item.clone()
+                    })
+                })
+            }
+
+            // filter toggle (show only if the list isn't empty)
+            {(!todos.is_empty()).then(||
+                rsx!( FilterToggles {})
+            )}
+        }
+    })
+}
+
+#[derive(PartialEq, Props)]
+pub struct TodoEntryProps {
+    item: Rc<TodoItem>,
+}
+
+pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
+    let (is_editing, set_is_editing) = use_state(&ctx, || false);
+    let todo = &props.item;
+
+    ctx.render(rsx! (
+        li {
+            "{todo.id}"
+            input {
+                class: "toggle"
+                type: "checkbox"
+                "{todo.checked}"
+            }
+            {is_editing.then(|| rsx!(
+                input {
+                    value: "{contents}"
+                }
+            ))}
+        }
+    ))
+}
+
+pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
+    let toggles = [
+        ("All", "", FilterState::All),
+        ("Active", "active", FilterState::Active),
+        ("Completed", "completed", FilterState::Completed),
+    ]
+    .iter()
+    .map(|(name, path, filter)| {
+        rsx!(
+            li {
+                class: "{name}"
+                a {
+                    href: "{path}"
+                    // onclick: move |_| reducer.set_filter(&filter)
+                    "{name}"
+                }
+            }
+        )
+    });
+
+    // todo
+    let item_text = "";
+    let items_left = "";
+
+    ctx.render(rsx! {
+        footer {
+            span {
+                strong {"{items_left}"}
+                span {"{item_text} left"}
+            }
+            ul {
+                class: "filters"
+                {toggles}
+            }
+        }
+    })
+}

+ 281 - 0
packages/web/examples/todomvcsingle.rs

@@ -0,0 +1,281 @@
+use dioxus_core::prelude::*;
+use dioxus_web::WebsysRenderer;
+
+static APP_STYLE: &'static str = include_str!("./todomvc/style.css");
+
+fn main() {
+    wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
+}
+// =======================
+// state-related items
+// =======================
+pub static TODOS: AtomFamily<uuid::Uuid, TodoItem> = atom_family(|_| {});
+pub static FILTER: Atom<FilterState> = atom(|_| FilterState::All);
+pub static SHOW_ALL_TODOS: selector<bool> = selector(|g| g.getter(|f| false));
+
+#[derive(PartialEq)]
+pub enum FilterState {
+    All,
+    Active,
+    Completed,
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct TodoItem {
+    pub id: uuid::Uuid,
+    pub checked: bool,
+    pub contents: String,
+}
+
+impl RecoilContext<()> {
+    pub fn add_todo(&self, contents: String) {}
+    pub fn remove_todo(&self, id: &uuid::Uuid) {}
+    pub fn select_all_todos(&self) {}
+    pub fn toggle_todo(&self, id: &uuid::Uuid) {}
+    pub fn clear_completed(&self) {}
+    pub fn set_filter(&self, filter: &FilterState) {}
+}
+
+// =======================
+// Components
+// =======================
+pub fn App(ctx: Context, props: &()) -> DomTree {
+    ctx.render(rsx! {
+        div {
+            id: "app"
+            style { "{APP_STYLE}" }
+
+            // list
+            TodoList {}
+
+            // footer
+            footer {
+                class: "info"
+                p {"Double-click to edit a todo"}
+                p {
+                    "Created by "
+                    a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" }
+                }
+                p {
+                    "Part of "
+                    a { "TodoMVC", href: "http://todomvc.com" }
+                }
+            }
+        }
+    })
+}
+
+pub fn TodoList(ctx: Context, props: &()) -> DomTree {
+    let (draft, set_draft) = use_state(&ctx, || "".to_string());
+    let (todos, _) = use_state(&ctx, || Vec::<TodoItem>::new());
+    let filter = use_atom(&ctx, &FILTER);
+
+    ctx.render(rsx! {
+        div {
+            header {
+                class: "header"
+                h1 {"todos"}
+                input {
+                    class: "new-todo"
+                    placeholder: "What needs to be done?"
+                    value: "{draft}"
+                    oninput: move |evt| set_draft(evt.value)
+                }
+            }
+
+            { // list
+                todos
+                .iter()
+                .filter(|item| match filter {
+                    FilterState::All => true,
+                    FilterState::Active => !item.checked,
+                    FilterState::Completed => item.checked,
+                })
+                .map(|item| {
+                    rsx!(TodoEntry {
+                        key: "{order}",
+                        id: item.id,
+                    })
+                })
+            }
+
+            // filter toggle (show only if the list isn't empty)
+            {(!todos.is_empty()).then(||
+                rsx!( FilterToggles {})
+            )}
+        }
+    })
+}
+
+#[derive(PartialEq, Props)]
+pub struct TodoEntryProps {
+    id: uuid::Uuid,
+}
+
+pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
+    let (is_editing, set_is_editing) = use_state(&ctx, || false);
+    let todo = use_atom_family(&ctx, &TODOS, props.id);
+
+    ctx.render(rsx! (
+        li {
+            "{todo.id}"
+            input {
+                class: "toggle"
+                type: "checkbox"
+                "{todo.checked}"
+            }
+            {is_editing.then(|| rsx!(
+                input {
+                    value: "{contents}"
+                }
+            ))}
+        }
+    ))
+}
+
+pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
+    let reducer = recoil::use_callback(&ctx, || ());
+    let items_left = recoil::use_atom_family(&ctx, &TODOS, uuid::Uuid::new_v4());
+
+    let toggles = [
+        ("All", "", FilterState::All),
+        ("Active", "active", FilterState::Active),
+        ("Completed", "completed", FilterState::Completed),
+    ]
+    .iter()
+    .map(|(name, path, filter)| {
+        rsx!(
+            li {
+                class: "{name}"
+                a {
+                    href: "{path}"
+                    onclick: move |_| reducer.set_filter(&filter)
+                    "{name}"
+                }
+            }
+        )
+    });
+
+    // todo
+    let item_text = "";
+    let items_left = "";
+
+    ctx.render(rsx! {
+        footer {
+            span {
+                strong {"{items_left}"}
+                span {"{item_text} left"}
+            }
+            ul {
+                class: "filters"
+                {toggles}
+            }
+        }
+    })
+}
+
+pub use recoil::*;
+mod recoil {
+    use dioxus_core::context::Context;
+
+    pub struct RecoilContext<T: 'static> {
+        _inner: T,
+    }
+
+    impl<T: 'static> RecoilContext<T> {
+        /// Get the value of an atom. Returns a reference to the underlying data.
+
+        pub fn get(&self) {}
+
+        /// Replace an existing value with a new value
+        ///
+        /// This does not replace the value instantly, and all calls to "get" within the current scope will return
+        pub fn set(&self) {}
+
+        // Modify lets you modify the value in place. However, because there's no previous value around to compare
+        // the new one with, we are unable to memoize the change. As such, all downsteam users of this Atom will
+        // be updated, causing all subsrcibed components to re-render.
+        //
+        // This is fine for most values, but might not be performant when dealing with collections. For collections,
+        // use the "Family" variants as these will stay memoized for inserts, removals, and modifications.
+        //
+        // Note - like "set" this won't propogate instantly. Once all "gets" are dropped, only then will we run the
+        pub fn modify(&self) {}
+    }
+
+    pub fn use_callback<'a, G>(c: &Context<'a>, f: impl Fn() -> G) -> &'a RecoilContext<G> {
+        todo!()
+    }
+
+    pub fn use_atom<T: PartialEq, O>(c: &Context, t: &'static Atom<T>) -> O {
+        todo!()
+    }
+    pub fn use_batom<T: PartialEq, O>(c: &Context, t: impl Readable) -> O {
+        todo!()
+    }
+
+    pub trait Readable {}
+    impl<T: PartialEq> Readable for &'static Atom<T> {}
+    impl<K: PartialEq, V: PartialEq> Readable for &'static AtomFamily<K, V> {}
+
+    pub fn use_atom_family<'a, K: PartialEq, V: PartialEq>(
+        c: &Context<'a>,
+        t: &'static AtomFamily<K, V>,
+        g: K,
+    ) -> &'a V {
+        todo!()
+    }
+
+    pub use atoms::{atom, Atom};
+    pub use atoms::{atom_family, AtomFamily};
+    mod atoms {
+
+        use super::*;
+        pub struct AtomBuilder<T: PartialEq> {
+            pub key: String,
+            pub manual_init: Option<Box<dyn Fn() -> T>>,
+            _never: std::marker::PhantomData<T>,
+        }
+
+        impl<T: PartialEq> AtomBuilder<T> {
+            pub fn new() -> Self {
+                Self {
+                    key: uuid::Uuid::new_v4().to_string(),
+                    manual_init: None,
+                    _never: std::marker::PhantomData {},
+                }
+            }
+
+            pub fn init<A: Fn() -> T + 'static>(&mut self, f: A) {
+                self.manual_init = Some(Box::new(f));
+            }
+
+            pub fn set_key(&mut self, _key: &'static str) {}
+        }
+
+        pub struct atom<T: PartialEq>(pub fn(&mut AtomBuilder<T>) -> T);
+        pub type Atom<T: PartialEq> = atom<T>;
+
+        pub struct AtomFamilyBuilder<K, V> {
+            _never: std::marker::PhantomData<(K, V)>,
+        }
+
+        pub struct atom_family<K: PartialEq, V: PartialEq>(pub fn(&mut AtomFamilyBuilder<K, V>));
+        pub type AtomFamily<K: PartialEq, V: PartialEq> = atom_family<K, V>;
+    }
+
+    pub use selectors::selector;
+    mod selectors {
+        pub struct SelectorBuilder<Out, const Built: bool> {
+            _p: std::marker::PhantomData<Out>,
+        }
+        impl<O> SelectorBuilder<O, false> {
+            pub fn getter(self, f: impl Fn(()) -> O) -> SelectorBuilder<O, true> {
+                todo!()
+                // std::rc::Rc::pin(value)
+                // todo!()
+            }
+        }
+        pub struct selector<O>(pub fn(SelectorBuilder<O, false>) -> SelectorBuilder<O, true>);
+    }
+}