Răsfoiți Sursa

Merge pull request #14 from jkelleyrtp/jk/none-handling

feat: allow return none
Jonathan Kelley 3 ani în urmă
părinte
comite
308414f5ef

+ 1 - 0
.vscode/spellright.dict

@@ -64,3 +64,4 @@ Tokio
 asynchronicity
 constified
 SegVec
+contentful

+ 4 - 3
README.md

@@ -165,15 +165,16 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
 | Custom elements           | ✅      | ✅     | Define new element primitives                               |
 | Suspense                  | ✅      | ✅     | schedule future render from future/promise                  |
 | Integrated error handling | ✅      | ✅     | Gracefully handle errors with ? syntax                      |
-| Effects                   | 🛠      | ✅     | Run effects after a component has been committed to render  |
+| Re-hydration              | 🛠      | ✅     | Pre-render to HTML to speed up first contentful paint       |
 | Cooperative Scheduling    | 🛠      | ✅     | Prioritize important events over non-important events       |
-| NodeRef                   | 🛠      | ✅     | gain direct access to nodes [1]                             |
 | Runs natively             | ✅      | ❓     | runs as a portable binary w/o a runtime (Node)              |
 | 1st class global state    | ✅      | ❓     | redux/recoil/mobx on top of context                         |
 | Subtree Memoization       | ✅      | ❓     | skip diffing static element subtrees                        |
 | Compile-time correct      | ✅      | ❓     | Throw errors on invalid template layouts                    |
 | Heuristic Engine          | 🛠      | ❓     | track component memory usage to minimize future allocations |
 | Fine-grained reactivity   | 🛠      | ❓     | Skip diffing for fine-grain updates                         |
+| Effects                   | 🛠      | ✅     | Run effects after a component has been committed to render  |
+| NodeRef                   | 🛠      | ✅     | gain direct access to nodes [1]                             |
 
 - [1] Currently blocked until we figure out a cross-platform way of exposing an imperative Node API.
 
@@ -195,7 +196,7 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
 | Feature              | Dioxus | React | Notes for Dioxus                     |
 | -------------------- | ------ | ----- | ------------------------------------ |
 | Portal               | ❓      | ✅     | cast elements through tree           |
-| Error/Panic boundary |       | ✅     | catch panics and display custom BSOD |
+| Error/Panic boundary | 👀      | ✅     | catch panics and display custom BSOD |
 | Code-splitting       | 👀      | ✅     | Make bundle smaller/lazy             |
 | LiveView             | 👀      | ❓     | Example for SSR + WASM apps          |
 

+ 85 - 0
examples/reference/errorhandling.rs

