docs: even more docs

This commit is contained in:
Jonathan Kelley committed 2022-03-06 20:37:57 -05:00
1 parent a4ab2d9de0
commit 0f87ebb277
25 files changed
+986 -174

No files matched your search

+154
View File
@@ -0,0 +1,154 @@
# UseRef
You might've noticed a fundamental limitation to `use_state`: to modify the value in-place, it must be cheaply cloneable. But what if your type is not cheap to clone?
In these cases, you should reach for `use_ref` which is essentially just a glorified `Rc<RefCell<T>>` (Rust [smart pointers](https://doc.rust-lang.org/book/ch15-04-rc.html)).
This provides us some runtime locks around our data, trading reliability for performance. For most cases though, you will find it hard to make `use_ref` crash.
> Note: this is *not* exactly like its React counterpart since calling `write` will cause a re-render. For more React parity, use the `write_silent` method.
To use the hook:
```rust
let names = use_ref(&cx, || vec!["susie", "calvin"]);
```
To read the hook values, use the `read()` method:
```rust
cx.render(rsx!{
ul {
names.read().iter().map(|name| {
rsx!{ "{name}" }
})
}
})
```
And then to write into the hook value, use the `write` method:
```rust
names.write().push("Tiger");
```
If you don't want to re-render the component when names is updated, then we can use the `write_silent` method:
```rust
names.write_silent().push("Transmogrifier");
```
Again, like `UseState`, the `UseRef` handle is clonable into async contexts:
```rust
// infinitely push calvin into the list
cx.spawn({
to_owned![names];
async move {
loop {
wait_ms(100).await;
names.write().push("Calvin");
}
}
})
```
## Using UseRef for complex state
The best use case of `use_ref` is to manage "complex" data local to your component. This should be things like large structs, collections, queues, state machines, and other data where cloning would be expensive.
```rust
let val = use_state(&cx, || vec![1, 3, 3, 7]);
let val = use_state(&cx, || (0..10000).collect::<Vec<_>x>());
let val = use_state(&cx, || Configuration {
a: "asdasd",
// .. more complex fields
});
```
UseRef can be sent down into child components too.
UseRef memoizes with itself, performing a cheap pointer comparison. If the UseRef handle is the same, then the component can be memoized.
```rust
fn app(cx: Scope) -> Element {
let val = use_ref(&cx, || 0);
cx.render(rsx!{
Child { val: val.clone() }
})
}
fn Child(cx: Scope, val: UseRef<i32>) -> Element {
// ...
}
```
## Using UseRef with "models"
One option for state management that UseRef enables is the development of a "model" for your components. This particular pattern enables you to model your state with regular structs.
For instance, our calculator example uses a struct to model the state.
```rust
struct Calculator {
display_value: String,
operator: Option<Operator>,
waiting_for_operand: bool,
cur_val: f64,
}
```
Our component is really simple - we just call `use_ref` to get an initial calculator state.
```rust
fn app(cx: Scope) -> Element {
let state = use_ref(&cx, Calculator::new);
cx.render(rsx!{
// the rendering code
})
}
```
In our UI, we can then use `read` and a helper method to get data out of the model.
```rust
// Our accessor method
impl Calculator {
fn formatted_display(&self) -> String {
self.display_value
.parse::<f64>()
.unwrap()
.separated_string()
}
}
// And then in the UI
cx.render(rsx!{
div { [state.read().formatted_display()] }
})
```
To modify the state, we can setup a helper method and then attach it to a callback.
```rust
// our helper
impl Calculator {
fn clear_display(&mut self) {
self.display_value = "0".to_string()
}
}
// our callback
cx.render(rsx!{
button {
onclick: move |_| state.write().clear_display(),
}
})
```