docs: add rules of hooks

This commit is contained in:
Jonathan Kelley 2022-01-28 03:04:21 -05:00
parent 4fb2221ee1
commit 564284f4be
5 changed files with 113 additions and 9 deletions

View file

@ -72,3 +72,4 @@ VirtualDoms
bootstrapper
WebkitGtk
laymans
iter

View file

@ -13,10 +13,11 @@
- [Properties](elements/propsmacro.md)
- [Reusing, Importing, and Exporting](elements/exporting_components.md)
- [Children and Attributes](elements/component_children.md)
- [Theory of React](elements/composing.md)
- [Theory of Reactive Programming](elements/composing.md)
- [Adding Interactivity](interactivity/index.md)
- [Hooks and Internal State](interactivity/hooks.md)
- [Event handlers](interactivity/event_handlers.md)
- [Event Listeners](interactivity/event_handlers.md)
- [UseState and UseRef](interactivity/importanthooks.md)
- [User Input and Controlled Components](interactivity/user_input.md)
- [Lifecycle, updates, and effects](interactivity/lifecycles.md)
- [Managing State](state/index.md)

View file

@ -76,6 +76,78 @@ This is why hooks called out of order will fail - if we try to downcast a `Hook<
This pattern might seem strange at first, but it can be a significant upgrade over structs as blobs of state, which tend to be difficult to use in [Rust given the ownership system](https://rust-lang.github.io/rfcs/2229-capture-disjoint-fields.html).
## Rules of hooks
Hooks are sensitive to how they are used. To use hooks, you must abide by the
"rules of hooks" (borrowed from react)](https://reactjs.org/docs/hooks-rules.html):
- Functions with "use_" should not be called in callbacks
- Functions with "use_" should not be called out of order
- Functions with "use_" should not be called in loops or conditionals
Examples of "no-nos" include:
### ❌ Nested uses
```rust
// ❌ don't call use_hook or any `use_` function *inside* use_hook!
cx.use_hook(|_| {
let name = cx.use_hook(|_| "ads");
})
// ✅ instead, move the first hook above
let name = cx.use_hook(|_| "ads");
cx.use_hook(|_| {
// do something with name here
})
```
### ❌ Uses in conditionals
```rust
// ❌ don't call use_ in conditionals!
if do_thing {
let name = use_state(&cx, || 0);
}
// ✅ instead, *always* call use_state but leave your logic
let name = use_state(&cx, || 0);
if do_thing {
// do thing with name here
}
```
### ❌ Uses in loops
```rust
// ❌ Do not use hooks in loops!
let mut nodes = vec![];
for name in names {
let age = use_state(&cx, |_| 0);
nodes.push(cx.render(rsx!{
div { "{age}" }
}))
}
// ✅ Instead, consider refactoring your usecase into components
#[inline_props]
fn Child(cx: Scope, name: String) -> Element {
let age = use_state(&cx, |_| 0);
cx.render(rsx!{ div { "{age}" } })
}
// ✅ Or, use a hashmap with use_ref
```rust
let ages = use_ref(&cx, |_| HashMap::new());
names.iter().map(|name| {
let age = ages.get(name).unwrap();
cx.render(rsx!{ div { "{age}" } })
})
```
## Building new Hooks
However, most hooks you'll interact with *don't* return an `&mut T` since this is not very useful in a real-world situation.
@ -168,6 +240,7 @@ fn example(cx: Scope) -> Element {
```
## Hooks provided by the `Dioxus-Hooks` package
By default, we bundle a handful of hooks in the Dioxus-Hooks package. Feel free to click on each hook to view its definition and associated documentation.

View file

@ -0,0 +1,23 @@
# `use_state` and `use_ref`
Most components you will write in Dioxus will need to store state somehow. For local state, we provide two very convenient hooks:
- `use_state`
- `use_ref`
Both of these hooks are extremely powerful and flexible, so we've dedicated this section to understanding them properly.
## Note on Hooks
If you're struggling with errors due to usage in hooks, make sure you're following the rules of hooks:
## `use_state`
The `use_state` hook is very similar to its React counterpart.
## `use_ref`

View file

@ -64,7 +64,7 @@ The most common hook you'll use for storing state is `use_state`. `use_state` pr
```rust
fn App(cx: Scope)-> Element {
let post = use_state(&cx, || {
let (post, set_post) = use_state(&cx, || {
PostData {
id: Uuid::new_v4(),
score: 10,
@ -84,10 +84,10 @@ fn App(cx: Scope)-> Element {
}
```
Whenever we have a new post that we want to render, we can call `set` on `post` and provide a new value:
Whenever we have a new post that we want to render, we can call `set_post` and provide a new value:
```rust
post.set(PostData {
set_post(PostData {
id: Uuid::new_v4(),
score: 20,
comment_count: 0,
@ -128,7 +128,13 @@ We'll dive much deeper into event listeners later.
### Updating state asynchronously
We can also update our state outside of event listeners with `coroutines`. `Coroutines` are asynchronous blocks of our component that have the ability to cleanly interact with values, hooks, and other data in the component. Since coroutines stick around between renders, the data in them must be valid for the `'static` lifetime. We must explicitly declare which values our task will rely on to avoid the `stale props` problem common in React.
We can also update our state outside of event listeners with `futures` and `coroutines`.
- `Futures` are Rust's version of promises that can execute asynchronous work by an efficient polling system. We can submit new futures to Dioxus either through `push_future` which returns a `TaskId` or with `spawn`.
-
- `Coroutines` are asynchronous blocks of our component that have the ability to cleanly interact with values, hooks, and other data in the component.
Since coroutines and Futures stick around between renders, the data in them must be valid for the `'static` lifetime. We must explicitly declare which values our task will rely on to avoid the `stale props` problem common in React.
We can use tasks in our components to build a tiny stopwatch that ticks every second.
@ -139,7 +145,7 @@ fn App(cx: Scope)-> Element {
let mut sec_elapsed = use_state(&cx, || 0);
use_future(&cx, || {
let mut sec_elapsed = sec_elapsed.for_async();
to_owned![sec_elapsed]; // explicitly capture this hook for use in async
async move {
loop {
TimeoutFuture::from_ms(1000).await;
@ -152,7 +158,7 @@ fn App(cx: Scope)-> Element {
}
```
Using asynchronous code can be difficult! This is just scratching the surface of what's possible. We have an entire chapter on using async properly in your Dioxus Apps.
Using asynchronous code can be difficult! This is just scratching the surface of what's possible. We have an entire chapter on using async properly in your Dioxus Apps. We have an entire section dedicated to using `async` properly later in this book.
### How do I tell Dioxus that my state changed?
@ -170,7 +176,7 @@ With these building blocks, we can craft new hooks similar to `use_state` that l
In general, Dioxus should be plenty fast for most use cases. However, there are some rules you should consider following to ensure your apps are quick.
- 1) **Don't call set—state _while rendering_**. This will cause Dioxus to unnecessarily re-check the component for updates.
- 1) **Don't call set—state _while rendering_**. This will cause Dioxus to unnecessarily re-check the component for updates or enter an infinite loop.
- 2) **Break your state apart into smaller sections.** Hooks are explicitly designed to "unshackle" your state from the typical model-view-controller paradigm, making it easy to reuse useful bits of code with a single function.
- 3) **Move local state down**. Dioxus will need to re-check child components of your app if the root component is constantly being updated. You'll get best results if rapidly-changing state does not cause major re-renders.