@@ -0,0 +1,85 @@
+//! Example: Errror Handling
+//! ------------------------
+//!
+//! Error handling in Dioxus comes in a few flavors. Because Dioxus is a Rust project, Options and Results are obviously
+//! the go-to way of wrapping possibly-errored data. However, if a component fails to "unwrapping," everything will crash,
+//! the page will deadlock, and your users will be sad.
+//!
+//! So, obviously, you need to handle your errors.
+//!
+//! Fortunately, it's easy to avoid panics, even during quick prototyping.
+//!
+//! Here's a few strategies:
+//! - Leverage the ability to return "None" and propogate None directly
+//! - Instead of propogating "None" manually, use the "?" syntax sugar
+//! - Covert Results into Options with .ok()
+//! - Manually display a separate screen by matching on Options/Results
+//!
+//! There *are* plans to add helpful screens for when apps completely panic in WASM. However, you should really try to
+//! avoid panicking.
+use dioxus::prelude::*;
+fn main() {}
+
+/// This is one way to go about error handling (just toss things away with unwrap).
+/// However, if you get it wrong, the whole app will crash.
+/// This is pretty flimsy.
+static App: FC<()> = |cx| {
+    let data = get_data().unwrap();
+    cx.render(rsx!( div { "{data}" } ))
+};
+
+/// This is a pretty verbose way of error handling
+/// However, it's still pretty good since we don't panic, just fail to render anything
+static App1: FC<()> = |cx| {
+    let data = match get_data() {
+        Some(data) => data,
+        None => return None,
+    };
+    cx.render(rsx!( div { "{data}" } ))
+};
+
+/// This is an even better form of error handling.
+/// However, it _does_ make the component go blank, which might not be desirable.
+///
+/// This type of error handling is good when you have "selectors" that produce Some/None based on some state that's
+/// already controlled for higher in the tree. IE displaying a "Username" in a component that should only be shown if
+/// a user is logged in.
+///
+/// Dioxus will throw an error in the console if the None-path is ever taken.
+static App2: FC<()> = |cx| {
+    let data = get_data()?;
+    cx.render(rsx!( div { "{data}" } ))
+};
+
+/// This is top-tier error handling since it displays a failure state.
+///
+/// However, the error is lacking in context.
+static App3: FC<()> = |cx| match get_data() {
+    Some(data) => cx.render(rsx!( div { "{data}" } )),
+    None => cx.render(rsx!( div { "Failed to load data :(" } )),
+};
+
+/// For errors that return results, it's possible short-circuit the match-based error handling with `.ok()` which converts
+/// a Result<T, V> into an Option<T> and lets you
+static App4: FC<()> = |cx| {
+    let data = get_data_err().ok()?;
+    cx.render(rsx!( div { "{data}" } ))
+};
+
+/// This is great error handling since it displays a failure state... with context!
+///
+/// Hopefully you never need to disply a screen like this. It's rather bad taste
+static App5: FC<()> = |cx| match get_data_err() {
+    Ok(data) => cx.render(rsx!( div { "{data}" } )),
+    Err(c) => cx.render(rsx!( div { "Failed to load data: {c}" } )),
+};
+
+// this fetching function produces "nothing"
+fn get_data() -> Option<String> {
+    None
+}
+
+// this fetching function produces "nothing"
+fn get_data_err() -> Result<String, &'static str> {
+    Result::Err("Failed!")
+}

+ 1 - 1
packages/core-macro/Cargo.toml

@@ -12,7 +12,7 @@ description = "Core macro for Dioxus Virtual DOM"
 proc-macro = true
 
 [dependencies]
-once_cell = "1.7.2"
+once_cell = "1.8"
 proc-macro2 = { version = "1.0.6" }
 quote = "1.0"
 syn = { version = "1.0.11", features = ["full", "extra-traits"] }

+ 2 - 2
packages/core/src/arena.rs

@@ -1,13 +1,13 @@
 use std::{cell::UnsafeCell, rc::Rc};
 
 use crate::innerlude::*;
-use slotmap::{DefaultKey, SlotMap};
+use slotmap::SlotMap;
 
 #[derive(Clone)]
 pub struct SharedArena {
     pub components: Rc<UnsafeCell<ScopeMap>>,
 }
-pub type ScopeMap = SlotMap<DefaultKey, Scope>;
+pub type ScopeMap = SlotMap<ScopeId, Scope>;
 
 enum MutStatus {
     Immut,

+ 8 - 7
packages/core/src/bumpframe.rs

@@ -77,13 +77,14 @@ impl ActiveFrame {
         }
     }
 
