Repository: leptos-rs/leptos
Pull request #1935 (2023-10-24 09:06:50)

feat: add `new` method to `Trigger`
Adds a `new` method to `Trigger`.
This PR also implements `Default` for `Trigger` (as was suggested by clippy).


Patch:
diff --git a/.github/workflows/get-changed-examples-matrix.yml b/.github/workflows/get-changed-examples-matrix.yml
index a1c4d14f..d232a708 100644
--- a/.github/workflows/get-changed-examples-matrix.yml
+++ b/.github/workflows/get-changed-examples-matrix.yml
@@ -26,7 +26,7 @@ jobs:
 
       - name: Get example project directories that changed
         id: changed-dirs
-        uses: tj-actions/changed-files@v36
+        uses: tj-actions/changed-files@v39
         with:
           dir_names: true
           dir_names_max_depth: "2"
diff --git a/.github/workflows/get-example-changed.yml b/.github/workflows/get-example-changed.yml
index 11e7dd9f..d250c7e3 100644
--- a/.github/workflows/get-example-changed.yml
+++ b/.github/workflows/get-example-changed.yml
@@ -21,7 +21,7 @@ jobs:
 
       - name: Get example files that changed
         id: changed-files
-        uses: tj-actions/changed-files@v36
+        uses: tj-actions/changed-files@v39
         with:
           files: |
             examples
diff --git a/.github/workflows/get-leptos-changed.yml b/.github/workflows/get-leptos-changed.yml
index 9d5d1e13..5a1e407c 100644
--- a/.github/workflows/get-leptos-changed.yml
+++ b/.github/workflows/get-leptos-changed.yml
@@ -19,7 +19,7 @@ jobs:
 
       - name: Get source files that changed
         id: changed-source
-        uses: tj-actions/changed-files@v36
+        uses: tj-actions/changed-files@v39
         with:
           files: |
             integrations
diff --git a/.github/workflows/run-cargo-make-task.yml b/.github/workflows/run-cargo-make-task.yml
index 9c97fb3b..0de13d14 100644
--- a/.github/workflows/run-cargo-make-task.yml
+++ b/.github/workflows/run-cargo-make-task.yml
@@ -53,7 +53,7 @@ jobs:
         run: trunk --version
 
       - name: Install Node.js
-        uses: actions/setup-node@v3
+        uses: actions/setup-node@v4
         with:
           node-version: 18
 
