瀏覽代碼

Fix cargo test and a number of little cleanup bugs

Jonathan Kelley 1 年之前
父節點
當前提交
0bd9692e45
共有 49 個文件被更改,包括 312 次插入314 次删除
  1. 0 2
      Cargo.lock
  2. 1 1
      examples/crm.rs
  3. 1 1
      examples/ssr.rs
  4. 1 1
      packages/autofmt/tests/samples/immediate_expr.rsx
  5. 1 1
      packages/autofmt/tests/samples/long.rsx
  6. 1 1
      packages/autofmt/tests/samples/messy_indent.rsx
  7. 1 1
      packages/autofmt/tests/samples/reallylong.rsx
  8. 1 1
      packages/autofmt/tests/samples/trailing_expr.rsx
  9. 1 1
      packages/autofmt/tests/wrong/multi-4sp.rsx
  10. 1 1
      packages/autofmt/tests/wrong/multi-4sp.wrong.rsx
  11. 1 1
      packages/autofmt/tests/wrong/multi-tab.rsx
  12. 1 1
      packages/autofmt/tests/wrong/multi-tab.wrong.rsx
  13. 1 1
      packages/autofmt/tests/wrong/multiexpr-4sp.rsx
  14. 1 1
      packages/autofmt/tests/wrong/multiexpr-4sp.wrong.rsx
  15. 1 1
      packages/autofmt/tests/wrong/multiexpr-tab.rsx
  16. 1 1
      packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx
  17. 14 14
      packages/check/src/check.rs
  18. 14 14
      packages/check/src/issues.rs
  19. 2 2
      packages/cli/src/server/web/mod.rs
  20. 9 0
      packages/core/src/global_context.rs
  21. 2 0
      packages/core/src/tasks.rs
  22. 115 115
      packages/core/tests/diff_element.rs
  23. 1 3
      packages/core/tests/fuzzing.rs
  24. 1 1
      packages/core/tests/miri_stress.rs
  25. 2 2
      packages/core/tests/task.rs
  26. 1 1
      packages/desktop/Cargo.toml
  27. 26 27
      packages/desktop/headless_tests/events.rs
  28. 9 11
      packages/desktop/headless_tests/rendering.rs
  29. 0 10
      packages/dioxus/src/lib.rs
  30. 2 2
      packages/hooks/src/lib.rs
  31. 3 6
      packages/hooks/src/use_memo.rs
  32. 1 1
      packages/liveview/src/pool.rs
  33. 1 1
      packages/router/Cargo.toml
  34. 1 1
      packages/router/examples/simple_routes.rs
  35. 21 7
      packages/router/src/components/link.rs
  36. 8 8
      packages/router/src/contexts/router.rs
  37. 3 5
      packages/router/src/hooks/use_route.rs
  38. 9 17
      packages/router/tests/via_ssr/link.rs
  39. 1 1
      packages/rsx/src/element.rs
  40. 11 11
      packages/signals/src/comparer.rs
  41. 9 2
      packages/signals/src/global/signal.rs
  42. 1 1
      packages/signals/src/props.rs
  43. 4 2
      packages/signals/src/rt.rs
  44. 9 9
      packages/signals/src/write.rs
  45. 1 1
      packages/ssr/tests/hydration.rs
  46. 11 13
      packages/web/src/cfg.rs
  47. 2 3
      packages/web/src/launch.rs
  48. 3 5
      playwright-tests/fullstack/Cargo.toml
  49. 0 1
      playwright-tests/fullstack/src/main.rs

+ 0 - 2
Cargo.lock

@@ -2787,8 +2787,6 @@ version = "0.1.0"
 dependencies = [
  "axum 0.6.20",
  "dioxus",
- "dioxus-fullstack",
- "dioxus-web",
  "execute",
  "serde",
  "tokio",

+ 1 - 1
examples/crm.rs

@@ -51,7 +51,7 @@ fn ClientList() -> Element {
         h2 { "List of Clients" }
         Link { to: Route::ClientAdd, class: "pure-button pure-button-primary", "Add Client" }
         Link { to: Route::Settings, class: "pure-button", "Settings" }
-        for client in CLIENTS.iter() {
+        for client in CLIENTS.read().iter() {
             div { class: "client", style: "margin-bottom: 50px",
                 p { "Name: {client.first_name} {client.last_name}" }
                 p { "Description: {client.description}" }

+ 1 - 1
examples/ssr.rs

@@ -6,7 +6,7 @@ use dioxus::prelude::*;
 
 fn main() {
     // We can render VirtualDoms
-    let mut vdom = VirtualDom::prebuilt(app);
+    let vdom = VirtualDom::prebuilt(app);
     println!("{}", dioxus_ssr::render(&vdom));
 
     // Or we can render rsx! calls themselves

+ 1 - 1
packages/autofmt/tests/samples/immediate_expr.rsx

@@ -1,4 +1,4 @@
 fn it_works() {
-    rsx!({()}))
+    rsx!({()})
 }
 

+ 1 - 1
packages/autofmt/tests/samples/long.rsx

@@ -37,5 +37,5 @@ pub fn Explainer<'a>(
             {left},
             {right}
         }
-    })
+    }
 }

+ 1 - 1
packages/autofmt/tests/samples/messy_indent.rsx

@@ -9,5 +9,5 @@ fn SaveClipboard() -> Element {
 
     rsx! {
         div { "hello world", "hello world", "hello world" }
-    })
+    }
 }

+ 1 - 1
packages/autofmt/tests/samples/reallylong.rsx

@@ -11,5 +11,5 @@ pub static Icon3: Component<()> = |cx| {
             path { d: "M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2" }
             circle { cx: "12", cy: "7", r: "4" }
         }
-    })
+    }
 };

+ 1 - 1
packages/autofmt/tests/samples/trailing_expr.rsx

@@ -3,5 +3,5 @@ fn it_works() {
         div {
             span { "Description: ", {package.description.as_deref().unwrap_or("❌❌❌❌ missing")} }
         }
-    })
+    }
 }

+ 1 - 1
packages/autofmt/tests/wrong/multi-4sp.rsx

@@ -1,3 +1,3 @@
 fn app() -> Element {
-    rsx! { div { "hello world" } })
+    rsx! { div { "hello world" } }
 }

+ 1 - 1
packages/autofmt/tests/wrong/multi-4sp.wrong.rsx

@@ -1,5 +1,5 @@
 fn app() -> Element {
     rsx! {
         div {"hello world" }
-    })
+    }
 }

+ 1 - 1
packages/autofmt/tests/wrong/multi-tab.rsx

@@ -1,3 +1,3 @@
 fn app() -> Element {
-	rsx! { div { "hello world" } })
+	rsx! { div { "hello world" } }
 }

+ 1 - 1
packages/autofmt/tests/wrong/multi-tab.wrong.rsx

@@ -1,5 +1,5 @@
 fn app() -> Element {
 	rsx! {
 		div {"hello world" }
-	})
+	}
 }

+ 1 - 1
packages/autofmt/tests/wrong/multiexpr-4sp.rsx

@@ -4,5 +4,5 @@ fn ItWroks() {
             {left},
             {right}
         }
-    })
+    }
 }

+ 1 - 1
packages/autofmt/tests/wrong/multiexpr-4sp.wrong.rsx

@@ -1,5 +1,5 @@
 fn ItWroks() {
     rsx! {
         div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", {left}, {right} }
-    })
+    }
 }

+ 1 - 1
packages/autofmt/tests/wrong/multiexpr-tab.rsx

@@ -4,5 +4,5 @@ fn ItWroks() {
 			{left},
 			{right}
 		}
-	})
+	}
 }