-    pub fn next(&mut self) -> &mut BumpFrame {
-        *self.generation.borrow_mut() += 1;
-
-        if *self.generation.borrow() % 2 == 0 {
-            &mut self.frames[0]
-        } else {
-            &mut self.frames[1]
+    pub fn old_frame_mut(&mut self) -> &mut BumpFrame {
+        match *self.generation.borrow() & 1 == 0 {
+            true => &mut self.frames[1],
+            false => &mut self.frames[0],
         }
     }
+
+    pub fn cycle_frame(&mut self) {
+        *self.generation.borrow_mut() += 1;
+    }
 }

+ 50 - 33
packages/core/src/context.rs

@@ -115,6 +115,11 @@ impl<'src, P> Context<'src, P> {
         todo!()
     }
 
+    /// Get's this context's ScopeId
+    pub fn get_scope_id(&self) -> ScopeId {
+        self.scope.our_arena_idx.clone()
+    }
+
     /// 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.
@@ -200,9 +205,13 @@ Any function prefixed with "use" should not be called conditionally.
     ///
     ///
     ///
-    pub fn use_create_context<T: 'static>(&self, init: impl Fn() -> T) {
-        let mut cxs = self.scope.shared_contexts.borrow_mut();
+    pub fn use_provide_context<T, F>(self, init: F) -> &'src Rc<T>
+    where
+        T: 'static,
+        F: FnOnce() -> T,
+    {
         let ty = TypeId::of::<T>();
+        let contains_key = self.scope.shared_contexts.borrow().contains_key(&ty);
 
         let is_initialized = self.use_hook(
             |_| false,
@@ -214,18 +223,30 @@ Any function prefixed with "use" should not be called conditionally.
             |_| {},
         );
 
-        match (is_initialized, cxs.contains_key(&ty)) {
+        match (is_initialized, contains_key) {
             // Do nothing, already initialized and already exists
             (true, true) => {}
 
             // Needs to be initialized
             (false, false) => {
                 log::debug!("Initializing context...");
-                cxs.insert(ty, Rc::new(init()));
+                let initialized = Rc::new(init());
+                let p = self
+                    .scope
+                    .shared_contexts
+                    .borrow_mut()
+                    .insert(ty, initialized);
+                log::info!(
+                    "There are now {} shared contexts for scope {:?}",
+                    self.scope.shared_contexts.borrow().len(),
+                    self.scope.our_arena_idx,
+                );
             }
 
             _ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
-        }
+        };
+
+        self.use_context::<T>()
     }
 
     /// There are hooks going on here!
@@ -234,26 +255,19 @@ Any function prefixed with "use" should not be called conditionally.
     }
 
     /// Uses a context, storing the cached value around