diff --git a/Cargo.toml b/Cargo.toml
index 91388a05..ddba0fe0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,22 +26,22 @@ members = [
 exclude = ["benchmarks", "examples"]
 
 [workspace.package]
-version = "0.5.1"
+version = "0.5.2"
 
 [workspace.dependencies]
-leptos = { path = "./leptos", version = "0.5.1" }
-leptos_dom = { path = "./leptos_dom", version = "0.5.1" }
-leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.5.1" }
-leptos_macro = { path = "./leptos_macro", version = "0.5.1" }
-leptos_reactive = { path = "./leptos_reactive", version = "0.5.1" }
-leptos_server = { path = "./leptos_server", version = "0.5.1" }
-server_fn = { path = "./server_fn", version = "0.5.1" }
-server_fn_macro = { path = "./server_fn_macro", version = "0.5.1" }
-server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.5.1" }
-leptos_config = { path = "./leptos_config", version = "0.5.1" }
-leptos_router = { path = "./router", version = "0.5.1" }
-leptos_meta = { path = "./meta", version = "0.5.1" }
-leptos_integration_utils = { path = "./integrations/utils", version = "0.5.1" }
+leptos = { path = "./leptos", version = "0.5.2" }
+leptos_dom = { path = "./leptos_dom", version = "0.5.2" }
+leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.5.2" }
+leptos_macro = { path = "./leptos_macro", version = "0.5.2" }
+leptos_reactive = { path = "./leptos_reactive", version = "0.5.2" }
+leptos_server = { path = "./leptos_server", version = "0.5.2" }
+server_fn = { path = "./server_fn", version = "0.5.2" }
+server_fn_macro = { path = "./server_fn_macro", version = "0.5.2" }
+server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.5.2" }
+leptos_config = { path = "./leptos_config", version = "0.5.2" }
+leptos_router = { path = "./router", version = "0.5.2" }
+leptos_meta = { path = "./meta", version = "0.5.2" }
+leptos_integration_utils = { path = "./integrations/utils", version = "0.5.2" }
 
 [profile.release]
 codegen-units = 1
diff --git a/README.md b/README.md
index de54806c..782cdd7e 100644
--- a/README.md
+++ b/README.md
@@ -48,10 +48,6 @@ pub fn main() {
 }
 ```
 
-### Important Note
-
-This example, and the entire `main` branch, now reflect the upcoming `0.5.1` release. You can use `0.5.1` with the `0.5.1-beta` release on crates.io or by a git dependency on the `main` branch of this repo. [Click here for the 0.4.9 `README`](https://crates.io/crates/leptos).
-
 ## About the Framework
 
 Leptos is a full-stack, isomorphic Rust web framework leveraging fine-grained reactivity to build declarative user interfaces.
diff --git a/docs/book/src/view/08_parent_child.md b/docs/book/src/view/08_parent_child.md
index 4d9b526c..ffd0126c 100644
--- a/docs/book/src/view/08_parent_child.md
+++ b/docs/book/src/view/08_parent_child.md
@@ -72,9 +72,7 @@ pub fn App() -> impl IntoView {
 
 
 #[component]
-pub fn ButtonB<F>(on_click: F) -> impl IntoView
-where
-    F: Fn(MouseEvent) + 'static,
+pub fn ButtonB(#[prop(into)] on_click: Callback<MouseEvent>) -> impl IntoView
 {
     view! {
         <button on:click=on_click>
@@ -90,10 +88,50 @@ of keeping local state local, preventing the problem of spaghetti mutation. But
 the logic to mutate that signal needs to exist up in `<App/>`, not down in `<ButtonB/>`. These
 are real trade-offs, not a simple right-or-wrong choice.
 
+> Note the way we use the `Callback<In, Out>` type. This is basically a
+> wrapper around a closure `Fn(In) -> Out` that is also `Copy` and makes it
+> easy to pass around.
+> 
+> We also used the `#[prop(into)]` attribute so we can pass a normal closure into
+> `on_click`. Please see the [chapter "`into` Props"](./03_components.md#into-props) for more details.
+
+### 2.1 Use Closure instead of `Callback`
+
+You can use a Rust closure `Fn(MouseEvent)` directly instead of `Callback`:
+
+```rust
+#[component]
+pub fn App() -> impl IntoView {
+    let (toggled, set_toggled) = create_signal(false);
+    view! {
+        <p>"Toggled? " {toggled}</p>
+        <ButtonB on_click=move |_| set_toggled.update(|value| *value = !*value)/>
+    }
+}
+
+
+#[component]
+pub fn ButtonB<F>(on_click: F) -> impl IntoView
+where
+    F: Fn(MouseEvent) + 'static,
+pub fn ButtonB(#[prop(into)] on_click: Callback<MouseEvent>) -> impl IntoView
+{
+    view! {
+        <button on:click=on_click>
+            "Toggle"
+        </button>
+    }
+}
+```
+
+The code is very similar in this case. On more advanced use-cases using a
+closure might require some cloning compared to using a `Callback`.
+
 > Note the way we declare the generic type `F` here for the callback. If you’re
 > confused, look back at the [generic props](./03_components.html#generic-props) section
 > of the chapter on components.
 
+
 ## 3. Use an Event Listener
 
 You can actually write Option 2 in a slightly different way. If the callback maps directly onto
diff --git a/examples/counters/rust-toolchain.toml b/examples/counters/rust-toolchain.toml
new file mode 100644
index 00000000..e9743fb4
--- /dev/null
+++ b/examples/counters/rust-toolchain.toml
@@ -0,0 +1,3 @@
+
+[toolchain]
+channel = "nightly"
diff --git a/examples/hackernews_islands_axum/Cargo.toml b/examples/hackernews_islands_axum/Cargo.toml
index fcbee56b..b183bc7a 100644
--- a/examples/hackernews_islands_axum/Cargo.toml
+++ b/examples/hackernews_islands_axum/Cargo.toml
@@ -39,7 +39,6 @@ tokio = { version = "1.22.0", features = ["full"], optional = true }
 http = { version = "0.2.8", optional = true }
 web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
 wasm-bindgen = "0.2"
-wee_alloc = "0.4.5"
 lazy_static = "1.4.0"
 
 [features]
diff --git a/examples/hackernews_islands_axum/src/lib.rs b/examples/hackernews_islands_axum/src/lib.rs
index 216b9f69..8024b736 100644
--- a/examples/hackernews_islands_axum/src/lib.rs
+++ b/examples/hackernews_islands_axum/src/lib.rs
@@ -36,12 +36,6 @@ cfg_if! {
     if #[cfg(feature = "hydrate")] {
         use wasm_bindgen::prelude::wasm_bindgen;
 
-        extern crate wee_alloc;
-
-        // Use `wee_alloc` as the global allocator.
-        #[global_allocator]
-        static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
-
         #[wasm_bindgen]
         pub fn hydrate() {
             #[cfg(debug_assertions)]
diff --git a/examples/hackernews_islands_axum/src/main.rs b/examples/hackernews_islands_axum/src/main.rs
index 34696c2c..64f75ed8 100644
--- a/examples/hackernews_islands_axum/src/main.rs
+++ b/examples/hackernews_islands_axum/src/main.rs
@@ -4,7 +4,6 @@ mod ssr_imports {
     pub use hackernews_islands::fallback::file_and_error_handler;
     pub use leptos::*;
     pub use leptos_axum::{generate_route_list, LeptosRoutes};
-    pub use tower_http::{compression::CompressionLayer, services::ServeFile};
 }
 
 #[cfg(feature = "ssr")]
diff --git a/examples/login_with_token_csr_only/client/src/components/navbar.rs b/examples/login_with_token_csr_only/client/src/components/navbar.rs
index df18c4c9..af5b4164 100644
--- a/examples/login_with_token_csr_only/client/src/components/navbar.rs
+++ b/examples/login_with_token_csr_only/client/src/components/navbar.rs
@@ -3,10 +3,10 @@ use leptos::*;
 use leptos_router::*;
 
 #[component]
-pub fn NavBar<F>(logged_in: Signal<bool>, on_logout: F) -> impl IntoView
-where
-    F: Fn() + 'static + Clone,
-{
+pub fn NavBar(
+    logged_in: Signal<bool>,
+    #[prop(into)] on_logout: Callback<()>,
+) -> impl IntoView {
     view! {
         <nav>
             <Show
@@ -21,10 +21,7 @@ where
             >
                 <a
                     href="#"
-                    on:click={
-                        let on_logout = on_logout.clone();
-                        move |_| on_logout()
-                    }
+                    on:click=move |_| on_logout.call(())
                 >
                     "Logout"
                 </a>
diff --git a/examples/login_with_token_csr_only/client/src/lib.rs b/examples/login_with_token_csr_only/client/src/lib.rs
index 53b33f0d..48bd7eeb 100644
--- a/examples/login_with_token_csr_only/client/src/lib.rs
+++ b/examples/login_with_token_csr_only/client/src/lib.rs
@@ -57,7 +57,7 @@ pub fn App() -> impl IntoView {
 
     // -- callbacks -- //
 
-    let on_logout = move || {
+    let on_logout = move |_| {
         logout.dispatch(());
     };
 
diff --git a/examples/login_with_token_csr_only/client/src/pages/login.rs b/examples/login_with_token_csr_only/client/src/pages/login.rs
index bf9725b8..b2a85a92 100644
--- a/examples/login_with_token_csr_only/client/src/pages/login.rs
+++ b/examples/login_with_token_csr_only/client/src/pages/login.rs
@@ -8,10 +8,10 @@ use leptos::*;
 use leptos_router::*;
 
 #[component]
-pub fn Login<F>(api: UnauthorizedApi, on_success: F) -> impl IntoView
-where
-    F: Fn(AuthorizedApi) + 'static + Clone,
-{
+pub fn Login(
+    api: UnauthorizedApi,
+    #[prop(into)] on_success: Callback<AuthorizedApi>,
+) -> impl IntoView {
     let (login_error, set_login_error) = create_signal(None::<String>);
     let (wait_for_response, set_wait_for_response) = create_signal(false);
 
@@ -21,7 +21,6 @@ where
             let email = email.to_string();
             let password = password.to_string();
             let credentials = Credentials { email, password };
-            let on_success = on_success.clone();
             async move {
                 set_wait_for_response.update(|w| *w = true);
                 let result = api.login(&credentials).await;
@@ -29,7 +28,7 @@ where
                 match result {
                     Ok(res) => {
                         set_login_error.update(|e| *e = None);
-                        on_success(res);
+                        on_success.call(res);
                     }
                     Err(err) => {
                         let msg = match err {
diff --git a/examples/parent_child/rust-toolchain.toml b/examples/parent_child/rust-toolchain.toml
new file mode 100644
index 00000000..e9743fb4
--- /dev/null
+++ b/examples/parent_child/rust-toolchain.toml
@@ -0,0 +1,3 @@
+
+[toolchain]
+channel = "nightly"
diff --git a/examples/parent_child/src/lib.rs b/examples/parent_child/src/lib.rs
index fcb7cc6b..c67f9add 100644
--- a/examples/parent_child/src/lib.rs
+++ b/examples/parent_child/src/lib.rs
@@ -74,13 +74,11 @@ pub fn ButtonA(
 
 /// Button B receives a closure
 #[component]
-pub fn ButtonB<F>(
+pub fn ButtonB(
     /// Callback that will be invoked when the button is clicked.
-    on_click: F,
-) -> impl IntoView
-where
-    F: Fn(MouseEvent) + 'static,
-{
+    #[prop(into)]
+    on_click: Callback<MouseEvent>,
+) -> impl IntoView {
     view! {
 
         <button
@@ -89,18 +87,6 @@ where
             "Toggle Right"
         </button>
     }
-
-    // just a note: in an ordinary function ButtonB could take on_click: impl Fn(MouseEvent) + 'static
-    // and save you from typing out the generic
-    // the component macro actually expands to define a
-    //
-    // struct ButtonBProps<F> where F: Fn(MouseEvent) + 'static {
-    //   on_click: F
-    // }
-    //
-    // this is what allows us to have named props in our component invocation,
-    // instead of an ordered list of function arguments
-    // if Rust ever had named function arguments we could drop this requirement
 }
 
 /// Button C is a dummy: it renders a button but doesn't handle
diff --git a/integrations/actix/src/lib.rs b/integrations/actix/src/lib.rs
index 418aa10a..6b3c385d 100644
--- a/integrations/actix/src/lib.rs
+++ b/integrations/actix/src/lib.rs
@@ -115,7 +115,7 @@ pub fn redirect(path: &str) {
     }
 }
 
-/// An Actix [Route](actix_web::Route) that listens for a `POST` request with
+/// An Actix [struct@Route](actix_web::Route) that listens for a `POST` request with
 /// Leptos server function arguments in the body, runs the server function if found,
 /// and returns the resulting [HttpResponse].
 ///
@@ -156,7 +156,7 @@ pub fn handle_server_fns() -> Route {
     handle_server_fns_with_context(|| {})
 }
 
-/// An Actix [Route](actix_web::Route) that listens for `GET` or `POST` requests with
+/// An Actix [struct@Route](actix_web::Route) that listens for `GET` or `POST` requests with
 /// Leptos server function arguments in the URL (`GET`) or body (`POST`),
 /// runs the server function if found, and returns the resulting [HttpResponse].
 ///
@@ -292,7 +292,7 @@ pub fn handle_server_fns_with_context(
                 } else {
                     HttpResponse::BadRequest().body(format!(
                         "Could not find a server function at the route {:?}. \
-                         \n\nIt's likely that either 
+                         \n\nIt's likely that either
                          1. The API prefix you specify in the `#[server]` \
                          macro doesn't match the prefix at which your server \
                          function handler is mounted, or \n2. You are on a \
@@ -308,7 +308,7 @@ pub fn handle_server_fns_with_context(
     )
 }
 
-/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
+/// Returns an Actix [struct@Route](actix_web::Route) that listens for a `GET` request and tries
 /// to route it using [leptos_router], serving an HTML stream of your application. The stream
 /// will include fallback content for any `<Suspense/>` nodes, and be immediately interactive,
 /// but requires some client-side JavaScript.
@@ -376,7 +376,7 @@ where
     render_app_to_stream_with_context(options, || {}, app_fn, method)
 }
 
-/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
+/// Returns an Actix [struct@Route](actix_web::Route) that listens for a `GET` request and tries
 /// to route it using [leptos_router], serving an in-order HTML stream of your application.
 /// This stream will pause at each `<Suspense/>` node and wait for it to resolve before
 /// sending down its HTML. The app will become interactive once it has fully loaded.
@@ -445,7 +445,7 @@ where
     render_app_to_stream_in_order_with_context(options, || {}, app_fn, method)
 }
 
-/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
+/// Returns an Actix [struct@Route](actix_web::Route) that listens for a `GET` request and tries
 /// to route it using [leptos_router], asynchronously rendering an HTML page after all
 /// `async` [Resource](leptos::Resource)s have loaded.
 ///
@@ -512,7 +512,7 @@ where
     render_app_async_with_context(options, || {}, app_fn, method)
 }
 
-/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
+/// Returns an Actix [struct@Route] that listens for a `GET` request and tries
 /// to route it using [leptos_router], serving an HTML stream of your application.
 ///
 /// This function allows you to provide additional information to Leptos for your route.
@@ -543,7 +543,7 @@ where
     )
 }
 
-/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
+/// Returns an Actix [struct@Route](actix_web::Route) that listens for a `GET` request and tries
 /// to route it using [leptos_router], serving an HTML stream of your application.
 ///
 /// This function allows you to provide additional information to Leptos for your route.
@@ -606,7 +606,7 @@ where
     }
 }
 
-/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
+/// Returns an Actix [struct@Route](actix_web::Route) that listens for a `GET` request and tries
 /// to route it using [leptos_router], serving an in-order HTML stream of your application.
 ///
 /// This function allows you to provide additional information to Leptos for your route.
@@ -657,7 +657,7 @@ where
     }
 }
 
-/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
+/// Returns an Actix [struct@Route](actix_web::Route) that listens for a `GET` request and tries
 /// to route it using [leptos_router], asynchronously serving the page once all `async`
 /// [Resource](leptos::Resource)s have loaded.
 ///
@@ -1361,7 +1361,7 @@ impl LeptosRoutes for &mut ServiceConfig {
 
 /// A helper to make it easier to use Actix extractors in server functions. This takes
 /// a handler function as its argument. The handler follows similar rules to an Actix
-/// [Handler](actix_web::Handler): it is an async function that receives arguments that
+/// [Handler]: it is an async function that receives arguments that
 /// will be extracted from the request and returns some value.
 ///
 /// ```rust,ignore
@@ -1416,9 +1416,9 @@ where
 }
 
 /// A helper to make it easier to use Axum extractors in server functions, with a
-/// simpler API than [`extract`].
+/// simpler API than [`extract()`].
 ///
-/// It is generic over some type `T` that implements [`FromRequestParts`] and can
+/// It is generic over some type `T` that implements [`FromRequest`] and can
 /// therefore be used in an extractor. The compiler can often infer this type.
 ///
 /// Any error that occurs during extraction is converted to a [`ServerFnError`].
diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs
index dc208a92..1d0393e5 100644
--- a/integrations/axum/src/lib.rs
+++ b/integrations/axum/src/lib.rs
@@ -7,7 +7,7 @@
 //! To run in this environment, you need to disable the default feature set and enable
 //! the `wasm` feature on `leptos_axum` in your `Cargo.toml`.
 //! ```toml
-//! leptos_axum = { version = "0.5.1", default-features = false, features = ["wasm"] }
+//! leptos_axum = { version = "0.5.2", default-features = false, features = ["wasm"] }
 //! ```
 //!
 //! ## Features
diff --git a/integrations/utils/src/lib.rs b/integrations/utils/src/lib.rs
index 10d7a51b..702e18fc 100644
--- a/integrations/utils/src/lib.rs
+++ b/integrations/utils/src/lib.rs
@@ -125,7 +125,7 @@ pub fn html_parts_separated(
                         }});
                     </script>
                     {leptos_autoreload}
-                    "#
+                </head>"#
     );
     let tail = "</body></html>";
     (head, tail)
@@ -155,5 +155,5 @@ pub async fn build_async_response(
 
     runtime.dispose();
 
-    format!("{head}</head><body{body_meta}>{buf}{tail}")
+    format!("{head}<body{body_meta}>{buf}{tail}")
 }
diff --git a/integrations/viz/src/lib.rs b/integrations/viz/src/lib.rs
index ac4491d6..edea6f7f 100644
--- a/integrations/viz/src/lib.rs
+++ b/integrations/viz/src/lib.rs
@@ -306,7 +306,7 @@ async fn handle_server_fns_inner(
                                 .body(Body::from(format!(
                                     "Could not find a server function at the \
                                      route {fn_name}. \n\nIt's likely that \
-                                     either 
+                                     either
                                     1. The API prefix you specify in the \
                                      `#[server]` macro doesn't match the \
                                      prefix at which your server function \
@@ -329,7 +329,7 @@ async fn handle_server_fns_inner(
 
     rx.await.map_err(Error::normal)
 }
-/// Returns a Viz [Handler](viz::Handler) that listens for a `GET` request and tries
+/// Returns a Viz [Handler] that listens for a `GET` request and tries
 /// to route it using [leptos_router], serving an HTML stream of your application.
 ///
 /// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s context before
@@ -380,8 +380,8 @@ async fn handle_server_fns_inner(
 /// This function always provides context values including the following types:
 /// - [RequestParts]
 /// - [ResponseOptions]
-/// - [MetaContext](leptos_meta::MetaContext)
-/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
+/// - [MetaContext]
+/// - [RouterIntegrationContext]
 pub fn render_app_to_stream<IV>(
     options: LeptosOptions,
     app_fn: impl Fn() -> IV + Clone + Send + 'static,
@@ -397,7 +397,7 @@ where
     render_app_to_stream_with_context(options, || {}, app_fn)
 }
 
-/// Returns a Viz [Handler](viz::Handler) that listens for a `GET` request and tries
+/// Returns a Viz [Handler] that listens for a `GET` request and tries
 /// to route it using [leptos_router], serving an HTML stream of your application.
 /// This stream will pause at each `<Suspense/>` node and wait for it to resolve before
 /// sending down its HTML. The app will become interactive once it has fully loaded.
@@ -450,8 +450,8 @@ where
 /// This function always provides context values including the following types:
 /// - [RequestParts]
 /// - [ResponseOptions]
-/// - [MetaContext](leptos_meta::MetaContext)
-/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
+/// - [MetaContext]
+/// - [RouterIntegrationContext]
 pub fn render_app_to_stream_in_order<IV>(
     options: LeptosOptions,
     app_fn: impl Fn() -> IV + Clone + Send + 'static,
@@ -467,7 +467,7 @@ where
     render_app_to_stream_in_order_with_context(options, || {}, app_fn)
 }
 
-/// Returns a Viz [Handler](viz::Handler) that listens for a `GET` request and tries
+/// Returns a Viz [Handler] that listens for a `GET` request and tries
 /// to route it using [leptos_router], serving an HTML stream of your application.
 ///
 /// This version allows us to pass Viz State/Extractor or other infro from Viz or network
@@ -492,8 +492,8 @@ where
 /// This function always provides context values including the following types:
 /// - [RequestParts]
 /// - [ResponseOptions]
-/// - [MetaContext](leptos_meta::MetaContext)
-/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
+/// - [MetaContext]
+/// - [RouterIntegrationContext]
 pub fn render_app_to_stream_with_context<IV>(
     options: LeptosOptions,
     additional_context: impl Fn() + Clone + Send + 'static,
@@ -515,7 +515,7 @@ where
     )
 }
 
-/// Returns a Viz [Handler](viz::Handler) that listens for a `GET` request and tries
+/// Returns a Viz [Handler] that listens for a `GET` request and tries
 /// to route it using [leptos_router], serving an HTML stream of your application.
 ///
 /// This version allows us to pass Viz State/Extractor or other infro from Viz or network
@@ -533,8 +533,8 @@ where
 /// This function always provides context values including the following types:
 /// - [RequestParts]
 /// - [ResponseOptions]
-/// - [MetaContext](leptos_meta::MetaContext)
-/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
+/// - [MetaContext]
+/// - [RouterIntegrationContext]
 pub fn render_app_to_stream_with_context_and_replace_blocks<IV>(
     options: LeptosOptions,
     additional_context: impl Fn() + Clone + Send + 'static,
@@ -675,7 +675,7 @@ async fn forward_stream(
     tx.close_channel();
 }
 
-/// Returns a Viz [Handler](viz::Handler) that listens for a `GET` request and tries
+/// Returns a Viz [Handler] that listens for a `GET` request and tries
 /// to route it using [leptos_router], serving an in-order HTML stream of your application.
 /// This stream will pause at each `<Suspense/>` node and wait for it to resolve before
 /// sending down its HTML. The app will become interactive once it has fully loaded.
@@ -702,8 +702,8 @@ async fn forward_stream(
 /// This function always provides context values including the following types:
 /// - [RequestParts]
 /// - [ResponseOptions]
-/// - [MetaContext](leptos_meta::MetaContext)
-/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
+/// - [MetaContext]
+/// - [RouterIntegrationContext]
 pub fn render_app_to_stream_in_order_with_context<IV>(
     options: LeptosOptions,
     additional_context: impl Fn() + 'static + Clone + Send,
@@ -794,9 +794,9 @@ fn provide_contexts(
     leptos::nonce::provide_nonce();
 }
 
-/// Returns a Viz [Handler](viz::Handler) that listens for a `GET` request and tries
+/// Returns a Viz [Handler] that listens for a `GET` request and tries
 /// to route it using [leptos_router], asynchronously rendering an HTML page after all
-/// `async` [Resource](leptos::Resource)s have loaded.
+/// `async` [Resource]s have loaded.
 ///
 /// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s context before
 /// rendering it, and includes any meta tags injected using [leptos_meta].
@@ -843,8 +843,8 @@ fn provide_contexts(
 /// This function always provides context values including the following types:
 /// - [RequestParts]
 /// - [ResponseOptions]
-/// - [MetaContext](leptos_meta::MetaContext)
-/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
+/// - [MetaContext]
+/// - [RouterIntegrationContext]
 pub fn render_app_async<IV>(
     options: LeptosOptions,
     app_fn: impl Fn() -> IV + Clone + Send + 'static,
@@ -860,9 +860,9 @@ where
     render_app_async_with_context(options, || {}, app_fn)
 }
 
-/// Returns a Viz [Handler](viz::Handler) that listens for a `GET` request and tries
+/// Returns a Viz [Handler] that listens for a `GET` request and tries
 /// to route it using [leptos_router], asynchronously rendering an HTML page after all
-/// `async` [Resource](leptos::Resource)s have loaded.
+/// `async` [Resource]s have loaded.
 ///
 /// This version allows us to pass Viz State/Extractor or other infro from Viz or network
 /// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
@@ -886,8 +886,8 @@ where
 /// This function always provides context values including the following types:
 /// - [RequestParts]
 /// - [ResponseOptions]
-/// - [MetaContext](leptos_meta::MetaContext)
-/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
+/// - [MetaContext]
+/// - [RouterIntegrationContext]
 pub fn render_app_async_with_context<IV>(
     options: LeptosOptions,
     additional_context: impl Fn() + 'static + Clone + Send,
diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs
index cdeb525d..ce4f5436 100644
--- a/leptos/src/lib.rs
+++ b/leptos/src/lib.rs
@@ -34,7 +34,7 @@
 //! - [`parent_child`](https://github.com/leptos-rs/leptos/tree/main/examples/parent_child) shows four different
 //!   ways a parent component can communicate with a child, including passing a closure, context, and more
 //! - [`fetch`](https://github.com/leptos-rs/leptos/tree/main/examples/fetch) introduces
-//!   [Resource](leptos_reactive::Resource)s, which allow you to integrate arbitrary `async` code like an
+//!   [Resource]s, which allow you to integrate arbitrary `async` code like an
 //!   HTTP request within your reactive code.
 //! - [`router`](https://github.com/leptos-rs/leptos/tree/main/examples/router) shows how to use Leptos’s nested router
 //!   to enable client-side navigation and route-specific, reactive data loading.
@@ -114,7 +114,7 @@
 //!     let increment = move |_| set_value.update(|value| *value += 1);
 //!
 //!     view! {
-//!         
+//!
 //!         <div>
 //!             <button on:click=clear>"Clear"</button>
 //!             <button on:click=decrement>"-1"</button>
diff --git a/leptos_dom/src/hydration.rs b/leptos_dom/src/hydration.rs
index 5aca7a42..62fcf272 100644
--- a/leptos_dom/src/hydration.rs
+++ b/leptos_dom/src/hydration.rs
@@ -73,6 +73,8 @@ pub(crate) use hydrate_only::*;
 /// A stable identifier within the server-rendering or hydration process.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 pub struct HydrationKey {
+    /// ID of the current outlet
+    pub outlet: usize,
     /// ID of the current fragment.
     pub fragment: usize,
     /// ID of the current error boundary.
@@ -83,7 +85,11 @@ pub struct HydrationKey {
 
 impl Display for HydrationKey {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}-{}-{}", self.fragment, self.error, self.id)
+        write!(
+            f,
+            "{}-{}-{}-{}",
+            self.outlet, self.fragment, self.error, self.id
+        )
     }
 }
 
@@ -91,14 +97,17 @@ impl std::str::FromStr for HydrationKey {
     type Err = (); // TODO better error
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let mut pieces = s.splitn(3, '-');
+        let mut pieces = s.splitn(4, '-');
         let first = pieces.next().ok_or(())?;
         let second = pieces.next().ok_or(())?;
         let third = pieces.next().ok_or(())?;
-        let fragment = usize::from_str(first).map_err(|_| ())?;
-        let error = usize::from_str(second).map_err(|_| ())?;
-        let id = usize::from_str(third).map_err(|_| ())?;
+        let fourth = pieces.next().ok_or(())?;
+        let outlet = usize::from_str(first).map_err(|_| ())?;
+        let fragment = usize::from_str(second).map_err(|_| ())?;
+        let error = usize::from_str(third).map_err(|_| ())?;
+        let id = usize::from_str(fourth).map_err(|_| ())?;
         Ok(HydrationKey {
+            outlet,
             fragment,
             error,
             id,
@@ -113,8 +122,9 @@ mod tests {
         use crate::HydrationKey;
         use std::str::FromStr;
         assert_eq!(
-            HydrationKey::from_str("1-2-3"),
+            HydrationKey::from_str("0-1-2-3"),
             Ok(HydrationKey {
+                outlet: 0,
                 fragment: 1,
                 error: 2,
                 id: 3
@@ -123,7 +133,7 @@ mod tests {
     }
 }
 
-thread_local!(static ID: RefCell<HydrationKey> = RefCell::new(HydrationKey { fragment: 0, error: 0, id: 0 }));
+thread_local!(static ID: RefCell<HydrationKey> = RefCell::new(HydrationKey { outlet: 0, fragment: 0, error: 0, id: 0 }));
 
 /// Control and utility methods for hydration.
 pub struct HydrationCtx;
@@ -187,6 +197,16 @@ impl HydrationCtx {
         })
     }
 
+    /// Resets the hydration `id` for the next outlet, and returns it
+    pub fn next_outlet() -> HydrationKey {
+        ID.with(|id| {
+            let mut id = id.borrow_mut();
+            id.outlet = id.outlet.wrapping_add(1);
+            id.id = 0;
+            *id
+        })
+    }
+
     /// Resets the hydration `id` for the next component, and returns it
     pub fn next_error() -> HydrationKey {
         ID.with(|id| {
@@ -202,6 +222,7 @@ impl HydrationCtx {
     pub fn reset_id() {
         ID.with(|id| {
             *id.borrow_mut() = HydrationKey {
+                outlet: 0,
                 fragment: 0,
                 error: 0,
                 id: 0,
@@ -220,6 +241,7 @@ impl HydrationCtx {
     pub fn continue_after(id: HydrationKey) {
         ID.with(|i| {
             *i.borrow_mut() = HydrationKey {
+                outlet: id.outlet,
                 fragment: id.fragment,
                 error: id.error,
                 id: id.id + 1,
diff --git a/leptos_dom/src/lib.rs b/leptos_dom/src/lib.rs
index 5ee56ba3..10531260 100644
--- a/leptos_dom/src/lib.rs
+++ b/leptos_dom/src/lib.rs
@@ -1173,6 +1173,19 @@ where
     }
 }
 
+impl IntoView for std::fmt::Arguments<'_> {
+    #[cfg_attr(
+        any(debug_assertions, feature = "ssr"),
+        instrument(level = "info", name = "#text", skip_all)
+    )]
+    fn into_view(self) -> View {
+        match self.as_str() {
+            Some(s) => s.into_view(),
+            None => self.to_string().into_view(),
+        }
+    }
+}
+
 macro_rules! viewable_primitive {
   ($($child_type:ty),* $(,)?) => {
     $(
@@ -1226,7 +1239,6 @@ viewable_primitive![
     std::num::NonZeroIsize,
     std::num::NonZeroUsize,
     std::panic::Location<'_>,
-    std::fmt::Arguments<'_>,
 ];
 
 cfg_if! {
diff --git a/leptos_dom/src/macro_helpers/into_attribute.rs b/leptos_dom/src/macro_helpers/into_attribute.rs
index 28307b27..34ca99da 100644
--- a/leptos_dom/src/macro_helpers/into_attribute.rs
+++ b/leptos_dom/src/macro_helpers/into_attribute.rs
@@ -261,6 +261,17 @@ impl IntoAttribute for Option<Box<dyn IntoAttribute>> {
     impl_into_attr_boxed! {}
 }
 
+impl IntoAttribute for std::fmt::Arguments<'_> {
+    fn into_attribute(self) -> Attribute {
+        match self.as_str() {
+            Some(s) => s.into_attribute(),
+            None => self.to_string().into_attribute(),
+        }
+    }
+
+    impl_into_attr_boxed! {}
+}
+
 /* impl IntoAttribute for Box<dyn IntoAttribute> {
     #[inline(always)]
     fn into_attribute(self) -> Attribute {
diff --git a/leptos_dom/src/nonce.rs b/leptos_dom/src/nonce.rs
index 2879beb0..e1ef4112 100644
--- a/leptos_dom/src/nonce.rs
+++ b/leptos_dom/src/nonce.rs
@@ -11,7 +11,7 @@ use std::{fmt::Display, ops::Deref};
 /// scripts used for HTML streaming and resource loading.
 ///
 /// The nonce being used during the current server response can be
-/// accessed using [`use_nonce`](use_nonce).
+/// accessed using [`use_nonce`].
 ///
 /// ```rust,ignore
 /// #[component]
diff --git a/leptos_macro/src/lib.rs b/leptos_macro/src/lib.rs
index d3eac61a..0bf4f784 100644
--- a/leptos_macro/src/lib.rs
+++ b/leptos_macro/src/lib.rs
@@ -34,6 +34,7 @@ mod view;
 use view::{client_template::render_template, render_view};
 mod component;
 mod server;
+mod slice;
 mod slot;
 
 /// The `view` macro uses RSX (like JSX, but Rust!) It follows most of the
@@ -958,7 +959,7 @@ pub fn params_derive(
     input: proc_macro::TokenStream,
 ) -> proc_macro::TokenStream {
     match syn::parse(input) {
-        Ok(ast) => params::impl_params(&ast),
+        Ok(ast) => params::params_impl(&ast),
         Err(err) => err.to_compile_error().into(),
     }
 }
@@ -969,3 +970,36 @@ pub(crate) fn attribute_value(attr: &KeyedAttribute) -> &syn::Expr {
         None => abort!(attr.key, "attribute should have value"),
     }
 }
+
+/// Generates a `lens` into struct with a default getter and setter
+///
+/// Can be used to access deeply nested fields within a global state object
+///
+/// ```rust
+/// # use leptos::{create_runtime, create_rw_signal};
+/// # use leptos_macro::slice;
+/// # let runtime = create_runtime();
+///
+/// #[derive(Default)]
+/// pub struct Outer {
+///     count: i32,
+///     inner: Inner,
+/// }
+///
+/// #[derive(Default)]
+/// pub struct Inner {
+///     inner_count: i32,
+///     inner_name: String,
+/// }
+///
+/// let outer_signal = create_rw_signal(Outer::default());
+///
+/// let (count, set_count) = slice!(outer_signal.count);
+///
+/// let (inner_count, set_inner_count) = slice!(outer_signal.inner.inner_count);
+/// let (inner_name, set_inner_name) = slice!(outer_signal.inner.inner_name);
+/// ```
+#[proc_macro]
+pub fn slice(input: TokenStream) -> TokenStream {
+    slice::slice_impl(input)
+}
diff --git a/leptos_macro/src/params.rs b/leptos_macro/src/params.rs
index 79a24cbb..c94fc88a 100644
--- a/leptos_macro/src/params.rs
+++ b/leptos_macro/src/params.rs
@@ -1,7 +1,7 @@
 use quote::{quote, quote_spanned};
 use syn::spanned::Spanned;
 
-pub fn impl_params(ast: &syn::DeriveInput) -> proc_macro::TokenStream {
+pub fn params_impl(ast: &syn::DeriveInput) -> proc_macro::TokenStream {
     let name = &ast.ident;
 
     let fields = if let syn::Data::Struct(syn::DataStruct {
diff --git a/leptos_macro/src/slice.rs b/leptos_macro/src/slice.rs
new file mode 100644
index 00000000..b609f31c
--- /dev/null
+++ b/leptos_macro/src/slice.rs
@@ -0,0 +1,59 @@
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+use proc_macro2::Ident;
+use quote::quote;
+use syn::{
+    parse::{Parse, ParseStream},
+    parse_macro_input,
+    punctuated::Punctuated,
+    token::Dot,
+    Token, Type,
+};
+
+struct SliceMacroInput {
+    pub root: Ident,
+    pub path: Punctuated<Type, Dot>,
+}
+
+impl Parse for SliceMacroInput {
+    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
+        let root = syn::Ident::parse(input)?;
+        let _dot = <Token![.]>::parse(input)?;
+        let path = input.parse_terminated(Type::parse, Token![.])?;
+
+        if path.is_empty() {
+            return Err(syn::Error::new(input.span(), "Expected identifier"));
+        }
+
+        if path.trailing_punct() {
+            return Err(syn::Error::new(
+                input.span(),
+                "Unexpected trailing `.`",
+            ));
+        }
+
+        Ok(SliceMacroInput { root, path })
+    }
+}
+
+impl From<SliceMacroInput> for TokenStream {
+    fn from(val: SliceMacroInput) -> Self {
+        let root = val.root;
+        let path = val.path;
+
+        quote! {
+            ::leptos::create_slice(
+                #root,
+                |st: &_| st.#path.clone(),
+                |st: &mut _, n| st.#path = n
+            )
+        }
+        .into()
+    }
+}
+
+pub fn slice_impl(tokens: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(tokens as SliceMacroInput);
+    input.into()
+}
diff --git a/leptos_reactive/Cargo.toml b/leptos_reactive/Cargo.toml
index 108ccf4b..5abbb48c 100644
--- a/leptos_reactive/Cargo.toml
+++ b/leptos_reactive/Cargo.toml
@@ -28,7 +28,9 @@ serde-wasm-bindgen = "0.5"
 serde_json = "1"
 base64 = "0.21"
 thiserror = "1"
-tokio = { version = "1", features = ["rt"], optional = true, default-features = false }
+tokio = { version = "1", features = [
+  "rt",
+], optional = true, default-features = false }
 tracing = "0.1"
 wasm-bindgen = { version = "0.2", optional = true }
 wasm-bindgen-futures = { version = "0.4", optional = true }
@@ -43,6 +45,7 @@ cfg-if = "1"
 indexmap = "2"
 self_cell = "1.0.0"
 pin-project = "1"
+paste = "1"
 
 [dev-dependencies]
 criterion = { version = "0.5.1", features = ["html_reports"] }
diff --git a/leptos_reactive/src/callback.rs b/leptos_reactive/src/callback.rs
index aed499c3..fbe93e7f 100644
--- a/leptos_reactive/src/callback.rs
+++ b/leptos_reactive/src/callback.rs
@@ -107,31 +107,41 @@ impl<In: 'static, Out: 'static> Callable<In, Out> for Callback<In, Out> {
     }
 }
 
-#[cfg(not(feature = "nightly"))]
-impl<F, In, T, Out> From<F> for Callback<In, Out>
-where
-    F: Fn(In) -> T + 'static,
-    T: Into<Out> + 'static,
-{
-    fn from(f: F) -> Callback<In, Out> {
-        Callback::new(move |x| f(x).into())
-    }
+macro_rules! impl_from_fn {
+    ($ty:ident) => {
+        #[cfg(not(feature = "nightly"))]
+        impl<F, In, T, Out> From<F> for $ty<In, Out>
+        where
+            F: Fn(In) -> T + 'static,
+            T: Into<Out> + 'static,
+        {
+            fn from(f: F) -> Self {
+                Self::new(move |x| f(x).into())
+            }
+        }
+
+        paste::paste! {
+            #[cfg(feature = "nightly")]
+            auto trait [<NotRaw $ty>] {}
+
+            #[cfg(feature = "nightly")]
+            impl<A, B> ![<NotRaw $ty>] for $ty<A, B> {}
+
+            #[cfg(feature = "nightly")]
+            impl<F, In, T, Out> From<F> for $ty<In, Out>
+            where
+                F: Fn(In) -> T + [<NotRaw $ty>] + 'static,
+                T: Into<Out> + 'static,
+            {
+                fn from(f: F) -> Self {
+                    Self::new(move |x| f(x).into())
+                }
+            }
+        }
+    };
 }
 
-#[cfg(feature = "nightly")]
-auto trait NotRawCallback {}
-#[cfg(feature = "nightly")]
-impl<A, B> !NotRawCallback for Callback<A, B> {}
-#[cfg(feature = "nightly")]
-impl<F, In, T, Out> From<F> for Callback<In, Out>
-where
-    F: Fn(In) -> T + NotRawCallback + 'static,
-    T: Into<Out> + 'static,
-{
-    fn from(f: F) -> Callback<In, Out> {
-        Callback::new(move |x| f(x).into())
-    }
-}
+impl_from_fn!(Callback);
 
 #[cfg(feature = "nightly")]
 impl<In, Out> FnOnce<(In,)> for Callback<In, Out> {
@@ -190,31 +200,7 @@ impl<In: 'static, Out: 'static> SyncCallback<In, Out> {
     }
 }
 
-#[cfg(not(feature = "nightly"))]
-impl<F, In, T, Out> From<F> for SyncCallback<In, Out>
-where
-    F: Fn(In) -> T + 'static,
-    T: Into<Out> + 'static,
-{
-    fn from(f: F) -> SyncCallback<In, Out> {
-        SyncCallback::new(move |x| f(x).into())
-    }
-}
-
-#[cfg(feature = "nightly")]
-auto trait NotRawSyncCallback {}
-#[cfg(feature = "nightly")]
-impl<A, B> !NotRawSyncCallback for SyncCallback<A, B> {}
-#[cfg(feature = "nightly")]
-impl<F, In, T, Out> From<F> for SyncCallback<In, Out>
-where
-    F: Fn(In) -> T + NotRawSyncCallback + 'static,
-    T: Into<Out> + 'static,
-{
-    fn from(f: F) -> SyncCallback<In, Out> {
-        SyncCallback::new(move |x| f(x).into())
-    }
-}
+impl_from_fn!(SyncCallback);
 
 #[cfg(feature = "nightly")]
 impl<In, Out> FnOnce<(In,)> for SyncCallback<In, Out> {
diff --git a/leptos_reactive/src/lib.rs b/leptos_reactive/src/lib.rs
index 9c4c026b..09470e18 100644
--- a/leptos_reactive/src/lib.rs
+++ b/leptos_reactive/src/lib.rs
@@ -20,19 +20,19 @@
 //! Here are the most commonly-used functions and types you'll need to build a reactive system:
 //!
 //! ### Signals
-//! 1. *Signals:* [`create_signal`](crate::create_signal), which returns a ([`ReadSignal`](crate::ReadSignal),
-//!    [`WriteSignal`](crate::WriteSignal)) tuple, or [`create_rw_signal`](crate::create_rw_signal), which returns
-//!    a signal [`RwSignal`](crate::RwSignal) without this read-write segregation.
+//! 1. *Signals:* [`create_signal`], which returns a ([`ReadSignal`],
+//!    [`WriteSignal`] tuple, or [`create_rw_signal`], which returns
+//!    a signal [`RwSignal`] without this read-write segregation.
 //! 2. *Derived Signals:* any function that relies on another signal.
-//! 3. *Memos:* [`create_memo`], which returns a [`Memo`](crate::Memo).
+//! 3. *Memos:* [`create_memo`], which returns a [`Memo`].
 //! 4. *Resources:* [`create_resource`], which converts an `async` [`Future`](std::future::Future) into a
-//!    synchronous [`Resource`](crate::Resource) signal.
+//!    synchronous [`Resource`] signal.
 //! 5. *Triggers:* [`create_trigger`], creates a purely reactive [`Trigger`] primitive without any associated state.
 //!
 //! ### Effects
-//! 1. Use [`create_effect`](crate::create_effect) when you need to synchronize the reactive system
+//! 1. Use [`create_effect`] when you need to synchronize the reactive system
 //!    with something outside it (for example: logging to the console, writing to a file or local storage)
-//! 2. The Leptos DOM renderer wraps any [`Fn`] in your template with [`create_effect`](crate::create_effect), so
+//! 2. The Leptos DOM renderer wraps any [`Fn`] in your template with [`create_effect`], so
 //!    components you write do *not* need explicit effects to synchronize with the DOM.
 //!
 //! ### Example
diff --git a/leptos_reactive/src/resource.rs b/leptos_reactive/src/resource.rs
index 43c7f39d..d1519bb3 100644
--- a/leptos_reactive/src/resource.rs
+++ b/leptos_reactive/src/resource.rs
@@ -58,14 +58,31 @@ use std::{
 /// // when we read the signal, it contains either
 /// // 1) None (if the Future isn't ready yet) or
 /// // 2) Some(T) (if the future's already resolved)
-/// assert_eq!(cats.read(), Some(vec!["1".to_string()]));
+/// assert_eq!(cats.get(), Some(vec!["1".to_string()]));
 ///
 /// // when the signal's value changes, the `Resource` will generate and run a new `Future`
 /// set_how_many_cats.set(2);
-/// assert_eq!(cats.read(), Some(vec!["2".to_string()]));
+/// assert_eq!(cats.get(), Some(vec!["2".to_string()]));
 /// # }
 /// # runtime.dispose();
 /// ```
+///
+/// We can provide single, multiple or even a non-reactive signal as `source`
+///
+/// ```rust
+/// # use leptos::*;
+/// # let runtime = create_runtime();
+/// # if false {
+/// # let how_many_cats = RwSignal::new(0); let how_many_dogs = RwSignal::new(0);
+/// // Single signal. `Resource` will run once initially and then every time `how_many_cats` changes
+/// let async_data = create_resource(move || how_many_cats.get() , |_| async move { todo!() });
+/// // Non-reactive signal. `Resource` runs only once
+/// let async_data = create_resource(|| (), |_| async move { todo!() });
+/// // Multiple signals. `Resource` will run once initially and then every time `how_many_cats` or `how_many_dogs` changes
+/// let async_data = create_resource(move || (how_many_cats.get(), how_many_dogs.get()), |_| async move { todo!() });
+/// # runtime.dispose();
+/// # }
+/// ```
 #[cfg_attr(
     any(debug_assertions, feature="ssr"),
     instrument(
@@ -902,14 +919,31 @@ impl<S, T> SignalSet for Resource<S, T> {
 /// // when we read the signal, it contains either
 /// // 1) None (if the Future isn't ready yet) or
 /// // 2) Some(T) (if the future's already resolved)
-/// assert_eq!(cats.read(), Some(vec!["1".to_string()]));
+/// assert_eq!(cats.get(), Some(vec!["1".to_string()]));
 ///
 /// // when the signal's value changes, the `Resource` will generate and run a new `Future`
 /// set_how_many_cats.set(2);
-/// assert_eq!(cats.read(), Some(vec!["2".to_string()]));
+/// assert_eq!(cats.get(), Some(vec!["2".to_string()]));
 /// # }
 /// # runtime.dispose();
 /// ```
+///
+/// We can provide single, multiple or even a non-reactive signal as `source`
+///
+/// ```rust
+/// # use leptos::*;
+/// # let runtime = create_runtime();
+/// # if false {
+/// # let how_many_cats = RwSignal::new(0); let how_many_dogs = RwSignal::new(0);
+/// // Single signal. `Resource` will run once initially and then every time `how_many_cats` changes
+/// let async_data = create_resource(move || how_many_cats.get() , |_| async move { todo!() });
+/// // Non-reactive signal. `Resource` runs only once
+/// let async_data = create_resource(|| (), |_| async move { todo!() });
+/// // Multiple signals. `Resource` will run once initially and then every time `how_many_cats` or `how_many_dogs` changes
+/// let async_data = create_resource(move || (how_many_cats.get(), how_many_dogs.get()), |_| async move { todo!() });
+/// # runtime.dispose();
+/// # }
+/// ```
 #[derive(Debug, PartialEq, Eq, Hash)]
 pub struct Resource<S, T>
 where
diff --git a/leptos_reactive/src/runtime.rs b/leptos_reactive/src/runtime.rs
index 88bca3c2..013645c4 100644
--- a/leptos_reactive/src/runtime.rs
+++ b/leptos_reactive/src/runtime.rs
@@ -751,7 +751,7 @@ pub struct RuntimeId;
 /// ## Panics
 /// Panics if there is no current reactive runtime.
 pub fn as_child_of_current_owner<T, U>(
-    f: impl Fn(T) -> U + 'static,
+    f: impl Fn(T) -> U,
 ) -> impl Fn(T) -> (U, Disposer)
 where
     T: 'static,
diff --git a/leptos_reactive/src/trigger.rs b/leptos_reactive/src/trigger.rs
index cb7fe48b..90035213 100644
--- a/leptos_reactive/src/trigger.rs
+++ b/leptos_reactive/src/trigger.rs
@@ -18,6 +18,47 @@ pub struct Trigger {
 }
 
 impl Trigger {
+    /// Creates a [`Trigger`](crate::Trigger), a kind of reactive primitive.
+    ///
+    /// A trigger is a data-less signal with the sole purpose
+    /// of notifying other reactive code of a change. This can be useful
+    /// for when using external data not stored in signals, for example.
+    ///
+    /// This is identical to [`create_trigger`].
+    ///
+    /// ```
+    /// # use leptos_reactive::*;
+    /// # let runtime = create_runtime();
+    /// use std::{cell::RefCell, fmt::Write, rc::Rc};
+    ///
+    /// let external_data = Rc::new(RefCell::new(1));
+    /// let output = Rc::new(RefCell::new(String::new()));
+    ///
+    /// let rerun_on_data = Trigger::new();
+    ///
+    /// let o = output.clone();
+    /// let e = external_data.clone();
+    /// create_effect(move |_| {
+    ///     // can be `rerun_on_data()` on nightly
+    ///     rerun_on_data.track();
+    ///     write!(o.borrow_mut(), "{}", *e.borrow());
+    ///     *e.borrow_mut() += 1;
+    /// });
+    /// # if !cfg!(feature = "ssr") {
+    /// assert_eq!(*output.borrow(), "1");
+    ///
+    /// rerun_on_data.notify(); // reruns the above effect
+    ///
+    /// assert_eq!(*output.borrow(), "12");
+    /// # }
+    /// # runtime.dispose();
+    /// ```
+    #[inline(always)]
+    #[track_caller]
+    pub fn new() -> Self {
+        create_trigger()
+    }
+
     /// Notifies any reactive code where this trigger is tracked to rerun.
     ///
     /// ## Panics
@@ -97,6 +138,12 @@ pub fn create_trigger() -> Trigger {
     Runtime::current().create_trigger()
 }
 
+impl Default for Trigger {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
 impl SignalGet for Trigger {
     type Value = ();
 
diff --git a/meta/Cargo.toml b/meta/Cargo.toml
index 427054f9..bc60edb7 100644
--- a/meta/Cargo.toml
+++ b/meta/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "leptos_meta"
-version = "0.5.1"
+version = "0.5.2"
 edition = "2021"
 authors = ["Greg Johnston"]
 license = "MIT"
diff --git a/router/Cargo.toml b/router/Cargo.toml
index c6ad810e..8c90791a 100644
--- a/router/Cargo.toml
+++ b/router/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "leptos_router"
-version = "0.5.1"
+version = "0.5.2"
 edition = "2021"
 authors = ["Greg Johnston"]
 license = "MIT"
diff --git a/router/src/components/form.rs b/router/src/components/form.rs
index 68768256..48ce8aff 100644
--- a/router/src/components/form.rs
+++ b/router/src/components/form.rs
@@ -59,6 +59,9 @@ pub fn Form<A>(
     /// Sets whether the page should be scrolled to the top when the form is submitted.
     #[prop(optional)]
     noscroll: bool,
+    /// Sets whether the page should replace the current location in the history when the form is submitted.
+    #[prop(optional)]
+    replace: bool,
     /// Arbitrary attributes to add to the `<form>`. Attributes can be added with the
     /// `attr:` syntax in the `view` macro.
     #[prop(attrs)]
@@ -83,6 +86,7 @@ where
         children: Children,
         node_ref: Option<NodeRef<html::Form>>,
         noscroll: bool,
+        replace: bool,
         attributes: Vec<(&'static str, Attribute)>,
     ) -> HtmlElement<html::Form> {
         let action_version = version;
@@ -94,6 +98,7 @@ where
                 let navigate = has_router.then(use_navigate);
                 let navigate_options = NavigateOptions {
                     scroll: !noscroll,
+                    replace,
                     ..Default::default()
                 };
 
@@ -333,6 +338,7 @@ where
         children,
         node_ref,
         noscroll,
+        replace,
         attributes,
     )
 }
diff --git a/router/src/components/outlet.rs b/router/src/components/outlet.rs
index 693aac95..82c3e699 100644
--- a/router/src/components/outlet.rs
+++ b/router/src/components/outlet.rs
@@ -15,7 +15,7 @@ use web_sys::AnimationEvent;
 )]
 #[component]
 pub fn Outlet() -> impl IntoView {
-    _ = HydrationCtx::next_component();
+    _ = HydrationCtx::next_outlet();
     let id = HydrationCtx::id();
     let route = use_route();
     let route_states = expect_context::<Memo<crate::RouterState>>();


Test patch:
diff --git a/leptos/tests/ssr.rs b/leptos/tests/ssr.rs
index 0b7324dd..32a116a9 100644
--- a/leptos/tests/ssr.rs
+++ b/leptos/tests/ssr.rs
@@ -21,11 +21,11 @@ fn simple_ssr_test() {
         );
     } else {
         assert!(rendered.into_view().render_to_string().contains(
-            "<div data-hk=\"0-0-1\"><button \
-             data-hk=\"0-0-2\">-1</button><span data-hk=\"0-0-3\">Value: \
-             <!--hk=0-0-4o|leptos-dyn-child-start-->0<!\
-             --hk=0-0-4c|leptos-dyn-child-end-->!</span><button \
-             data-hk=\"0-0-5\">+1</button></div>"
+            "<div data-hk=\"0-0-0-1\"><button \
+             data-hk=\"0-0-0-2\">-1</button><span data-hk=\"0-0-0-3\">Value: \
+             <!--hk=0-0-0-4o|leptos-dyn-child-start-->0<!\
+             --hk=0-0-0-4c|leptos-dyn-child-end-->!</span><button \
+             data-hk=\"0-0-0-5\">+1</button></div>"
         ));
     }
 
@@ -68,11 +68,11 @@ fn ssr_test_with_components() {
         );
     } else {
         assert!(rendered.into_view().render_to_string().contains(
-            "<div data-hk=\"0-0-3\"><button \
-             data-hk=\"0-0-4\">-1</button><span data-hk=\"0-0-5\">Value: \
-             <!--hk=0-0-6o|leptos-dyn-child-start-->1<!\
-             --hk=0-0-6c|leptos-dyn-child-end-->!</span><button \
-             data-hk=\"0-0-7\">+1</button></div>"
+            "<div data-hk=\"0-0-0-3\"><button \
+             data-hk=\"0-0-0-4\">-1</button><span data-hk=\"0-0-0-5\">Value: \
+             <!--hk=0-0-0-6o|leptos-dyn-child-start-->1<!\
+             --hk=0-0-0-6c|leptos-dyn-child-end-->!</span><button \
+             data-hk=\"0-0-0-7\">+1</button></div>"
         ));
     }
     runtime.dispose();
@@ -114,11 +114,11 @@ fn ssr_test_with_snake_case_components() {
         );
     } else {
         assert!(rendered.into_view().render_to_string().contains(
-            "<div data-hk=\"0-0-3\"><button \
-             data-hk=\"0-0-4\">-1</button><span data-hk=\"0-0-5\">Value: \
-             <!--hk=0-0-6o|leptos-dyn-child-start-->1<!\
-             --hk=0-0-6c|leptos-dyn-child-end-->!</span><button \
-             data-hk=\"0-0-7\">+1</button></div>"
+            "<div data-hk=\"0-0-0-3\"><button \
+             data-hk=\"0-0-0-4\">-1</button><span data-hk=\"0-0-0-5\">Value: \
+             <!--hk=0-0-0-6o|leptos-dyn-child-start-->1<!\
+             --hk=0-0-0-6c|leptos-dyn-child-end-->!</span><button \
+             data-hk=\"0-0-0-7\">+1</button></div>"
         ));
     }
 
@@ -144,7 +144,7 @@ fn test_classes() {
         );
     } else {
         assert!(rendered.into_view().render_to_string().contains(
-            "<div data-hk=\"0-0-1\" class=\"my big  red car\"></div>"
+            "<div data-hk=\"0-0-0-1\" class=\"my big  red car\"></div>"
         ));
     }
     runtime.dispose();
@@ -173,8 +173,8 @@ fn ssr_with_styles() {
         );
     } else {
         assert!(rendered.into_view().render_to_string().contains(
-            "<div data-hk=\"0-0-1\" class=\" myclass\"><button \
-             data-hk=\"0-0-2\" class=\"btn myclass\">-1</button></div>"
+            "<div data-hk=\"0-0-0-1\" class=\" myclass\"><button \
+             data-hk=\"0-0-0-2\" class=\"btn myclass\">-1</button></div>"
         ));
     }
     runtime.dispose();
@@ -201,7 +201,7 @@ fn ssr_option() {
         assert!(rendered
             .into_view()
             .render_to_string()
-            .contains("<option data-hk=\"0-0-1\"></option>"));
+            .contains("<option data-hk=\"0-0-0-1\"></option>"));
     }
 
     runtime.dispose();
diff --git a/leptos_macro/tests/slice.rs b/leptos_macro/tests/slice.rs
new file mode 100644
index 00000000..4c413c1e
--- /dev/null
+++ b/leptos_macro/tests/slice.rs
@@ -0,0 +1,32 @@
+use leptos::{create_runtime, create_rw_signal};
+use leptos_macro::slice;
+
+#[derive(Default)]
+pub struct OuterState {
+    count: i32,
+    inner: InnerState,
+}
+
+#[derive(Clone, PartialEq, Default)]
+pub struct InnerState {
+    inner_count: i32,
+    inner_name: String,
+}
+
+#[test]
+fn green() {
+    let _ = create_runtime();
+
+    let outer_signal = create_rw_signal(OuterState::default());
+
+    let (_, _) = slice!(outer_signal.count);
+
+    let (_, _) = slice!(outer_signal.inner.inner_count);
+    let (_, _) = slice!(outer_signal.inner.inner_name);
+}
+
+#[test]
+fn red() {
+    let t = trybuild::TestCases::new();
+    t.compile_fail("tests/slice/red.rs")
+}
diff --git a/leptos_macro/tests/slice/red.rs b/leptos_macro/tests/slice/red.rs
new file mode 100644
index 00000000..7ced3ab7
--- /dev/null
+++ b/leptos_macro/tests/slice/red.rs
@@ -0,0 +1,28 @@
+use leptos::{create_runtime, create_rw_signal};
+use leptos_macro::slice;
+
+#[derive(Default, PartialEq)]
+pub struct OuterState {
+    count: i32,
+    inner: InnerState,
+}
+
+#[derive(Clone, PartialEq, Default)]
+pub struct InnerState {
+    inner_count: i32,
+    inner_name: String,
+}
+
+fn main() {
+    let _ = create_runtime();
+
+    let outer_signal = create_rw_signal(OuterState::default());
+
+    let (_, _) = slice!();
+
+    let (_, _) = slice!(outer_signal);
+
+    let (_, _) = slice!(outer_signal.);
+
+    let (_, _) = slice!(outer_signal.inner.);
+}
diff --git a/leptos_macro/tests/slice/red.stderr b/leptos_macro/tests/slice/red.stderr
new file mode 100644
index 00000000..db4666df
--- /dev/null
+++ b/leptos_macro/tests/slice/red.stderr
@@ -0,0 +1,31 @@
+error: unexpected end of input, expected identifier
+  --> tests/slice/red.rs:21:18
+   |
+21 |     let (_, _) = slice!();
+   |                  ^^^^^^^^
+   |
+   = note: this error originates in the macro `slice` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: expected `.`
+  --> tests/slice/red.rs:23:18
+   |
+23 |     let (_, _) = slice!(outer_signal);
+   |                  ^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: this error originates in the macro `slice` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: Expected identifier
+  --> tests/slice/red.rs:25:18
+   |
+25 |     let (_, _) = slice!(outer_signal.);
+   |                  ^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: this error originates in the macro `slice` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: Unexpected trailing `.`
+  --> tests/slice/red.rs:27:18
+   |
+27 |     let (_, _) = slice!(outer_signal.inner.);
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: this error originates in the macro `slice` (in Nightly builds, run with -Z macro-backtrace for more info)