From 9f70015804341a6edea7360be6b587453e29ff07 Mon Sep 17 00:00:00 2001
From: Greg Johnston
Date: Wed, 4 Dec 2024 20:15:38 -0500
Subject: [PATCH] add stores example for iteration
---
src/15_global_state.md | 2 +
src/view/04b_iteration.md | 110 +++++++++++++++++++++++++++++++++++++-
2 files changed, 111 insertions(+), 1 deletion(-)
diff --git a/src/15_global_state.md b/src/15_global_state.md
index 1f39e67..2fe4df2 100644
--- a/src/15_global_state.md
+++ b/src/15_global_state.md
@@ -81,6 +81,8 @@ fn FancyMath() -> impl IntoView {
## Option #3: Create a Global State Store
+> Some of this content is duplicated from the section on complex iteration with stores [here](../view/04b_iteration.md#option-4-stores). Both sections are intermediate/optional content, so I thought some duplication couldn’t hurt.
+
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.)
Stores allow you to wrap an entire struct, and reactively read from and update individual fields without tracking changes to other fields.
diff --git a/src/view/04b_iteration.md b/src/view/04b_iteration.md
index bb8b805..73e7b24 100644
--- a/src/view/04b_iteration.md
+++ b/src/view/04b_iteration.md
@@ -277,7 +277,115 @@ will still be more efficient for pinpoint updates here.
## Option 4: Stores
+> Some of this content is duplicated in the section on global state management with stores [here](../15_global_state.md#option-3-create-a-global-state-store). Both sections are intermediate/optional content, so I thought some duplication couldn’t hurt.
+
Leptos 0.7 introduces a new reactive primitive called “stores.” Stores are designed to address
the issues described in this chapter so far. They’re a bit experimental, so they require an additional dependency called `reactive_stores` in your `Cargo.toml`.
-**TODO finish stores example for this chapter.**
+Stores give you fine-grained reactive access to the individual fields of a struct, and to individual items in collections like `Vec<_>`, without needing to create nested signals or memos manually, as in the options given above.
+
+Stores are built on top of the `Store` derive macro, which creates a getter for each field of a struct. Calling this getter gives reactive access to that particular field. Reading from it will track only that field and its parents/children, and updating it will only notify that field and its parents/children, but not siblings. In other words, mutating `value` will not notify `key`, and so on.
+
+We can adapt the data types we used in the examples above.
+
+The top level of a store always needs to be a struct, so we’ll create a `Data` wrapper with a single `rows` field.
+```rust
+#[derive(Store, Debug, Clone)]
+pub struct Data {
+ #[store(key: String = |row| row.key.clone())]
+ rows: Vec,
+}
+
+#[derive(Store, Debug, Clone)]
+struct DatabaseEntry {
+ key: String,
+ value: i32,
+}
+```
+Adding `#[store(key)]` to the `rows` field allows us to have keyed access to the fields of the store, which will be useful in the `` component below. We can simply use `key`, the same key that we’ll use in ``.
+
+The `` component is pretty straightforward:
+```rust
+{move || value.get()}
}
+ }
+/>
+```
+Because `rows` is a keyed field, it implements `IntoIterator`, and we can simply use `move || data.rows()` as the `each` prop. This will react to any changes to the `rows` list, just as `move || data.get()` did in our nested-signal version.
+
+The `key` field calls `.read()` to get access to the current value of the row, then clones and returns the `key` field.
+
+In `children` prop, calling `child.value()` gives us reactive access to the `value` field for the row with this key. If rows are reordered, added, or removed, the keyed store field will keep in sync so that this `value` is always associated with the correct key.
+
+In the update button handler, we’ll iterate over the entries in `rows`, updating each one:
+```rust
+for row in data.rows().iter_unkeyed() {
+ *row.value().write() *= 2;
+}
+```
+
+### Pros
+
+We get the fine-grained reactivity of the nested-signal and memo versions, without needing to manually create nested signals or memoized slices. We can work with plain data (a struct and `Vec<_>`), annotated with a derive macro, rather than special nested reactive types.
+
+Personally, I think the stores version is the nicest one here. And no surprise, as it’s the newest API. We’ve had a few years to think about these things and stores include some of the lessons we’ve learned!
+
+### Cons
+
+On the other hand, it’s the newest API. As of writing this sentence (December 2024), stores have only been released for a few weeks; I am sure that there are still some bugs or edge cases to be figured out.
+
+
+### Full Example
+
+Here’s the complete store example. You can find another, more complete example [here](https://github.com/leptos-rs/leptos/blob/main/examples/stores/src/lib.rs), and more discussion in the book [here](../15_global_state.md).
+```
+#[component]
+pub fn App() -> impl IntoView {
+ // instead of a single with the rows, we create a store for Data
+ let data = Store::new(Data {
+ rows: vec![
+ DatabaseEntry {
+ key: "foo".to_string(),
+ value: 10,
+ },
+ DatabaseEntry {
+ key: "bar".to_string(),
+ value: 20,
+ },
+ DatabaseEntry {
+ key: "baz".to_string(),
+ value: 15,
+ },
+ ],
+ });
+
+ view! {
+ // when we click, update each row,
+ // doubling its value
+
+ // iterate over the rows and display each value
+ {move || value.get()} }
+ }
+ />
+ }
+}
+```