-    pub fn try_use_context<T: 'static>(self) -> Result<&'src Rc<T>> {
+    ///
+    /// If a context is not found on the first search, then this call will be  "dud", always returning "None" even if a
+    /// context was added later. This allows using another hook as a fallback
+    ///
+    pub fn try_use_context<T: 'static>(self) -> Option<&'src Rc<T>> {
         struct UseContextHook<C> {
             par: Option<Rc<C>>,
-            we: Option<Weak<C>>,
         }
 
         self.use_hook(
-            move |_| UseContextHook {
-                par: None as Option<Rc<T>>,
-                we: None as Option<Weak<T>>,
-            },
-            move |hook| {
+            move |_| {
                 let mut scope = Some(self.scope);
-
-                if let Some(we) = &hook.we {
-                    if let Some(re) = we.upgrade() {
-                        hook.par = Some(re);
-                        return Ok(hook.par.as_ref().unwrap());
-                    }
-                }
+                let mut par = None;
 
                 let ty = TypeId::of::<T>();
                 while let Some(inner) = scope {
@@ -261,35 +275,38 @@ Any function prefixed with "use" should not be called conditionally.
                         "Searching {:#?} for valid shared_context",
                         inner.our_arena_idx
                     );
-                    let shared_contexts = inner.shared_contexts.borrow();
+                    let shared_ctx = {
+                        let shared_contexts = inner.shared_contexts.borrow();
+
+                        log::debug!(
+                            "This component has {} shared contexts",
+                            shared_contexts.len()
+                        );
+                        shared_contexts.get(&ty).map(|f| f.clone())
+                    };
 
-                    if let Some(shared_cx) = shared_contexts.get(&ty) {
+                    if let Some(shared_cx) = shared_ctx {
                         log::debug!("found matching cx");
                         let rc = shared_cx
                             .clone()
                             .downcast::<T>()
                             .expect("Should not fail, already validated the type from the hashmap");
 
-                        hook.we = Some(Rc::downgrade(&rc));
-                        hook.par = Some(rc);
-                        return Ok(hook.par.as_ref().unwrap());
+                        par = Some(rc);
+                        break;
                     } else {
                         match inner.parent_idx {
                             Some(parent_id) => {
-                                let parent = inner
-                                    .arena_link
-                                    .get(parent_id)
-                                    .ok_or_else(|| Error::FatalInternal("Failed to find parent"))?;
-
-                                scope = Some(parent);
+                                scope = inner.arena_link.get(parent_id);
                             }
-                            None => return Err(Error::MissingSharedContext),
+                            None => break,
                         }
                     }
                 }
-
-                Err(Error::MissingSharedContext)
+                //
+                UseContextHook { par }
             },
+            move |hook| hook.par.as_ref(),
             |_| {},
         )
     }

+ 12 - 7
packages/core/src/diff.rs

@@ -50,6 +50,7 @@
 
 use crate::{arena::SharedArena, innerlude::*, tasks::TaskQueue};
 use fxhash::FxHashSet;
+use smallvec::{smallvec, SmallVec};
 
 use std::any::Any;
 
@@ -74,7 +75,7 @@ pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> {
     pub edits: DomEditor<'real, 'bump>,
     pub components: &'bump SharedArena,
     pub task_queue: &'bump TaskQueue,
-    pub cur_idx: ScopeId,
+    pub cur_idxs: SmallVec<[ScopeId; 5]>,
     pub diffed: FxHashSet<ScopeId>,
     pub event_queue: EventQueue,
     pub seen_nodes: FxHashSet<ScopeId>,
@@ -96,7 +97,7 @@ where
             edits: DomEditor::new(edits),
             components,
             dom,
-            cur_idx,
+            cur_idxs: smallvec![cur_idx],
             event_queue,
             task_queue,
             diffed: FxHashSet::default(),
@@ -156,6 +157,7 @@ where
                 log::warn!("diffing components? {:#?}", new.user_fc);
                 if old.user_fc == new.user_fc {
                     // Make sure we're dealing with the same component (by function pointer)
+                    self.cur_idxs.push(old.ass_scope.get().unwrap());
 
                     // Make sure the new component vnode is referencing the right scope id
                     let scope_id = old.ass_scope.get();
@@ -181,6 +183,7 @@ where
                     } else {
                         //
                     }
+                    self.cur_idxs.pop();
 
                     self.seen_nodes.insert(scope_id.unwrap());
                 } else {
@@ -399,10 +402,10 @@ where
                 log::debug!("Mounting a new component");
                 let caller = vcomponent.caller.clone();
 
-                let parent_idx = self.cur_idx;
+                let parent_idx = self.cur_idxs.last().unwrap().clone();
 
                 // Insert a new scope into our component list
-                let idx = self
+                let new_idx = self
                     .components
                     .with(|components| {
                         components.insert_with_key(|new_idx| {
@@ -435,20 +438,22 @@ where
 
                 // TODO: abstract this unsafe into the arena abstraction
                 let inner: &'bump mut _ = unsafe { &mut *self.components.components.get() };
-                let new_component = inner.get_mut(idx).unwrap();
+                let new_component = inner.get_mut(new_idx).unwrap();
 
                 // Actually initialize the caller's slot with the right address
-                vcomponent.ass_scope.set(Some(idx));
+                vcomponent.ass_scope.set(Some(new_idx));
 
                 // Run the scope for one iteration to initialize it
                 new_component.run_scope().unwrap();
 
                 // TODO: we need to delete (IE relcaim this node, otherwise the arena will grow infinitely)
                 let nextnode = new_component.next_frame();
+                self.cur_idxs.push(new_idx);
                 let meta = self.create(nextnode);
+                self.cur_idxs.pop();
 
                 // Finally, insert this node as a seen node.
-                self.seen_nodes.insert(idx);
+                self.seen_nodes.insert(new_idx);
 
                 CreateMeta::new(vcomponent.is_static, meta.added_to_stack)
             }

+ 3 - 0
packages/core/src/error.rs

@@ -19,6 +19,9 @@ pub enum Error {
     #[error("Wrong Properties Type")]
     WrongProps,
 
+    #[error("The component failed to return VNodes")]
+    ComponentFailed,
+
     #[error("Base scope has not been mounted yet")]
     NotMounted,
 

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

@@ -10,7 +10,7 @@
 //!
 
 pub use crate::innerlude::{
-    format_args_f, html, rsx, Context, DioxusElement, DomEdit, EventTrigger, LazyNodes,
+    format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, EventTrigger, LazyNodes,
     NodeFactory, Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom,
     VirtualEvent, FC,
 };

+ 35 - 0
packages/core/src/nodes.rs

@@ -267,6 +267,22 @@ impl<'a> NodeFactory<'a> {
         }
     }
 
+    pub fn attr_with_alloc_val(
+        &self,
+        name: &'static str,
+        val: &'a str,
+        namespace: Option<&'static str>,
+        is_volatile: bool,
+    ) -> Attribute<'a> {
+        Attribute {
+            name,
+            value: val,
+            is_static: false,
+            namespace,
+            is_volatile,
+        }
+    }
+
     pub fn component<P>(
         &self,
         component: FC<P>,
@@ -508,6 +524,25 @@ impl IntoVNode<'_> for Option<()> {
         cx.fragment_from_iter(None as Option<VNode>)
     }
 }
+impl<'a> IntoVNode<'a> for Option<VNode<'a>> {
+    fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
+        match self {
+            Some(n) => n,
+            None => cx.fragment_from_iter(None as Option<VNode>),
+        }
+    }
+}
+
+impl IntoVNode<'_> for &'static str {
+    fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
+        NodeFactory::static_text(self)
+    }
+}
+impl IntoVNode<'_> for Arguments<'_> {
+    fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
+        cx.text(self)
+    }
+}
 
 impl Debug for NodeFactory<'_> {
     fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

