From 96b169a2ff7c0b23f2f6623767c083f846555a6d Mon Sep 17 00:00:00 2001
From: Greg Johnston
Date: Sat, 30 Nov 2024 12:25:14 -0500
Subject: [PATCH] porting book to 0.7 (#133)
---
src/01_introduction.md | 2 +-
src/15_global_state.md | 320 ++-------------------
src/SUMMARY.md | 2 +-
src/appendix_life_cycle.md | 38 ++-
src/appendix_reactive_graph.md | 37 +--
src/async/10_resources.md | 125 ++++----
src/async/11_suspense.md | 90 ++++--
src/async/12_transition.md | 43 +--
src/async/13_actions.md | 47 +--
src/async/README.md | 6 +-
src/deployment/ssr.md | 2 +-
src/getting_started/README.md | 40 +--
src/getting_started/leptos_dx.md | 4 +-
src/interlude_projecting_children.md | 93 +++---
src/interlude_styling.md | 32 +--
src/islands.md | 82 +++---
src/metadata.md | 27 +-
src/progressive_enhancement/README.md | 12 +-
src/progressive_enhancement/action_form.md | 54 ++--
src/reactivity/14_create_effect.md | 166 ++++-------
src/reactivity/interlude_functions.md | 24 +-
src/reactivity/working_with_signals.md | 129 ++++-----
src/router/16_routes.md | 127 +++-----
src/router/17_nested_routing.md | 152 +++++-----
src/router/18_params_and_queries.md | 38 ++-
src/router/19_a.md | 8 +-
src/router/20_form.md | 57 ++--
src/server/25_server_functions.md | 79 +----
src/server/26_extractors.md | 16 +-
src/server/27_response.md | 51 ++--
src/ssr/21_cargo_leptos.md | 3 -
src/ssr/22_life_cycle.md | 2 +-
src/ssr/23_ssr_modes.md | 60 ++--
src/ssr/24_hydration_bugs.md | 19 +-
src/ssr/README.md | 11 +-
src/testing.md | 93 +++---
src/view/01_basic_component.md | 140 +++++----
src/view/02_dynamic_attributes.md | 83 +++---
src/view/03_components.md | 118 +++++---
src/view/04_iteration.md | 57 ++--
src/view/04b_iteration.md | 69 ++---
src/view/05_forms.md | 64 ++---
src/view/06_control_flow.md | 91 +++---
src/view/07_errors.md | 59 ++--
src/view/08_parent_child.md | 116 ++------
src/view/09_component_children.md | 47 +--
src/view/builder.md | 54 ++--
src/web_sys.md | 188 ++++--------
48 files changed, 1328 insertions(+), 1849 deletions(-)
diff --git a/src/01_introduction.md b/src/01_introduction.md
index 4030712..ed5e340 100644
--- a/src/01_introduction.md
+++ b/src/01_introduction.md
@@ -15,6 +15,6 @@ to other frameworks like React (JavaScript), Svelte (JavaScript), Yew (Rust), an
Dioxus (Rust), so knowledge of one of those frameworks may also make it easier to
understand Leptos.
-You can find more detailed docs for each part of the API at [Docs.rs](https://docs.rs/leptos/latest/leptos/).
+You can find more detailed docs for each part of the API at [Docs.rs](https://docs.rs/leptos/0.7.0-gamma3/leptos/).
> The source code for the book is available [here](https://github.com/leptos-rs/book). PRs for typos or clarification are always welcome.
diff --git a/src/15_global_state.md b/src/15_global_state.md
index 0017e19..1f39e67 100644
--- a/src/15_global_state.md
+++ b/src/15_global_state.md
@@ -8,7 +8,7 @@ The three best approaches to global state are
1. Using the router to drive global state via the URL
2. Passing signals through context
-3. Creating a global state struct and creating lenses into it with `create_slice`
+3. Creating a global state struct using stores
## Option #1: URL as Global State
@@ -32,7 +32,7 @@ all its children and descendants using `provide_context`.
fn App() -> impl IntoView {
// here we create a signal in the root that can be consumed
// anywhere in the app.
- let (count, set_count) = create_signal(0);
+ let (count, set_count) = signal(0);
// we'll pass the setter to specific components,
// but provide the count itself to the whole app via context
provide_context(count);
@@ -62,7 +62,7 @@ fn FancyMath() -> impl IntoView {
let count = use_context::>()
// we know we just provided this in the parent component
.expect("there to be a `count` signal provided");
- let is_even = move || count() & 1 == 0;
+ let is_even = move || count.get() & 1 == 0;
view! {
@@ -79,334 +79,58 @@ fn FancyMath() -> impl IntoView {
}
```
-Note that this same pattern can be applied to more complex state. If you have multiple fields you want to update independently, you can do that by providing some struct of signals:
+## Option #3: Create a Global State Store
-```rust
-#[derive(Copy, Clone, Debug)]
-struct GlobalState {
- count: RwSignal,
- name: RwSignal
-}
-
-impl GlobalState {
- pub fn new() -> Self {
- Self {
- count: create_rw_signal(0),
- name: create_rw_signal("Bob".to_string())
- }
- }
-}
-
-#[component]
-fn App() -> impl IntoView {
- provide_context(GlobalState::new());
+Stores are a new reactive primitive, available in Leptos 0.7 through the accompanying `reactive_stores` crate. (This crate is shipped separately for now so we can continue to develop it without requiring a version change to the whole framework.)
- // etc.
-}
-```
+Stores allow you to wrap an entire struct, and reactively read from and update individual fields without tracking changes to other fields.
-## Option #3: Create a Global State Struct and Slices
-
-You may find it cumbersome to wrap each field of a structure in a separate signal like this. In some cases, it can be useful to create a plain struct with non-reactive fields, and then wrap that in a signal.
+They are used by adding `#[derive(Store)]` onto a struct. (You can `use reactive_stores::Store;` to import the macro.) This creates an extension trait with a getter for each field of the struct, when the struct is wrapped in a `Store<_>`.
```rust
-#[derive(Copy, Clone, Debug, Default)]
+#[derive(Clone, Debug, Default, Store)]
struct GlobalState {
count: i32,
- name: String
+ name: String,
}
+```
+
+This creates a trait named `GlobalStateStoreFields` which adds with methods `count` and `name` to a `Store`. Each method returns a reactive store *field*.
+```rust
#[component]
fn App() -> impl IntoView {
- provide_context(create_rw_signal(GlobalState::default()));
+ provide_context(Store::new(GlobalState::default()));
// etc.
}
-```
-But there’s a problem: because our whole state is wrapped in one signal, updating the value of one field will cause reactive updates in parts of the UI that only depend on the other.
-
-```rust
-let state = expect_context::>();
-view! {
-
-
{move || state.with(|state| state.name.clone())}
-}
-```
-
-In this example, clicking the button will cause the text inside `
` to be updated, cloning `state.name` again! Because signals are the atomic unit of reactivity, updating any field of the signal triggers updates to everything that depends on the signal.
-
-There’s a better way. You can take fine-grained, reactive slices by using [`create_memo`](https://docs.rs/leptos/latest/leptos/fn.create_memo.html) or [`create_slice`](https://docs.rs/leptos/latest/leptos/fn.create_slice.html) (which uses `create_memo` but also provides a setter). “Memoizing” a value means creating a new reactive value which will only update when it changes. “Memoizing a slice” means creating a new reactive value which will only update when some field of the state struct updates.
-
-Here, instead of reading from the state signal directly, we create “slices” of that state with fine-grained updates via `create_slice`. Each slice signal only updates when the particular piece of the larger struct it accesses updates. This means you can create a single root signal, and then take independent, fine-grained slices of it in different components, each of which can update without notifying the others of changes.
-
-```rust
/// A component that updates the count in the global state.
#[component]
fn GlobalStateCounter() -> impl IntoView {
- let state = expect_context::>();
+ let state = expect_context::>();
- // `create_slice` lets us create a "lens" into the data
- let (count, set_count) = create_slice(
-
- // we take a slice *from* `state`
- state,
- // our getter returns a "slice" of the data
- |state| state.count,
- // our setter describes how to mutate that slice, given a new value
- |state, n| state.count = n,
- );
+ // this gives us reactive access to the `count` field only
+ let count = state.count();
view! {
- "Count is: " {count}
+ "Count is: " {move || count.get()}
}
}
```
-Clicking this button only updates `state.count`, so if we create another slice
-somewhere else that only takes `state.name`, clicking the button won’t cause
-that other slice to update. This allows you to combine the benefits of a top-down
+Clicking this button only updates `state.count`. If we read from `state.name` somewhere else,
+click the button won’t notify it. This allows you to combine the benefits of a top-down
data flow and of fine-grained reactive updates.
-> **Note**: There are some significant drawbacks to this approach. Both signals and memos need to own their values, so a memo will need to clone the field’s value on every change. The most natural way to manage state in a framework like Leptos is always to provide signals that are as locally-scoped and fine-grained as they can be, not to hoist everything up into global state. But when you _do_ need some kind of global state, `create_slice` can be a useful tool.
-
-```admonish sandbox title="Live example" collapsible=true
-
-[Click to open CodeSandbox.](https://codesandbox.io/p/sandbox/15-global-state-0-5-8c2ff6?file=%2Fsrc%2Fmain.rs%3A1%2C2)
-
-
-
-
-
-
-
-```
-
-
-CodeSandbox Source
-
-```rust
-use leptos::*;
-
-// So far, we've only been working with local state in components
-// We've only seen how to communicate between parent and child components
-// But there are also more general ways to manage global state
-//
-// The three best approaches to global state are
-// 1. Using the router to drive global state via the URL
-// 2. Passing signals through context
-// 3. Creating a global state struct and creating lenses into it with `create_slice`
-//
-// Option #1: URL as Global State
-// The next few sections of the tutorial will be about the router.
-// So for now, we'll just look at options #2 and #3.
-
-// Option #2: Pass Signals through Context
-//
-// In virtual DOM libraries like React, using the Context API to manage global
-// state is a bad idea: because the entire app exists in a tree, changing
-// some value provided high up in the tree can cause the whole app to render.
-//
-// In fine-grained reactive libraries like Leptos, this is simply not the case.
-// You can create a signal in the root of your app and pass it down to other
-// components using provide_context(). Changing it will only cause rerendering
-// in the specific places it is actually used, not the whole app.
-#[component]
-fn Option2() -> impl IntoView {
- // here we create a signal in the root that can be consumed
- // anywhere in the app.
- let (count, set_count) = create_signal(0);
- // we'll pass the setter to specific components,
- // but provide the count itself to the whole app via context
- provide_context(count);
-
- view! {
-
"Option 2: Passing Signals"
- // SetterButton is allowed to modify the count
-
- // These consumers can only read from it
- // But we could give them write access by passing `set_count` if we wanted
-
-
-
-
- }
-}
-
-/// A button that increments our global counter.
-#[component]
-fn SetterButton(set_count: WriteSignal) -> impl IntoView {
- view! {
-
-
-
- }
-}
-
-/// A component that does some "fancy" math with the global count
-#[component]
-fn FancyMath() -> impl IntoView {
- // here we consume the global count signal with `use_context`
- let count = use_context::>()
- // we know we just provided this in the parent component
- .expect("there to be a `count` signal provided");
- let is_even = move || count() & 1 == 0;
-
- view! {
-
- "The number "
- {count}
- {move || if is_even() {
- " is"
- } else {
- " is not"
- }}
- " even."
-
- }
-}
-
-/// A component that shows a list of items generated from the global count.
-#[component]
-fn ListItems() -> impl IntoView {
- // again, consume the global count signal with `use_context`
- let count = use_context::>().expect("there to be a `count` signal provided");
-
- let squares = move || {
- (0..count())
- .map(|n| view! {
{n}"2" " is " {n * n}
})
- .collect::>()
- };
-
- view! {
-
-
{squares}
-
- }
-}
-
-// Option #3: Create a Global State Struct
-//
-// You can use this approach to build a single global data structure
-// that holds the state for your whole app, and then access it by
-// taking fine-grained slices using `create_slice` or `create_memo`,
-// so that changing one part of the state doesn't cause parts of your
-// app that depend on other parts of the state to change.
-
-#[derive(Default, Clone, Debug)]
-struct GlobalState {
- count: u32,
- name: String,
-}
-
-#[component]
-fn Option3() -> impl IntoView {
- // we'll provide a single signal that holds the whole state
- // each component will be responsible for creating its own "lens" into it
- let state = create_rw_signal(GlobalState::default());
- provide_context(state);
-
- view! {
-
- }
-}
-
-/// A component that updates the count in the global state.
-#[component]
-fn GlobalStateCounter() -> impl IntoView {
- let state = use_context::>().expect("state to have been provided");
-
- // `create_slice` lets us create a "lens" into the data
- let (count, set_count) = create_slice(
-
- // we take a slice *from* `state`
- state,
- // our getter returns a "slice" of the data
- |state| state.count,
- // our setter describes how to mutate that slice, given a new value
- |state, n| state.count = n,
- );
-
- view! {
-
-
-
- "Count is: " {count}
-
- }
-}
-
-/// A component that updates the count in the global state.
-#[component]
-fn GlobalStateInput() -> impl IntoView {
- let state = use_context::>().expect("state to have been provided");
-
- // this slice is completely independent of the `count` slice
- // that we created in the other component
- // neither of them will cause the other to rerun
- let (name, set_name) = create_slice(
- // we take a slice *from* `state`
- state,
- // our getter returns a "slice" of the data
- |state| state.name.clone(),
- // our setter describes how to mutate that slice, given a new value
- |state, n| state.name = n,
- );
-
- view! {
-
-
-
- "Name is: " {name}
-
- }
-}
-// This `main` function is the entry point into the app
-// It just mounts our component to the
-// Because we defined it as `fn App`, we can now use it in a
-// template as
-fn main() {
- leptos::mount_to_body(|| view! { })
-}
-```
-
-
-
+Check out the [`stores` example](https://github.com/leptos-rs/leptos/blob/main/examples/stores/src/lib.rs) in the repo for a more extensive example.
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index 86996ff..71868bf 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -18,7 +18,7 @@
- [No Macros: The View Builder Syntax](./view/builder.md)
- [Reactivity](./reactivity/README.md)
- [Working with Signals](./reactivity/working_with_signals.md)
- - [Responding to Changes with `create_effect`](./reactivity/14_create_effect.md)
+ - [Responding to Changes with Effects](./reactivity/14_create_effect.md)
- [Interlude: Reactivity and Functions](./reactivity/interlude_functions.md)
- [Testing](./testing.md)
- [Async](./async/README.md)
diff --git a/src/appendix_life_cycle.md b/src/appendix_life_cycle.md
index 3ad33ce..ec6c40c 100644
--- a/src/appendix_life_cycle.md
+++ b/src/appendix_life_cycle.md
@@ -13,26 +13,26 @@ Consider the following simple Leptos app:
```rust
use leptos::logging::log;
-use leptos::*;
+use leptos::prelude::*;
#[component]
pub fn App() -> impl IntoView {
- let (count, set_count) = create_signal(0);
+ let (count, set_count) = signal(0);
view! {
-
- {move || if count() % 2 == 0 {
- view! {
}.into_any()
} else {
- view! { }.into_view()
+ view! { }.into_any()
}}
}
}
#[component]
pub fn InnerComponent(count: ReadSignal) -> impl IntoView {
- create_effect(move |_| {
- log!("count is odd and is {}", count());
+ Effect::new(move |_| {
+ log!("count is odd and is {}", count.get());
});
view! {
@@ -98,20 +98,20 @@ This means that when your application is rendered, it creates a tree of nested e
let button = /* render the
}
+ >
+ // Suspend allows you use to an async block in the view
+
+ "Your shouting name is "
+ {move || Suspend::new(async move {
+ async_data.await
+ })}
+
+
impl IntoView {
// the children will be rendered once initially,
// and then whenever any resources has been resolved
- "Your shouting name is "
- {move || async_data.get()}
+ "Which should be the same as... "
+ {move || async_data.get().as_deref().map(ToString::to_string)}
}
}
fn main() {
- leptos::mount_to_body(App)
+ leptos::mount::mount_to_body(App)
}
```
diff --git a/src/async/12_transition.md b/src/async/12_transition.md
index f62202e..0cc7a2f 100644
--- a/src/async/12_transition.md
+++ b/src/async/12_transition.md
@@ -1,6 +1,6 @@
# ``
-You’ll notice in the `` example that if you keep reloading the data, it keeps flickering back to `"Loading..."`. Sometimes this is fine. For other times, there’s [``](https://docs.rs/leptos/latest/leptos/fn.Transition.html).
+You’ll notice in the `` example that if you keep reloading the data, it keeps flickering back to `"Loading..."`. Sometimes this is fine. For other times, there’s [``](https://docs.rs/leptos/0.7.0-gamma3/leptos/suspense/fn.Transition.html).
`` behaves exactly the same as ``, but instead of falling back every time, it only shows the fallback the first time. On all subsequent loads, it continues showing the old data until the new data are ready. This can be really handy to prevent the flickering effect, and to allow users to continue interacting with your application.
@@ -8,14 +8,14 @@ This example shows how you can create a simple tabbed contact list with `
Please enable JavaScript to view examples.
-
+
```
@@ -25,7 +25,7 @@ This example shows how you can create a simple tabbed contact list with ` String {
TimeoutFuture::new(1_000).await;
@@ -40,52 +40,57 @@ async fn important_api_call(id: usize) -> String {
#[component]
fn App() -> impl IntoView {
- let (tab, set_tab) = create_signal(0);
+ let (tab, set_tab) = signal(0);
+ let (pending, set_pending) = signal(false);
// this will reload every time `tab` changes
- let user_data = create_resource(tab, |tab| async move { important_api_call(tab).await });
+ let user_data = LocalResource::new(move || important_api_call(tab.get()));
view! {
}
}
fn main() {
- leptos::mount_to_body(App)
+ leptos::mount::mount_to_body(App)
}
```
diff --git a/src/async/13_actions.md b/src/async/13_actions.md
index ff55c6e..e62df7a 100644
--- a/src/async/13_actions.md
+++ b/src/async/13_actions.md
@@ -2,11 +2,11 @@
We’ve talked about how to load `async` data with resources. Resources immediately load data and work closely with `` and `` components to show whether data is loading in your app. But what if you just want to call some arbitrary `async` function and keep track of what it’s doing?
-Well, you could always use [`spawn_local`](https://docs.rs/leptos/latest/leptos/fn.spawn_local.html). This allows you to just spawn an `async` task in a synchronous environment by handing the `Future` off to the browser (or, on the server, Tokio or whatever other runtime you’re using). But how do you know if it’s still pending? Well, you could just set a signal to show whether it’s loading, and another one to show the result...
+Well, you could always use [`spawn_local`](https://docs.rs/leptos/0.7.0-gamma3/leptos/task/fn.spawn_local.html). This allows you to just spawn an `async` task in a synchronous environment by handing the `Future` off to the browser (or, on the server, Tokio or whatever other runtime you’re using). But how do you know if it’s still pending? Well, you could just set a signal to show whether it’s loading, and another one to show the result...
-All of this is true. Or you could use the final `async` primitive: [`create_action`](https://docs.rs/leptos/latest/leptos/fn.create_action.html).
+All of this is true. Or you could use the final `async` primitive: [`Action`](https://docs.rs/leptos/0.7.0-gamma3/leptos/reactive/actions/struct.Action.html).
-Actions and resources seem similar, but they represent fundamentally different things. If you’re trying to load data by running an `async` function, either once or when some other value changes, you probably want to use `create_resource`. If you’re trying to occasionally run an `async` function in response to something like a user clicking a button, you probably want to use `create_action`.
+Actions and resources seem similar, but they represent fundamentally different things. If you’re trying to load data by running an `async` function, either once or when some other value changes, you probably want to use a resource. If you’re trying to occasionally run an `async` function in response to something like a user clicking a button, you probably want to use an `Action`.
Say we have some `async` function we want to run.
@@ -16,22 +16,22 @@ async fn add_todo_request(new_title: &str) -> Uuid {
}
```
-`create_action` takes an `async` function that takes a reference to a single argument, which you could think of as its “input type.”
+`Action::new()` takes an `async` function that takes a reference to a single argument, which you could think of as its “input type.”
> The input is always a single type. If you want to pass in multiple arguments, you can do it with a struct or tuple.
>
> ```rust
> // if there's a single argument, just use that
-> let action1 = create_action(|input: &String| {
+> let action1 = Action::new(|input: &String| {
> let input = input.clone();
> async move { todo!() }
> });
>
> // if there are no arguments, use the unit type `()`
-> let action2 = create_action(|input: &()| async { todo!() });
+> let action2 = Action::new(|input: &()| async { todo!() });
>
> // if there are multiple arguments, use a tuple
-> let action3 = create_action(
+> let action3 = Action::new(
> |input: &(usize, String)| async { todo!() }
> );
> ```
@@ -41,7 +41,7 @@ async fn add_todo_request(new_title: &str) -> Uuid {
So in this case, all we need to do to create an action is
```rust
-let add_todo_action = create_action(|input: &String| {
+let add_todo_action = Action::new(|input: &String| {
let input = input.to_owned();
async move { add_todo_request(&input).await }
});
@@ -66,7 +66,7 @@ let todo_id = add_todo_action.value(); // RwSignal