+ 1 - 1
packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx

@@ -1,5 +1,5 @@
 fn ItWroks() {
 	rsx! {
 		div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", {left}, {right} }
-	})
+	}
 }

+ 14 - 14
packages/check/src/check.rs

@@ -361,10 +361,10 @@ mod tests {
                         LineColumn { line: 3, column: 24 },
                     ),
                     Span::new_from_str(
-                        r#"use_state"#,
+                        r#"use_signal"#,
                         LineColumn { line: 3, column: 24 },
                     ),
-                    "use_state".to_string()
+                    "use_signal".to_string()
                 ),
                 ConditionalInfo::If(IfInfo::new(
                     Span::new_from_str(
@@ -401,8 +401,8 @@ mod tests {
             vec![Issue::HookInsideConditional(
                 HookInfo::new(
                     Span::new_from_str(r#"use_signal(|| "hands")"#, LineColumn { line: 4, column: 28 }),
-                    Span::new_from_str(r#"use_state"#, LineColumn { line: 4, column: 28 }),
-                    "use_state".to_string()
+                    Span::new_from_str(r#"use_signal"#, LineColumn { line: 4, column: 28 }),
+                    "use_signal".to_string()
                 ),
                 ConditionalInfo::Match(MatchInfo::new(
                     Span::new_from_str(
@@ -437,10 +437,10 @@ mod tests {
                         LineColumn { line: 3, column: 26 },
                     ),
                     Span::new_from_str(
-                        "use_state",
+                        "use_signal",
                         LineColumn { line: 3, column: 26 },
                     ),
-                    "use_state".to_string()
+                    "use_signal".to_string()
                 ),
                 AnyLoopInfo::For(ForInfo::new(
                     Span::new_from_str(
@@ -478,10 +478,10 @@ mod tests {
                         LineColumn { line: 3, column: 24 },
                     ),
                     Span::new_from_str(
-                        "use_state",
+                        "use_signal",
                         LineColumn { line: 3, column: 24 },
                     ),
-                    "use_state".to_string()
+                    "use_signal".to_string()
                 ),
                 AnyLoopInfo::While(WhileInfo::new(
                     Span::new_from_str(
@@ -519,10 +519,10 @@ mod tests {
                         LineColumn { line: 3, column: 24 },
                     ),
                     Span::new_from_str(
-                        "use_state",
+                        "use_signal",
                         LineColumn { line: 3, column: 24 },
                     ),
-                    "use_state".to_string()
+                    "use_signal".to_string()
                 ),
                 AnyLoopInfo::Loop(LoopInfo::new(Span::new_from_str(
                     "loop {\n        let something = use_signal(|| \"hands\");\n        println!(\"clap your {something}\")\n    }",
@@ -573,13 +573,13 @@ mod tests {
                         },
                     ),
                     Span::new_from_str(
-                        "use_state",
+                        "use_signal",
                         LineColumn {
                             line: 3,
                             column: 16
                         },
                     ),
-                    "use_state".to_string()
+                    "use_signal".to_string()
                 ),
                 ClosureInfo::new(Span::new_from_str(
                     "|| {\n        let b = use_signal(|| 0);\n        b.get()\n    }",
@@ -613,13 +613,13 @@ mod tests {
                     }
                 ),
                 Span::new_from_str(
-                    "use_state",
+                    "use_signal",
                     LineColumn {
                         line: 2,
                         column: 13
                     },
                 ),
-                "use_state".to_string()
+                "use_signal".to_string()
             ))]
         );
     }

+ 14 - 14
packages/check/src/issues.rs

@@ -240,11 +240,11 @@ mod tests {
         );
 
         let expected = indoc! {r#"
-            error: hook called conditionally: `use_state` (inside `if`)
+            error: hook called conditionally: `use_signal` (inside `if`)
               --> src/main.rs:3:25
               |
             3 |         let something = use_signal(|| "hands");
-              |                         ^^^^^^^^^
+              |                         ^^^^^^^^^^
               |
               = note: `if you_are_happy && you_know_it { … }` is the conditional
         "#};
@@ -271,11 +271,11 @@ mod tests {
         );
 
         let expected = indoc! {r#"
-            error: hook called conditionally: `use_state` (inside `match`)
+            error: hook called conditionally: `use_signal` (inside `match`)
               --> src/main.rs:4:29
               |
             4 |             let something = use_signal(|| "hands");
-              |                             ^^^^^^^^^
+              |                             ^^^^^^^^^^
               |
               = note: `match you_are_happy && you_know_it { … }` is the conditional
         "#};
@@ -299,11 +299,11 @@ mod tests {
         );
 
         let expected = indoc! {r#"
-            error: hook called in a loop: `use_state` (inside `for` loop)
+            error: hook called in a loop: `use_signal` (inside `for` loop)
               --> src/main.rs:3:25
               |
             3 |         let something = use_signal(|| "hands");
-              |                         ^^^^^^^^^
+              |                         ^^^^^^^^^^
               |
               = note: `for i in 0..10 { … }` is the loop
         "#};
@@ -327,11 +327,11 @@ mod tests {
         );
 
         let expected = indoc! {r#"
-            error: hook called in a loop: `use_state` (inside `while` loop)
+            error: hook called in a loop: `use_signal` (inside `while` loop)
               --> src/main.rs:3:25
               |
             3 |         let something = use_signal(|| "hands");
-              |                         ^^^^^^^^^
+              |                         ^^^^^^^^^^
               |
               = note: `while check_thing() { … }` is the loop
         "#};
@@ -355,11 +355,11 @@ mod tests {
         );
 
         let expected = indoc! {r#"
-            error: hook called in a loop: `use_state` (inside `loop`)
+            error: hook called in a loop: `use_signal` (inside `loop`)
               --> src/main.rs:3:25
               |
             3 |         let something = use_signal(|| "hands");
-              |                         ^^^^^^^^^
+              |                         ^^^^^^^^^^
               |
               = note: `loop { … }` is the loop
         "#};
@@ -383,11 +383,11 @@ mod tests {
         );
 
         let expected = indoc! {r#"
-            error: hook called in a closure: `use_state`
+            error: hook called in a closure: `use_signal`
               --> src/main.rs:3:25
               |
             3 |         let something = use_signal(|| "hands");
-              |                         ^^^^^^^^^
+              |                         ^^^^^^^^^^
         "#};
 
         assert_eq!(expected, issue_report.to_string());
@@ -411,11 +411,11 @@ mod tests {
         );
 
         let expected = indoc! {r#"
-            error: hook called conditionally: `use_state` (inside `if`)
+            error: hook called conditionally: `use_signal` (inside `if`)
               --> src/main.rs:3:25
               |
             3 |         let something = use_signal(|| {
-              |                         ^^^^^^^^^
+              |                         ^^^^^^^^^^
             4 |             "hands"
             5 |         });
               |

+ 2 - 2
packages/cli/src/server/web/mod.rs

@@ -366,11 +366,11 @@ async fn start_server(
     router: Router,
     start_browser: bool,
     rustls: Option<RustlsConfig>,
-    config: &CrateConfig,
+    _config: &CrateConfig,
 ) -> Result<()> {
     // If plugins, call on_serve_start event
     #[cfg(feature = "plugin")]
-    PluginManager::on_serve_start(config)?;
+    PluginManager::on_serve_start(_config)?;
 
     // Parse address
     let addr = format!("0.0.0.0:{}", port).parse().unwrap();

+ 9 - 0
packages/core/src/global_context.rs

@@ -216,10 +216,18 @@ pub fn use_drop<D: FnOnce() + 'static>(destroy: D) {
     });
 }
 
+/// A hook that allows you to insert a "before render" function.
+///
+/// This function will always be called before dioxus tries to render your component. This should be used for safely handling
+/// early returns
 pub fn use_before_render(f: impl FnMut() + 'static) {
     use_hook(|| before_render(f));
 }
 
+/// Push this function to be run after the next render
+///
+/// This function will always be called before dioxus tries to render your component. This should be used for safely handling
+/// early returns
 pub fn use_after_render(f: impl FnMut() + 'static) {
     use_hook(|| after_render(f));
 }
@@ -274,6 +282,7 @@ pub async fn flush_sync() {
     }
 }
 
+/// Use a hook with a cleanup function
 pub fn use_hook_with_cleanup<T: Clone + 'static>(
     hook: impl FnOnce() -> T,
     cleanup: impl FnOnce(T) + 'static,

+ 2 - 0
packages/core/src/tasks.rs

@@ -45,10 +45,12 @@ impl Task {
         Runtime::with(|rt| !rt.tasks.borrow()[self.0].active.get()).unwrap_or_default()
     }
 
+    /// Wake the task.
     pub fn wake(&self) {
         Runtime::with(|rt| _ = rt.sender.unbounded_send(SchedulerMsg::TaskNotified(*self)));
     }
 
+    /// Set the task as active or paused.
     pub fn set_active(&self, active: bool) {
         Runtime::with(|rt| rt.tasks.borrow()[self.0].active.set(active));
     }

+ 115 - 115
packages/core/tests/diff_element.rs

@@ -85,119 +85,119 @@ fn element_swap() {
 
 #[test]
 fn attribute_diff() {
-    fn app(cx: Scope) -> Element {
-        let gen = cx.generation();
-
-        // attributes have to be sorted by name
-        let attrs = match gen % 5 {
-            0 => cx.bump().alloc([Attribute::new(
-                "a",
-                AttributeValue::Text("hello"),
-                None,
-                false,
-            )]) as &[Attribute],
-            1 => cx.bump().alloc([
-                Attribute::new("a", AttributeValue::Text("hello"), None, false),
-                Attribute::new("b", AttributeValue::Text("hello"), None, false),
-                Attribute::new("c", AttributeValue::Text("hello"), None, false),
-            ]) as &[Attribute],
-            2 => cx.bump().alloc([
-                Attribute::new("c", AttributeValue::Text("hello"), None, false),
-                Attribute::new("d", AttributeValue::Text("hello"), None, false),
-                Attribute::new("e", AttributeValue::Text("hello"), None, false),
-            ]) as &[Attribute],
-            3 => cx.bump().alloc([Attribute::new(
-                "d",
-                AttributeValue::Text("world"),
-                None,
-                false,
-            )]) as &[Attribute],
-            _ => unreachable!(),
-        };
-
-        cx.render(rsx!(
-            div {
-                ..*attrs,
-                "hello"
-            }
-        ))
-    }
-
-    let mut vdom = VirtualDom::new(app);
-    _ = vdom.rebuild();
-
-    vdom.mark_dirty(ScopeId::ROOT);
-    assert_eq!(
-        vdom.render_immediate().santize().edits,
-        [
-            SetAttribute {
-                name: "b",
-                value: (&AttributeValue::Text("hello",)).into(),
-                id: ElementId(1,),
-                ns: None,
-            },
-            SetAttribute {
-                name: "c",
-                value: (&AttributeValue::Text("hello",)).into(),
-                id: ElementId(1,),
-                ns: None,
-            },
-        ]
-    );
-
-    vdom.mark_dirty(ScopeId::ROOT);
-    assert_eq!(
-        vdom.render_immediate().santize().edits,
-        [
-            SetAttribute {
-                name: "a",
-                value: (&AttributeValue::None).into(),
-                id: ElementId(1,),
-                ns: None,
-            },
-            SetAttribute {
-                name: "b",
-                value: (&AttributeValue::None).into(),
-                id: ElementId(1,),
-                ns: None,
-            },
-            SetAttribute {
-                name: "d",
-                value: (&AttributeValue::Text("hello",)).into(),
-                id: ElementId(1,),
-                ns: None,
-            },
-            SetAttribute {
-                name: "e",
-                value: (&AttributeValue::Text("hello",)).into(),
-                id: ElementId(1,),
-                ns: None,
-            },
-        ]
-    );
-
-    vdom.mark_dirty(ScopeId::ROOT);
-    assert_eq!(
-        vdom.render_immediate().santize().edits,
-        [
-            SetAttribute {
-                name: "c",
-                value: (&AttributeValue::None).into(),
-                id: ElementId(1,),
-                ns: None,
-            },
-            SetAttribute {
-                name: "d",
-                value: (&AttributeValue::Text("world",)).into(),
-                id: ElementId(1,),
-                ns: None,
-            },
-            SetAttribute {
-                name: "e",
-                value: (&AttributeValue::None).into(),
-                id: ElementId(1,),
-                ns: None,
-            },
-        ]
-    );
+    // fn app() -> Element {
+    //     let gen = cx.generation();
+
+    //     // attributes have to be sorted by name
+    //     let attrs = match gen % 5 {
+    //         0 => cx.bump().alloc([Attribute::new(
+    //             "a",
+    //             AttributeValue::Text("hello".into()),
+    //             None,
+    //             false,
+    //         )]) as &[Attribute],
+    //         1 => cx.bump().alloc([
+    //             Attribute::new("a", AttributeValue::Text("hello".into()), None, false),
+    //             Attribute::new("b", AttributeValue::Text("hello".into()), None, false),
+    //             Attribute::new("c", AttributeValue::Text("hello".into()), None, false),
+    //         ]) as &[Attribute],
+    //         2 => cx.bump().alloc([
+    //             Attribute::new("c", AttributeValue::Text("hello".into()), None, false),
+    //             Attribute::new("d", AttributeValue::Text("hello".into()), None, false),
+    //             Attribute::new("e", AttributeValue::Text("hello".into()), None, false),
+    //         ]) as &[Attribute],
+    //         3 => cx.bump().alloc([Attribute::new(
+    //             "d",
+    //             AttributeValue::Text("world".into()),
+    //             None,
+    //             false,
+    //         )]) as &[Attribute],
+    //         _ => unreachable!(),
+    //     };
+
+    //     cx.render(rsx!(
+    //         div {
+    //             ..*attrs,
+    //             "hello"
+    //         }
+    //     ))
+    // }
+
+    // let mut vdom = VirtualDom::new(app);
+    // _ = vdom.rebuild();
+
+    // vdom.mark_dirty(ScopeId::ROOT);
+    // assert_eq!(
+    //     vdom.render_immediate().santize().edits,
+    //     [
+    //         SetAttribute {
+    //             name: "b",
+    //             value: (&AttributeValue::Text("hello",)).into(),
+    //             id: ElementId(1,),
+    //             ns: None,
+    //         },
+    //         SetAttribute {
+    //             name: "c",
+    //             value: (&AttributeValue::Text("hello",)).into(),
+    //             id: ElementId(1,),
+    //             ns: None,
+    //         },
+    //     ]
+    // );
+
+    // vdom.mark_dirty(ScopeId::ROOT);
+    // assert_eq!(
+    //     vdom.render_immediate().santize().edits,
+    //     [
+    //         SetAttribute {
+    //             name: "a",
+    //             value: (&AttributeValue::None).into(),
+    //             id: ElementId(1,),
+    //             ns: None,
+    //         },
+    //         SetAttribute {
+    //             name: "b",
+    //             value: (&AttributeValue::None).into(),
+    //             id: ElementId(1,),
+    //             ns: None,
+    //         },
+    //         SetAttribute {
+    //             name: "d",
+    //             value: (&AttributeValue::Text("hello",)).into(),
+    //             id: ElementId(1,),
+    //             ns: None,
+    //         },
+    //         SetAttribute {
+    //             name: "e",
+    //             value: (&AttributeValue::Text("hello",)).into(),
+    //             id: ElementId(1,),
+    //             ns: None,
+    //         },
+    //     ]
+    // );
+
+    // vdom.mark_dirty(ScopeId::ROOT);
+    // assert_eq!(
+    //     vdom.render_immediate().santize().edits,
+    //     [
+    //         SetAttribute {
+    //             name: "c",
+    //             value: (&AttributeValue::None).into(),
+    //             id: ElementId(1,),
+    //             ns: None,
+    //         },
+    //         SetAttribute {
+    //             name: "d",
+    //             value: (&AttributeValue::Text("world",)).into(),
+    //             id: ElementId(1,),
+    //             ns: None,
+    //         },
+    //         SetAttribute {
+    //             name: "e",
+    //             value: (&AttributeValue::None).into(),
+    //             id: ElementId(1,),
+    //             ns: None,
+    //         },
+    //     ]
+    // );
 }

+ 1 - 3
packages/core/tests/fuzzing.rs

@@ -1,9 +1,7 @@
 #![cfg(not(miri))]
 
 use dioxus::prelude::*;
-use dioxus_core::{
-    prelude::EventHandler, AttributeValue, DynamicNode, NoOpMutations, VComponent, VNode, *,
-};
+use dioxus_core::{AttributeValue, DynamicNode, NoOpMutations, VComponent, VNode, *};
 use std::{cfg, collections::HashSet, default::Default};
 
 fn random_ns() -> Option<&'static str> {

+ 1 - 1
packages/core/tests/miri_stress.rs

@@ -90,7 +90,7 @@ fn memo_works_properly() {
         na: String,
     }
 
-    fn Child(cx: ChildProps) -> Element {
+    fn Child(_props: ChildProps) -> Element {
         rsx!( div { "goodbye world" } )
     }
 

+ 2 - 2
packages/core/tests/task.rs

@@ -60,7 +60,7 @@ async fn yield_now_works() {
         // these two tasks should yield to eachother
         use_hook(|| {
             spawn(async move {
-                for x in 0..10 {
+                for _ in 0..10 {
                     tokio::task::yield_now().await;
                     SEQUENCE.with(|s| s.borrow_mut().push(1));
                 }
@@ -69,7 +69,7 @@ async fn yield_now_works() {
 
         use_hook(|| {
             spawn(async move {
-                for x in 0..10 {
+                for _ in 0..10 {
                     tokio::task::yield_now().await;
                     SEQUENCE.with(|s| s.borrow_mut().push(2));
                 }

+ 1 - 1
packages/desktop/Cargo.toml

@@ -79,7 +79,7 @@ default-features = false
 features = ["tokio_runtime", "hot-reload"]
 
 [dev-dependencies]
-dioxus = { workspace = true }
+dioxus = { workspace = true, features = ["desktop"] }
 exitcode = "1.1.2"
 
 [build-dependencies]

+ 26 - 27
packages/desktop/headless_tests/events.rs

@@ -3,6 +3,10 @@ use dioxus::prelude::*;
 use dioxus_core::prelude::consume_context;
 use dioxus_desktop::DesktopContext;
 
+pub fn main() {
+    check_app_exits(app);
+}
+
 pub(crate) fn check_app_exits(app: fn() -> Element) {
     use dioxus_desktop::tao::window::WindowBuilder;
     use dioxus_desktop::Config;
@@ -10,7 +14,7 @@ pub(crate) fn check_app_exits(app: fn() -> Element) {
     let should_panic = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
     let should_panic_clone = should_panic.clone();
     std::thread::spawn(move || {
-        std::thread::sleep(std::time::Duration::from_secs(100));
+        std::thread::sleep(std::time::Duration::from_secs(5));
         if should_panic_clone.load(std::sync::atomic::Ordering::SeqCst) {
             std::process::exit(exitcode::SOFTWARE);
         }
@@ -24,14 +28,10 @@ pub(crate) fn check_app_exits(app: fn() -> Element) {
     should_panic.store(false, std::sync::atomic::Ordering::SeqCst);
 }
 
-pub fn main() {
-    check_app_exits(app);
-}
-
 fn mock_event(id: &'static str, value: &'static str) {
-    use_effect(move || {
+    use_hook(move || {
         spawn(async move {
-            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
+            tokio::time::sleep(std::time::Duration::from_millis(2000)).await;
 
             let js = format!(
                 r#"
@@ -45,7 +45,7 @@ fn mock_event(id: &'static str, value: &'static str) {
                 value, id
             );
 
-            dioxus::eval(js);
+            eval(&js).unwrap().await.unwrap();
         });
     })
 }
@@ -205,7 +205,7 @@ fn app() -> Element {
         r#"new FocusEvent("focusout",{bubbles: true})"#,
     );
 
-    if received_events() == 13 {
+    if received_events() == 12 {
         println!("all events recieved");
         desktop_context.close();
     }
@@ -216,12 +216,11 @@ fn app() -> Element {
                 width: "100px",
                 height: "100px",
                 onmounted: move |evt| async move {
-                    todo!();
-                    // let rect = evt.get_client_rect().await.unwrap();
-                    // println!("rect: {:?}", rect);
-                    // assert_eq!(rect.width(), 100.0);
-                    // assert_eq!(rect.height(), 100.0);
-                    // received_events.with_mut(|x| *x + 1)
+                    let rect = evt.get_client_rect().await.unwrap();
+                    println!("rect: {:?}", rect);
+                    assert_eq!(rect.width(), 100.0);
+                    assert_eq!(rect.height(), 100.0);
+                    received_events.with_mut(|x| *x += 1);
                 }
             }
             button {
@@ -234,7 +233,7 @@ fn app() -> Element {
                         event.data.trigger_button(),
                         Some(dioxus_html::input_data::MouseButton::Primary),
                     );
-                    received_events.with_mut(|x| *x + 1);
+                    received_events.with_mut(|x| *x += 1);
                 }
             }
             div {
@@ -248,7 +247,7 @@ fn app() -> Element {
                             .held_buttons()
                             .contains(dioxus_html::input_data::MouseButton::Secondary),
                     );
-                    received_events.with_mut(|x| *x + 1);
+                    received_events.with_mut(|x| *x += 1);
                 }
             }
             div {
@@ -266,7 +265,7 @@ fn app() -> Element {
                         event.data.trigger_button(),
                         Some(dioxus_html::input_data::MouseButton::Secondary),
                     );
-                    received_events.with_mut(|x| *x + 1);
+                    received_events.with_mut(|x| *x += 1);
                 }
             }
             div {
@@ -287,7 +286,7 @@ fn app() -> Element {
                         event.data.trigger_button(),
                         Some(dioxus_html::input_data::MouseButton::Secondary),
                     );
-                    received_events.with_mut(|x| *x + 1);
+                    received_events.with_mut(|x| *x += 1);
                 }
             }
             div {
@@ -305,7 +304,7 @@ fn app() -> Element {
                         event.data.trigger_button(),
                         Some(dioxus_html::input_data::MouseButton::Secondary),
                     );
-                    received_events.with_mut(|x| *x + 1);
+                    received_events.with_mut(|x| *x += 1);
                 }
             }
             div {
@@ -318,7 +317,7 @@ fn app() -> Element {
                         event.data.trigger_button(),
                         Some(dioxus_html::input_data::MouseButton::Primary),
                     );
-                    received_events.with_mut(|x| *x + 1);
+                    received_events.with_mut(|x| *x += 1);
                 }
             }
             div {
@@ -331,7 +330,7 @@ fn app() -> Element {
                     let dioxus_html::geometry::WheelDelta::Pixels(delta) = event.data.delta() else {
                     panic!("Expected delta to be in pixels") };
                     assert_eq!(delta, Vector3D::new(1.0, 2.0, 3.0));
-                    received_events.with_mut(|x| *x + 1);
+                    received_events.with_mut(|x| *x += 1);
                 }
             }
             input {
@@ -344,7 +343,7 @@ fn app() -> Element {
                     assert_eq!(event.data.location(), Location::Standard);
                     assert!(event.data.is_auto_repeating());
                     assert!(event.data.is_composing());
-                    received_events.with_mut(|x| *x + 1)
+                    received_events.with_mut(|x| *x += 1);
                 }
             }
             input {
@@ -357,7 +356,7 @@ fn app() -> Element {
                     assert_eq!(event.data.location(), Location::Standard);
                     assert!(!event.data.is_auto_repeating());
                     assert!(!event.data.is_composing());
-                    received_events.with_mut(|x| *x + 1)
+                    received_events.with_mut(|x| *x += 1);
                 }
             }
             input {
@@ -370,21 +369,21 @@ fn app() -> Element {
                     assert_eq!(event.data.location(), Location::Standard);
                     assert!(!event.data.is_auto_repeating());
                     assert!(!event.data.is_composing());
-                    received_events.with_mut(|x| *x + 1)
+                    received_events.with_mut(|x| *x += 1);
                 }
             }
             input {
                 id: "focus_in_div",
                 onfocusin: move |event| {
                     println!("{:?}", event.data);
-                    received_events.with_mut(|x| *x + 1);
+                    received_events.with_mut(|x| *x += 1);
                 }
             }
             input {
                 id: "focus_out_div",
                 onfocusout: move |event| {
                     println!("{:?}", event.data);
-                    received_events.with_mut(|x| *x + 1);
+                    received_events.with_mut(|x| *x += 1);
                 }
             }
         }

+ 9 - 11
packages/desktop/headless_tests/rendering.rs

@@ -13,7 +13,7 @@ pub(crate) fn check_app_exits(app: fn() -> Element) {
     let should_panic = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
     let should_panic_clone = should_panic.clone();
     std::thread::spawn(move || {
-        std::thread::sleep(std::time::Duration::from_secs(100));
+        std::thread::sleep(std::time::Duration::from_secs(5));
         if should_panic_clone.load(std::sync::atomic::Ordering::SeqCst) {
             std::process::exit(exitcode::SOFTWARE);
         }
@@ -31,19 +31,21 @@ fn use_inner_html(id: &'static str) -> Option<String> {
 
     use_effect(move || {
         spawn(async move {
-            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
+            tokio::time::sleep(std::time::Duration::from_millis(2000)).await;
 
-            let res = dioxus::eval(format!(
+            let res = eval(&format!(
                 r#"let element = document.getElementById('{}');
                     return element.innerHTML"#,
                 id
             ))
-            .await;
+            .unwrap()
+            .await
+            .unwrap();
 
-            if let Ok(html) = res {
+            if let Some(html) = res.as_str() {
                 // serde_json::Value::String(html)
                 println!("html: {}", html);
-                value.set(Some(html));
+                value.set(Some(html.to_string()));
             }
         });
     });
@@ -51,7 +53,7 @@ fn use_inner_html(id: &'static str) -> Option<String> {
     value.read().clone()
 }
 
-const EXPECTED_HTML: &str = r#"<div id="5" style="width: 100px; height: 100px; color: rgb(0, 0, 0);"><input type="checkbox"><h1>text</h1><div><p>hello world</p></div></div>"#;
+const EXPECTED_HTML: &str = r#"<div style="width: 100px; height: 100px; color: rgb(0, 0, 0);" id="5"><input type="checkbox"><h1>text</h1><div><p>hello world</p></div></div>"#;
 
 fn check_html_renders() -> Element {
     let inner_html = use_inner_html("main_div");
@@ -62,10 +64,6 @@ fn check_html_renders() -> Element {
         println!("{}", raw_html);
         let fragment = &raw_html;
         let expected = EXPECTED_HTML;
-        // let fragment = scraper::Html::parse_fragment(&raw_html);
-        // println!("fragment: {}", fragment.html());
-        // let expected = scraper::Html::parse_fragment(EXPECTED_HTML);
-        // println!("expected: {}", expected.html());
         assert_eq!(raw_html, EXPECTED_HTML);
         if fragment == expected {
             println!("html matches");

+ 0 - 10
packages/dioxus/src/lib.rs

@@ -85,13 +85,3 @@ pub use dioxus_tui as tui;
 
 #[cfg(feature = "ssr")]
 pub use dioxus_ssr as ssr;
-
-/// Try to evaluate javascript in the target window
-///
-/// For the browser, this is the window object
-/// For desktop/mobile, this is the webview object
-///
-/// For native, it will try and use the platform's JS engine if available
-pub async fn eval(src: String) -> std::result::Result<String, Box<dyn std::error::Error>> {
-    todo!()
-}

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

@@ -72,8 +72,8 @@ pub use use_coroutine::*;
 mod use_future;
 pub use use_future::*;
 
-mod use_sorted;
-pub use use_sorted::*;
+// mod use_sorted;
+// pub use use_sorted::*;
 
 mod use_resource;
 pub use use_resource::*;

+ 3 - 6
packages/hooks/src/use_memo.rs

@@ -1,11 +1,8 @@
+use crate::dependency::Dependency;
+use crate::use_signal;
 use dioxus_core::prelude::*;
-use dioxus_signals::{CopyValue, ReadOnlySignal, Readable, Signal, SignalData};
+use dioxus_signals::{ReadOnlySignal, Readable, Signal, SignalData};
 use dioxus_signals::{Storage, Writable};
-// use generational_box::Storage;
-
-use crate::use_signal;
-use crate::{dependency::Dependency, use_hook_did_run};
-// use dioxus_signals::{signal::SignalData, ReadOnlySignal, Signal};
 
 /// Creates a new unsync Selector. The selector will be run immediately and whenever any signal it reads changes.
 ///

+ 1 - 1
packages/liveview/src/pool.rs

@@ -6,7 +6,7 @@ use crate::{
     LiveViewError,
 };
 use dioxus_core::prelude::*;
-use dioxus_html::{select, EventData, HtmlEvent, PlatformEventData};
+use dioxus_html::{EventData, HtmlEvent, PlatformEventData};
 use dioxus_interpreter_js::MutationState;
 use futures_util::{pin_mut, SinkExt, StreamExt};
 use serde::Serialize;

+ 1 - 1
packages/router/Cargo.toml

@@ -41,7 +41,7 @@ fullstack = ["dioxus-fullstack"]
 
 [dev-dependencies]
 axum = { version = "0.6.1", features = ["ws"] }
-dioxus = { workspace = true }
+dioxus = { workspace = true, features = ["router" ]}
 # dioxus-liveview = { workspace = true, features = ["axum"] }
 dioxus-ssr = { path = "../ssr" }
 criterion = { version = "0.5", features = ["async_tokio", "html_reports"] }

+ 1 - 1
packages/router/examples/simple_routes.rs

@@ -106,7 +106,7 @@ fn Route2(user_id: usize) -> Element {
 fn Route3(dynamic: String) -> Element {
     let mut current_route_str = use_signal(String::new);
 
-    let current_route = use_route()?;
+    let current_route = use_route();
     let parsed = Route::from_str(&current_route_str.read());
 
     let site_map = Route::SITE_MAP

+ 21 - 7
packages/router/src/components/link.rs

@@ -81,6 +81,9 @@ impl From<&Url> for IntoRoutable {
 /// The properties for a [`Link`].
 #[derive(Props, Clone, PartialEq)]
 pub struct LinkProps {
+    /// The class attribute for the `a` tag.
+    pub class: Option<String>,
+
     /// A class to apply to the generate HTML anchor tag if the `target` route is active.
     pub active_class: Option<String>,
 
@@ -204,6 +207,7 @@ pub fn Link(props: LinkProps) -> Element {
         onclick_only,
         rel,
         to,
+        class,
         ..
     } = props;
 
@@ -226,9 +230,19 @@ pub fn Link(props: LinkProps) -> Element {
         IntoRoutable::Route(route) => router.any_route_to_string(&**route),
     };
     let parsed_route: NavigationTarget<Rc<dyn Any>> = router.resolve_into_routable(to.clone());
-    let class = active_class
-        .and_then(|active_class| (href == current_url).then(|| format!(" {active_class}")))
-        .unwrap_or_default();
+
+    let mut class_ = String::new();
+    if let Some(c) = class {
+        class_.push_str(&c);
+    }
+    if let Some(c) = active_class {
+        if href == current_url {
+            if !class_.is_empty() {
+                class_.push(' ');
+            }
+            class_.push_str(&c);
+        }
+    }
 
     let tag_target = new_tab.then_some("_blank").unwrap_or_default();
 
@@ -254,10 +268,10 @@ pub fn Link(props: LinkProps) -> Element {
     rsx! {
         a {
             onclick: action,
-            href: "{href}",
-            prevent_default: "{prevent_default}",
-            class: "{class}",
-            rel: "{rel}",
+            href,
+            prevent_default,
+            class: class_,
+            rel,
             target: "{tag_target}",
             ..attributes,
             {children}

+ 8 - 8
packages/router/src/contexts/router.rs

@@ -157,7 +157,7 @@ impl RouterContext {
     /// Will fail silently if there is no previous location to go to.
     pub fn go_back(&self) {
         {
-            self.inner.write().history.go_back();
+            self.inner.clone().write().history.go_back();
         }
 
         self.change_route();
@@ -168,7 +168,7 @@ impl RouterContext {
     /// Will fail silently if there is no next location to go to.
     pub fn go_forward(&self) {
         {
-            self.inner.write().history.go_forward();
+            self.inner.clone().write().history.go_forward();
         }
 
         self.change_route();
@@ -179,7 +179,7 @@ impl RouterContext {
         target: NavigationTarget<Rc<dyn Any>>,
     ) -> Option<ExternalNavigationFailure> {
         {
-            let mut write = self.inner.write();
+            let mut write = self.inner.clone().write();
             match target {
                 NavigationTarget::Internal(p) => write.history.push(p),
                 NavigationTarget::External(e) => return write.external(e),
@@ -195,7 +195,7 @@ impl RouterContext {
     pub fn push(&self, target: impl Into<IntoRoutable>) -> Option<ExternalNavigationFailure> {
         let target = self.resolve_into_routable(target.into());
         {
-            let mut write = self.inner.write();
+            let mut write = self.inner.clone().write();
             match target {
                 NavigationTarget::Internal(p) => write.history.push(p),
                 NavigationTarget::External(e) => return write.external(e),
@@ -212,7 +212,7 @@ impl RouterContext {
         let target = self.resolve_into_routable(target.into());
 
         {
-            let mut state = self.inner.write();
+            let mut state = self.inner.clone().write();
             match target {
                 NavigationTarget::Internal(p) => state.history.replace(p),
                 NavigationTarget::External(e) => return state.external(e),
@@ -276,14 +276,14 @@ impl RouterContext {
 
     /// Clear any unresolved errors
     pub fn clear_error(&self) {
-        let mut write_inner = self.inner.write();
+        let mut write_inner = self.inner.clone().write();
         write_inner.unresolved_error = None;
 
         write_inner.update_subscribers();
     }
 
     pub(crate) fn render_error(&self) -> Element {
-        let inner_read = self.inner.write();
+        let inner_read = self.inner.clone().write();
         inner_read
             .unresolved_error
             .as_ref()
@@ -297,7 +297,7 @@ impl RouterContext {
             let callback = callback.clone();
             drop(self_read);
             if let Some(new) = callback(myself) {
-                let mut self_write = self.inner.write();
+                let mut self_write = self.inner.clone().write();
                 match new {
                     NavigationTarget::Internal(p) => self_write.history.replace(p),
                     NavigationTarget::External(e) => return self_write.external(e),

+ 3 - 5
packages/router/src/hooks/use_route.rs

@@ -33,7 +33,7 @@ use crate::utils::use_router_internal::use_router_internal;
 ///
 /// #[component]
 /// fn Index() -> Element {
-///     let path: Route = use_route(&cx).unwrap();
+///     let path: Route = use_route();
 ///     rsx! {
 ///         h2 { "Current Path" }
 ///         p { "{path}" }
@@ -45,14 +45,12 @@ use crate::utils::use_router_internal::use_router_internal;
 /// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><h2>Current Path</h2><p>/</p>")
 /// ```
 #[must_use]
-pub fn use_route<R: Routable + Clone>() -> Option<R> {
+pub fn use_route<R: Routable + Clone>() -> R {
     match use_router_internal() {
-        Some(r) => Some(r.current()),
+        Some(r) => r.current(),
         None => {
             #[cfg(debug_assertions)]
             panic!("`use_route` must have access to a parent router");
-            #[allow(unreachable_code)]
-            None
         }
     }
 }

+ 9 - 17
packages/router/tests/via_ssr/link.rs

@@ -73,11 +73,10 @@ fn href_internal() {
     }
 
     let expected = format!(
-        "<h1>App</h1><a {href} {default} {class} {id} {rel} {target}>Link</a>",
+        "<h1>App</h1><a {href} {default} {class} {rel} {target}>Link</a>",
         href = r#"href="/test""#,
         default = r#"dioxus-prevent-default="onclick""#,
         class = r#"class="""#,
-        id = r#"id="""#,
         rel = r#"rel="""#,
         target = r#"target="""#
     );
@@ -111,11 +110,10 @@ fn href_external() {
     }
 
     let expected = format!(
-        "<h1>App</h1><a {href} {default} {class} {id} {rel} {target}>Link</a>",
+        "<h1>App</h1><a {href} {default} {class} {rel} {target}>Link</a>",
         href = r#"href="https://dioxuslabs.com/""#,
         default = r#"dioxus-prevent-default="""#,
         class = r#"class="""#,
-        id = r#"id="""#,
         rel = r#"rel="noopener noreferrer""#,
         target = r#"target="""#
     );
@@ -150,11 +148,10 @@ fn with_class() {
     }
 
     let expected = format!(
-        "<h1>App</h1><a {href} {default} {class} {id} {rel} {target}>Link</a>",
+        "<h1>App</h1><a {href} {default} {class} {rel} {target}>Link</a>",
         href = r#"href="/test""#,
         default = r#"dioxus-prevent-default="onclick""#,
         class = r#"class="test_class""#,
-        id = r#"id="""#,
         rel = r#"rel="""#,
         target = r#"target="""#
     );
@@ -183,11 +180,10 @@ fn with_active_class_active() {
     }
 
     let expected = format!(
-        "<h1>App</h1><a {href} {default} {class} {id} {rel} {target}>Link</a>",
+        "<h1>App</h1><a {href} {default} {class} {rel} {target}>Link</a>",
         href = r#"href="/""#,
         default = r#"dioxus-prevent-default="onclick""#,
         class = r#"class="test_class active_class""#,
-        id = r#"id="""#,
         rel = r#"rel="""#,
         target = r#"target="""#
     );
@@ -223,11 +219,10 @@ fn with_active_class_inactive() {
     }
 
     let expected = format!(
-        "<h1>App</h1><a {href} {default} {class} {id} {rel} {target}>Link</a>",
+        "<h1>App</h1><a {href} {default} {class} {rel} {target}>Link</a>",
         href = r#"href="/test""#,
         default = r#"dioxus-prevent-default="onclick""#,
         class = r#"class="test_class""#,
-        id = r#"id="""#,
         rel = r#"rel="""#,
         target = r#"target="""#
     );
@@ -262,7 +257,7 @@ fn with_id() {
     }
 
     let expected = format!(
-        "<h1>App</h1><a {href} {default} {class} {id} {rel} {target}>Link</a>",
+        "<h1>App</h1><a {href} {default} {class} {rel} {target} {id}>Link</a>",
         href = r#"href="/test""#,
         default = r#"dioxus-prevent-default="onclick""#,
         class = r#"class="""#,
@@ -301,11 +296,10 @@ fn with_new_tab() {
     }
 
     let expected = format!(
-        "<h1>App</h1><a {href} {default} {class} {id} {rel} {target}>Link</a>",
+        "<h1>App</h1><a {href} {default} {class} {rel} {target}>Link</a>",
         href = r#"href="/test""#,
         default = r#"dioxus-prevent-default="""#,
         class = r#"class="""#,
-        id = r#"id="""#,
         rel = r#"rel="""#,
         target = r#"target="_blank""#
     );
@@ -333,11 +327,10 @@ fn with_new_tab_external() {
     }
 
     let expected = format!(
-        "<h1>App</h1><a {href} {default} {class} {id} {rel} {target}>Link</a>",
+        "<h1>App</h1><a {href} {default} {class} {rel} {target}>Link</a>",
         href = r#"href="https://dioxuslabs.com/""#,
         default = r#"dioxus-prevent-default="""#,
         class = r#"class="""#,
-        id = r#"id="""#,
         rel = r#"rel="noopener noreferrer""#,
         target = r#"target="_blank""#
     );
@@ -372,11 +365,10 @@ fn with_rel() {
     }
 
     let expected = format!(
-        "<h1>App</h1><a {href} {default} {class} {id} {rel} {target}>Link</a>",
+        "<h1>App</h1><a {href} {default} {class} {rel} {target}>Link</a>",
         href = r#"href="/test""#,
         default = r#"dioxus-prevent-default="onclick""#,
         class = r#"class="""#,
-        id = r#"id="""#,
         rel = r#"rel="test_rel""#,
         target = r#"target="""#
     );

+ 1 - 1
packages/rsx/src/element.rs

@@ -20,7 +20,7 @@ pub struct Element {
     pub name: ElementName,
     pub key: Option<IfmtInput>,
     pub attributes: Vec<AttributeType>,
-    pub(crate) merged_attributes: Vec<AttributeType>,
+    pub merged_attributes: Vec<AttributeType>,
     pub children: Vec<BodyNode>,
     pub brace: syn::token::Brace,
 }

+ 11 - 11
packages/signals/src/comparer.rs

@@ -19,23 +19,23 @@ impl<R: Eq + Hash> Comparer<R> {
     ///
     /// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_memo`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to.
     pub fn new(mut f: impl FnMut() -> R + 'static) -> Comparer<R> {
-        let subscribers: CopyValue<FxHashMap<R, Signal<bool>>> =
+        let mut subscribers: CopyValue<FxHashMap<R, Signal<bool>>> =
             CopyValue::new(FxHashMap::default());
-        let previous = CopyValue::new(None);
+        let mut previous = CopyValue::new(None);
 
         Effect::new(move || {
-            let subscribers = subscribers.read();
+            let mut subscribers = subscribers.read();
             let mut previous = previous.write();
 
             if let Some(previous) = previous.take() {
-                if let Some(value) = subscribers.get(&previous) {
-                    *value.write() = false;
+                if let Some(mut value) = subscribers.get(&previous).cloned() {
+                    value.set(false)
                 }
             }
 
             let current = f();
 
-            if let Some(value) = subscribers.get(&current) {
+            if let Some(mut value) = subscribers.get(&current).cloned() {
                 *value.write() = true;
             }
 
@@ -53,21 +53,21 @@ impl<R: Eq + Hash, S: Storage<SignalData<bool>>> Comparer<R, S> {
     pub fn new_maybe_sync(mut f: impl FnMut() -> R + 'static) -> Comparer<R> {
         let subscribers: CopyValue<FxHashMap<R, Signal<bool>>> =
             CopyValue::new(FxHashMap::default());
-        let previous = CopyValue::new(None);
+        let mut previous = CopyValue::new(None);
 
         Effect::new(move || {
             let subscribers = subscribers.read();
             let mut previous = previous.write();
 
             if let Some(previous) = previous.take() {
-                if let Some(value) = subscribers.get(&previous) {
+                if let Some(mut value) = subscribers.get(&previous).cloned() {
                     *value.write() = false;
                 }
             }
 
             let current = f();
 
-            if let Some(value) = subscribers.get(&current) {
+            if let Some(mut value) = subscribers.get(&current).cloned() {
                 *value.write() = true;
             }
 
@@ -78,8 +78,8 @@ impl<R: Eq + Hash, S: Storage<SignalData<bool>>> Comparer<R, S> {
     }
 
     /// Returns a signal which is true when the value is equal to the value passed to this function.
-    pub fn equal(&self, value: R) -> ReadOnlySignal<bool, S> {
-        let subscribers = self.subscribers.read();
+    pub fn equal(&mut self, value: R) -> ReadOnlySignal<bool, S> {
+        let mut subscribers = self.subscribers.write();
 
         match subscribers.get(&value) {
             Some(&signal) => signal.into(),

+ 9 - 2
packages/signals/src/global/signal.rs

@@ -3,7 +3,7 @@ use crate::write::Writable;
 use crate::Write;
 use dioxus_core::prelude::{IntoAttributeValue, ScopeId};
 use generational_box::{AnyStorage, GenerationalRef, UnsyncStorage};
-use std::{cell::Ref, mem::MaybeUninit, ops::Deref};
+use std::{cell::Ref, io::prelude::Read, mem::MaybeUninit, ops::Deref};
 
 use super::get_global_context;
 use crate::{MappedSignal, Signal};
@@ -43,6 +43,10 @@ impl<T: 'static> GlobalSignal<T> {
         }
     }
 
+    pub fn write(&self) -> Write<T, UnsyncStorage> {
+        self.signal().write()
+    }
+
     /// Get the scope the signal was created in.
     pub fn origin_scope(&self) -> ScopeId {
         ScopeId::ROOT
@@ -143,8 +147,11 @@ impl<T: Clone + 'static> Deref for GlobalSignal<T> {
 
         // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
         let uninit_callable = MaybeUninit::<Self>::uninit();
+
         // Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
-        let uninit_closure = move || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone();
+        let uninit_closure = move || {
+            <GlobalSignal<T> as Readable<T>>::read(unsafe { &*uninit_callable.as_ptr() }).clone()
+        };
 
         // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
         let size_of_closure = std::mem::size_of_val(&uninit_closure);

+ 1 - 1
packages/signals/src/props.rs

@@ -22,7 +22,7 @@ fn into_signal_compiles() {
 
     fn don_t_run() {
         takes_signal_string("hello world");
-        takes_signal_string(ReadOnlySignal::new(String::from("hello world")));
+        takes_signal_string(Signal::new(String::from("hello world")));
         takes_option_signal_string("hello world");
     }
 }

+ 4 - 2
packages/signals/src/rt.rs

@@ -64,7 +64,9 @@ fn current_owner<S: Storage<T>, T>() -> Owner<S> {
     let id = TypeId::of::<S>();
     let override_owner = if id == TypeId::of::<SyncStorage>() {
         SYNC_OWNER.with(|cell| {
-            cell.borrow().clone().map(|owner| {
+            let owner = cell.borrow();
+
+            owner.clone().map(|owner| {
                 *(Box::new(owner) as Box<dyn Any>)
                     .downcast::<Owner<S>>()
                     .unwrap()
@@ -268,7 +270,7 @@ impl<T: 'static, S: Storage<T>> Writable<T> for CopyValue<T, S> {
         self.value.try_write()
     }
 
-    fn write(&self) -> Self::Mut<T> {
+    fn write(&mut self) -> Self::Mut<T> {
         self.value.write()
     }
 

+ 9 - 9
packages/signals/src/write.rs

@@ -19,7 +19,7 @@ pub trait Writable<T: 'static>: Readable<T> {
 
     /// Get a mutable reference to the value. If the value has been dropped, this will panic.
     #[track_caller]
-    fn write(&self) -> Self::Mut<T> {
+    fn write(&mut self) -> Self::Mut<T> {
         self.try_write().unwrap()
     }
 
@@ -28,7 +28,7 @@ pub trait Writable<T: 'static>: Readable<T> {
 
     /// Run a function with a mutable reference to the value. If the value has been dropped, this will panic.
     #[track_caller]
-    fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
+    fn with_mut<O>(&mut self, f: impl FnOnce(&mut T) -> O) -> O {
         f(&mut *self.write())
     }
 
@@ -49,7 +49,7 @@ pub trait Writable<T: 'static>: Readable<T> {
 
     /// Index into the inner value and return a reference to the result.
     #[track_caller]
-    fn index_mut<I>(&self, index: I) -> Self::Mut<T::Output>
+    fn index_mut<I>(&mut self, index: I) -> Self::Mut<T::Output>
     where
         T: std::ops::IndexMut<I>,
     {
@@ -58,7 +58,7 @@ pub trait Writable<T: 'static>: Readable<T> {
 
     /// Takes the value out of the Signal, leaving a Default in its place.
     #[track_caller]
-    fn take(&self) -> T
+    fn take(&mut self) -> T
     where
         T: Default,
     {
@@ -67,7 +67,7 @@ pub trait Writable<T: 'static>: Readable<T> {
 
     /// Replace the value in the Signal, returning the old value.
     #[track_caller]
-    fn replace(&self, value: T) -> T {
+    fn replace(&mut self, value: T) -> T {
         self.with_mut(|v| std::mem::replace(v, value))
     }
 }
@@ -75,12 +75,12 @@ pub trait Writable<T: 'static>: Readable<T> {
 /// An extension trait for Writable<Option<T>> that provides some convenience methods.
 pub trait WritableOptionExt<T: 'static>: Writable<Option<T>> {
     /// Gets the value out of the Option, or inserts the given value if the Option is empty.
-    fn get_or_insert(&self, default: T) -> Self::Mut<T> {
+    fn get_or_insert(&mut self, default: T) -> Self::Mut<T> {
         self.get_or_insert_with(|| default)
     }
 
     /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty.
-    fn get_or_insert_with(&self, default: impl FnOnce() -> T) -> Self::Mut<T> {
+    fn get_or_insert_with(&mut self, default: impl FnOnce() -> T) -> Self::Mut<T> {
         let borrow = self.read();
         if borrow.is_none() {
             drop(borrow);
@@ -93,7 +93,7 @@ pub trait WritableOptionExt<T: 'static>: Writable<Option<T>> {
 
     /// Attempts to write the inner value of the Option.
     #[track_caller]
-    fn as_mut(&self) -> Option<Self::Mut<T>> {
+    fn as_mut(&mut self) -> Option<Self::Mut<T>> {
         Self::try_map_mut(self.write(), |v: &mut Option<T>| v.as_mut())
     }
 }
@@ -169,7 +169,7 @@ pub trait WritableVecExt<T: 'static>: Writable<Vec<T>> {
 
     /// Try to mutably get an element from the vector.
     #[track_caller]
-    fn get_mut(&self, index: usize) -> Option<Self::Mut<T>> {
+    fn get_mut(&mut self, index: usize) -> Option<Self::Mut<T>> {
         Self::try_map_mut(self.write(), |v: &mut Vec<T>| v.get_mut(index))
     }
 

+ 1 - 1
packages/ssr/tests/hydration.rs

@@ -175,7 +175,7 @@ fn components_hydrate() {
 
 #[test]
 fn hello_world_hydrates() {
-    use dioxus_signals::use_signal;
+    use dioxus::hooks::use_signal;
 
     fn app() -> Element {
         let mut count = use_signal(|| 0);

+ 11 - 13
packages/web/src/cfg.rs

@@ -10,19 +10,12 @@
 pub struct Config {
     pub(crate) hydrate: bool,
     pub(crate) root: ConfigRoot,
-    pub(crate) cached_strings: Vec<String>,
     pub(crate) default_panic_hook: bool,
 }
 
-impl Default for Config {
-    fn default() -> Self {
-        Self {
-            hydrate: false,
-            root: ConfigRoot::RootName("main".to_string()),
-            cached_strings: Vec::new(),
-            default_panic_hook: true,
-        }
-    }
+pub(crate) enum ConfigRoot {
+    RootName(String),
+    RootElement(web_sys::Element),
 }
 
 impl Config {
@@ -72,7 +65,12 @@ impl Config {
     }
 }
 
-pub(crate) enum ConfigRoot {
-    RootName(String),
-    RootElement(web_sys::Element),
+impl Default for Config {
+    fn default() -> Self {
+        Self {
+            hydrate: false,
+            root: ConfigRoot::RootName("main".to_string()),
+            default_panic_hook: true,
+        }
+    }
 }

+ 2 - 3
packages/web/src/launch.rs

@@ -28,8 +28,7 @@ pub fn launch_virtual_dom(vdom: VirtualDom, platform_config: Config) {
     });
 }
 
-///
+/// Launch the web application with the given root component and config
 pub fn launch_cfg(root: fn() -> Element, platform_config: Config) {
-    let mut vdom = VirtualDom::new(root);
-    launch_virtual_dom(vdom, platform_config);
+    launch_virtual_dom(VirtualDom::new(root), platform_config);
 }

+ 3 - 5
playwright-tests/fullstack/Cargo.toml

@@ -7,9 +7,7 @@ publish = false
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-web = { path = "../../packages/web", features=["hydrate"], optional = true }
-dioxus = { path = "../../packages/dioxus" }
-dioxus-fullstack = { path = "../../packages/fullstack" }
+dioxus = { workspace = true }
 axum = { version = "0.6.12", optional = true }
 tokio = { version = "1.27.0", features = ["full"], optional = true }
 serde = "1.0.159"
@@ -17,5 +15,5 @@ execute = "0.2.12"
 
 [features]
 default = []
-ssr = ["axum", "tokio", "dioxus-fullstack/axum"]
-web = ["dioxus-web"]
+ssr = ["axum", "tokio", "dioxus/axum"]
+web = ["dioxus/web"]

+ 0 - 1
playwright-tests/fullstack/src/main.rs

@@ -6,7 +6,6 @@
 
 #![allow(non_snake_case)]
 use dioxus::prelude::*;
-use dioxus_fullstack::prelude::*;
 use serde::{Deserialize, Serialize};
 
 fn main() {