+ 15 - 11
packages/core/src/scope.rs

@@ -113,7 +113,8 @@ impl Scope {
         // Remove all the outdated listeners
 
         // This is a very dangerous operation
-        self.frames.next().bump.reset();
+        let next_frame = self.frames.old_frame_mut();
+        next_frame.bump.reset();
 
         self.listeners.borrow_mut().clear();
 
@@ -123,16 +124,19 @@ impl Scope {
         // Cast the caller ptr from static to one with our own reference
         let c3: &WrappedCaller = self.caller.as_ref();
 
-        self.frames.cur_frame_mut().head_node = unsafe { self.call_user_component(c3) };
-
-        Ok(())
-    }
-
-    // this is its own function so we can preciesly control how lifetimes flow
-    unsafe fn call_user_component<'a>(&'a self, caller: &WrappedCaller) -> VNode<'static> {
-        let new_head: Option<VNode<'a>> = caller(self);
-        let new_head = new_head.unwrap_or(errored_fragment());
-        std::mem::transmute(new_head)
+        match c3(self) {
+            None => {
+                // the user's component failed. We avoid cycling to the next frame
+                log::error!("Running your component failed! It will no longer receive events.");
+                Err(Error::ComponentFailed)
+            }
+            Some(new_head) => {
+                // the user's component succeeded. We can safely cycle to the next frame
+                self.frames.old_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
+                self.frames.cycle_frame();
+                Ok(())
+            }
+        }
     }
 
     // A safe wrapper around calling listeners

+ 13 - 10
packages/core/src/virtual_dom.rs

@@ -30,7 +30,9 @@ use std::any::TypeId;
 use std::cell::RefCell;
 use std::pin::Pin;
 
-pub type ScopeId = DefaultKey;
+slotmap::new_key_type! {
+    pub struct ScopeId;
+}
 
 /// An integrated virtual node system that progresses events and diffs UI trees.
 /// Differences are converted into patches which a renderer can use to draw the UI.
@@ -140,7 +142,7 @@ impl VirtualDom {
     /// let dom = VirtualDom::new(Example);
     /// ```
     pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
-        let components = SharedArena::new(SlotMap::new());
+        let components = SharedArena::new(SlotMap::<ScopeId, Scope>::with_key());
 
         let root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
         let props_ptr = root_props.as_ref().downcast_ref::<P>().unwrap() as *const P;
@@ -225,11 +227,12 @@ impl VirtualDom {
         );
 
         let cur_component = self.components.get_mut(self.base_scope).unwrap();
-        cur_component.run_scope()?;
-
-        let meta = diff_machine.create(cur_component.next_frame());
 
-        diff_machine.edits.append_children(meta.added_to_stack);
+        // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
+        if cur_component.run_scope().is_ok() {
+            let meta = diff_machine.create(cur_component.next_frame());
+            diff_machine.edits.append_children(meta.added_to_stack);
+        }
 
         Ok(())
     }
