Skip to content

Commit adf11eb

Browse files
authored
Improve stores documentation (#538)
1 parent 5750e23 commit adf11eb

File tree

1 file changed

+63
-16
lines changed

1 file changed

+63
-16
lines changed

docs-src/0.7/src/essentials/basics/collections.md

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ To make working with structs and collections easier, Dioxus provides the **Store
2020

2121
## Reactive Stores
2222

23-
In Dioxus, reactive stores are types that isolate reactivity to just a single field or entry in a collection. Stores allow us to "zoom in" on a smaller portion of our data, ignoring all other reads and writes.
23+
In Dioxus, reactive stores are types that isolate reactivity to just a path in a data structure. Stores allow us to "zoom in" on a smaller portion of our data, ignoring all other reads and writes.
2424

25-
The simplest stores are structs that derive the `Store` trait:
25+
The simplest stores are structs that derive a `Store` trait:
2626

2727
```rust
2828
#[derive(Store)]
@@ -41,7 +41,7 @@ let header = use_store(|| HeaderState {
4141
});
4242
```
4343

44-
The `Store` drive macro generates additional methods on `HeaderState` that allow us to "zoom in" to fields of the struct. We access the fields by calling the field name like a method:
44+
The `Store` drive macro generates additional methods on `Store<HeaderState>` that allow us to "zoom in" to fields of the struct. We access the fields by calling the field name like a method:
4545

4646
```rust
4747
fn app() -> Element {
@@ -74,13 +74,22 @@ Notice how the default `Store` we get from `use_store` has an elided default gen
7474
let title: Store<HeaderState> = use_store(|| HeaderState::new());
7575
```
7676

77-
Because the lens is "unnamable," we can't easily add it to structs or pass it as a function argument. In these cases, we can use the `boxed` and `boxed_mut` methods to convert the lens into a ReadSignal or WriteSignal at the cost of an allocation:
77+
Because the lens is "unnamable", we need to accept the lens as a generic in any functions that work with stores. If we only need to read the store, we can require the lens implements the `Readable` trait. If we need to write to the store, we can require the lens implements the `Writable` trait.
7878

7979
```rust
80-
let title: ReadSignal<HeaderState> = header.title().boxed();
80+
// This function works with any lens that can read the header state like ReadStore<HeaderState> or Store<HeaderState, _>
81+
fn get_title(state: Store<HeaderState, impl Readable<Target = HeaderState>>) -> String {
82+
state.title().cloned()
83+
}
84+
85+
// This function works with any lens that can write to the header state like Store<HeaderState> or Store<HeaderState, _>
86+
fn clear_title(state: Store<HeaderState, impl Writable<Target = HeaderState>>) {
87+
state.title().take();
88+
}
8189
```
8290

83-
On the boundary of components, this is done automatically by "decaying" lenses into ReadSignals:
91+
On the component boundary, Stores are automatically boxed and converted to `ReadSignal` or `ReadStore` as needed so you don't need to worry about the lens type:
92+
8493

8594
```rust
8695
fn app() -> Element {
@@ -92,13 +101,20 @@ fn app() -> Element {
92101
rsx! {
93102
// the lens returned by `.title()` decays into a `ReadSignal` automatically!
94103
Title { title: header.title() }
104+
// the lens returned by `.subtitle()` decays into a `ReadStore` automatically!
105+
Subtitle { subtitle: header.subtitle() }
95106
}
96107
}
97108

98109
#[component]
99110
fn Title(title: ReadSignal<String>) -> Element {
100111
// ..
101112
}
113+
114+
#[component]
115+
fn Subtitle(subtitle: ReadStore<String>) -> Element {
116+
// ..
117+
}
102118
```
103119

104120
### Stores are Readable and Writable
@@ -213,7 +229,7 @@ let len = match header.subtitle().transpose() {
213229
};
214230
```
215231

216-
Alternatively, we can use `.ref()` the lens to gain access to the underlying value, but we lose the ability to reactively "zoom in" further:
232+
Alternatively, we can use `.as_ref()` the lens to gain access to the underlying value, but we lose the ability to reactively "zoom in" further:
217233

218234
```rust
219235
let len = match header.subtitle().as_ref() {
@@ -222,7 +238,7 @@ let len = match header.subtitle().as_ref() {
222238
};
223239
```
224240

225-
You can usually choose either approach - just know that using `.ref()` calls `.read()` internally, and the "reactivity zoom" might not be perfectly precise.
241+
You can usually choose either approach - just know that using `.as_ref()` calls `.read()` internally, and the "reactivity zoom" might not be perfectly precise.
226242

227243
## Reactive Collections
228244

@@ -284,7 +300,7 @@ fn app() -> Element {
284300
let mut users = use_store(|| HashMap::<UserId, UserData>::new());
285301

286302
rsx! {
287-
for (id, user) in users.read() {
303+
for (id, user) in users.iter() {
288304
ListItem { key: "{id}", user }
289305
}
290306
}
@@ -301,24 +317,55 @@ fn ListItem(user: ReadSignal<UserData>) -> Element {
301317

302318
The `Store<HashMap<K, V>>` type is a special type that implements reactivity on a per-entry basis. When we insert or remove values from the `users` store, only *one* re-render is queued. If we edit an individual entry in the HashMap, only a single `ListItem` will re-render.
303319

304-
Alternatively, we could pass the entire `Store` to the ListItem, along with the `UserId` key, allowing us to further lens into specific fields of our UserData entries:
320+
Alternatively, we could derive `Store` on our `UserData` type, and accept `ReadStore<UserData>` allowing us to further lens into specific fields of our UserData entry:
305321

306322
```rust
323+
#[derive(Store)]
324+
struct UserData {
325+
name: String,
326+
email: String,
327+
}
328+
307329
fn app() -> Element {
308-
// switch to using `use_store`
309-
let mut users = use_store(|| HashMap::<UserId, UserData>::new());
330+
let users = use_store(|| HashMap::<UserId, UserData>::new());
310331

311332
rsx! {
312-
for (id, user) in users.read() {
313-
ListItem { key: "{id}", users, id }
333+
for (id, user) in users.iter() {
334+
ListItem { key: "{id}", user }
314335
}
315336
}
316337
}
317338

318339
#[component]
319-
fn ListItem(users: Store<HashMap<UserId, UserData>>, id: UserId) -> Element {
340+
fn ListItem(user: ReadStore<UserData>) -> Element {
320341
rsx! {
321-
li { "{users.get(id)).read()}" }
342+
li { "{user.name()}" }
343+
}
344+
}
345+
```
346+
347+
## Extending Stores with Methods
348+
349+
You can extend your store types with methods with the `#[store]` attribute macro. Methods inside the macro are converted into an extension trait that is automatically implemented for `Store<T, Lens>`. The macro will automatically add bounds to the `Lens` generic based on the self parameter of the method. If the method takes `&self`, the `Lens` will be bound by `Readable`. If the method takes `&mut self`, the `Lens` will be bound by `Writable`.
350+
351+
```rust
352+
type MappedUserDataStore<Lens> = Store<String, MappedMutSignal<String, Lens, fn(&UserData) -> &String, fn(&mut UserData) -> &mut String>>;
353+
354+
#[store]
355+
impl<Lens> Store<UserData, Lens> {
356+
// This will automatically require `Readable` on the lens since it takes `&self`
357+
fn user_email(&self) -> String {
358+
self.email().cloned()
359+
}
360+
361+
// This will automatically require `Writable` on the lens since it takes `&mut self`
362+
fn clear_name(&mut self) {
363+
self.name().take();
364+
}
365+
366+
// This method does not require any bounds on the lens since it takes `self`
367+
fn into_parts(self) -> (MappedUserDataStore<Lens>, MappedUserDataStore<Lens>) where Self: Copy {
368+
(self.email(), self.name())
322369
}
323370
}
324371
```

0 commit comments

Comments
 (0)