@@ -370,10 +373,10 @@ impl VirtualDom {
                     // We are guaranteeed that this scope is unique because we are tracking which nodes have modified
                     let cur_component = self.components.get_mut(update.idx).unwrap();
 
-                    cur_component.run_scope()?;
-
-                    let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
-                    diff_machine.diff_node(old, new);
+                    if cur_component.run_scope().is_ok() {
+                        let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
+                        diff_machine.diff_node(old, new);
+                    }
                 }
             }
         }

+ 15 - 7
packages/desktop/src/index.html

@@ -51,8 +51,7 @@
                 const node = self.stack.pop();
                 node.remove();
             }
-            RemoveAllChildren(self, edit) {
-            }
+            RemoveAllChildren(self, edit) {}
             CreateTextNode(self, edit) {
                 self.stack.push(document.createTextNode(edit.text));
             }
@@ -62,24 +61,32 @@
                 self.stack.push(document.createElement(tagName));
             }
             CreateElementNs(self, edit) {
+                const tagName = edit.tag;
+                console.log(`creating namespaced element: `, edit);
                 self.stack.push(document.createElementNS(edit.ns, edit.tag));
             }
             CreatePlaceholder(self, edit) {
                 const a = `self.stack.push(document.createElement("pre"))`;
                 self.stack.push(document.createComment("vroot"));
             }
-            NewEventListener(self, edit) {
-            }
-            RemoveEventListener(self, edit) {
-            }
+            NewEventListener(self, edit) {}
+            RemoveEventListener(self, edit) {}
             SetText(self, edit) {
                 self.top().textContent = edit.text;
             }
             SetAttribute(self, edit) {
                 const name = edit.field;
                 const value = edit.value;
+                const ns = edit.ns;
+
                 const node = self.top(self.stack);
-                node.setAttribute(name, value);
+                if (ns == "style") {
+                    node.style[name] = value;
+                } else if (ns !== undefined) {
+                    node.setAttributeNS(ns, name, value);
+                } else {
+                    node.setAttribute(name, value);
+                }
 
                 if ((name === "value", self)) {
                     node.value = value;
@@ -94,6 +101,7 @@
             RemoveAttribute(self, edit) {
                 const name = edit.field;
                 const node = self.top(self.stack);
+
                 node.removeAttribute(name);
 
                 if ((name === "value", self)) {

+ 25 - 6
packages/hooks/src/usestate.rs

@@ -2,13 +2,13 @@ use dioxus_core::prelude::Context;
 use std::{
     cell::{Cell, Ref, RefCell, RefMut},
     fmt::Display,
-    ops::{Deref, DerefMut},
+    ops::{Deref, DerefMut, Not},
     rc::Rc,
 };
 
 /// Store state between component renders!
 ///
-/// ## The "King" of state hooks
+/// ## The "Pinnacle" of state hooks
 ///
 /// The Dioxus version of `useState` is the "king daddy" of state management. It allows you to ergonomically store and
 /// modify state between component renders. When the state is updated, the component will re-render.
@@ -106,7 +106,7 @@ impl<'a, T: 'static> UseState<'a, T> {
         *self.inner.wip.borrow_mut() = Some(new_val);
     }
 
-    pub fn get(&self) -> &T {
+    pub fn get(&self) -> &'a T {
         &self.inner.current_val
     }
 
@@ -129,6 +129,10 @@ impl<'a, T: 'static> UseState<'a, T> {
             wip: self.inner.wip.clone(),
         }
     }
+
+    pub fn split_for_async(&'a self) -> (&'a Self, AsyncUseState<T>) {
+        (self, self.for_async())
+    }
 }
 
 impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
@@ -147,15 +151,15 @@ impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
     }
 }
 
-impl<'a, T: 'static> std::ops::Deref for UseState<'a, T> {
+impl<'a, T> std::ops::Deref for UseState<'a, T> {
     type Target = T;
 
     fn deref(&self) -> &Self::Target {
-        &self.inner.current_val
+        self.get()
     }
 }
 
-use std::ops::{Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Sub, SubAssign};
+use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
 
 impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
     type Output = T;
@@ -208,6 +212,18 @@ impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseState<'a, T> {
         self.set(self.inner.current_val.div(rhs));
     }
 }
+impl<'a, T: PartialEq<T>> PartialEq<T> for UseState<'a, T> {
+    fn eq(&self, other: &T) -> bool {
+        self.get() == other
+    }
+}
+impl<'a, O, T: Not<Output = O> + Copy> Not for UseState<'a, T> {
+    type Output = O;
+
+    fn not(self) -> Self::Output {
+        !*self.get()
+    }
+}
 
 // enable displaty for the handle
 impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
@@ -233,4 +249,7 @@ impl<T: ToOwned> AsyncUseState<T> {
             slot.as_mut().unwrap()
         })
     }
+    pub fn set(&mut self, val: T) {
+        *self.wip.borrow_mut() = Some(val);
+    }
 }

+ 2 - 2
packages/ssr/src/lib.rs

@@ -12,8 +12,8 @@ use dioxus_core::*;
 
 pub fn render_vnode(vnode: &VNode, string: &mut String) {}
 
-pub fn render_vdom(vdom: &VirtualDom) -> String {
-    format!("{:}", TextRenderer::from_vdom(vdom))
+pub fn render_vdom(dom: &VirtualDom) -> String {
+    format!("{:}", TextRenderer::from_vdom(dom))
 }
 
 pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {

+ 3 - 3
packages/web/Cargo.toml

@@ -17,12 +17,10 @@ wasm-bindgen-futures = "0.4.20"
 wasm-logger = "0.2.0"
 log = "0.4.14"
 fxhash = "0.2.1"
-pretty_env_logger = "0.4.0"
 console_error_panic_hook = "0.1.6"
 generational-arena = "0.2.8"
 wasm-bindgen-test = "0.3.21"
-once_cell = "1.7.2"
-# atoms = { path = "../atoms" }
+once_cell = "1.8"
 async-channel = "1.6.1"
 anyhow = "1.0.41"
 slotmap = "1.0.3"
@@ -61,6 +59,8 @@ features = [
     "CompositionEvent",
     "DocumentType",
     "CharacterData",
+    "SvgElement",
+    "SvgAnimatedString",
     "HtmlOptionElement",
 ]
 

+ 14 - 2
packages/web/src/new.rs

@@ -294,8 +294,20 @@ impl WebsysDom {
 
     fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>) {
         if name == "class" {
-            if let Some(el) = self.stack.top().dyn_ref::<Element>() {
-                el.set_class_name(value);
+            match ns {
+                Some("http://www.w3.org/2000/svg") => {
+                    //
+                    if let Some(el) = self.stack.top().dyn_ref::<web_sys::SvgElement>() {
+                        let r: web_sys::SvgAnimatedString = el.class_name();
+                        r.set_base_val(value);
+                        // el.set_class_name(value);
+                    }
+                }
+                _ => {
+                    if let Some(el) = self.stack.top().dyn_ref::<Element>() {
+                        el.set_class_name(value);
+                    }
+                }
             }
         } else {
             if let Some(el) = self.stack.top().dyn_ref::<Element>() {

+ 0 - 19
wip.md

@@ -1,19 +0,0 @@
-
-6 oz. button mushrooms
-6 oz. shiitake mushrooms
-7 oz. smoked or firm tofu (half a 14-oz. package), drained, cut into 1"–2  pieces
-1 medium onion, cut into a few large pieces
-¼ cup plus 3 Tbsp. vegetable oil
-5 garlic cloves, finely chopped
-¼ cup gochujang (Korean hot pepper paste)
-1 tsp. five-spice powder
-½ tsp. ground cumin
-3 Tbsp. Shaoxing wine (Chinese rice wine) or medium-dry sherry
-2 Tbsp. vegetarian mushroom oyster sauce (such as Lee Kum Kee)
-Kosher salt
-2 Tbsp. vegetable oil
-1 tsp. toasted sesame oil
-6 English muffins, split
-2 scallions, thinly sliced
-½ cup coarsely chopped cilantro
-1 Tbsp. toasted sesame seeds