mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
docs: add rules of hooks
This commit is contained in:
parent
4fb2221ee1
commit
564284f4be
5 changed files with 113 additions and 9 deletions
1
.vscode/spellright.dict
vendored
1
.vscode/spellright.dict
vendored
|
@ -72,3 +72,4 @@ VirtualDoms
|
||||||
bootstrapper
|
bootstrapper
|
||||||
WebkitGtk
|
WebkitGtk
|
||||||
laymans
|
laymans
|
||||||
|
iter
|
||||||
|
|
|
@ -13,10 +13,11 @@
|
||||||
- [Properties](elements/propsmacro.md)
|
- [Properties](elements/propsmacro.md)
|
||||||
- [Reusing, Importing, and Exporting](elements/exporting_components.md)
|
- [Reusing, Importing, and Exporting](elements/exporting_components.md)
|
||||||
- [Children and Attributes](elements/component_children.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)
|
- [Adding Interactivity](interactivity/index.md)
|
||||||
- [Hooks and Internal State](interactivity/hooks.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)
|
- [User Input and Controlled Components](interactivity/user_input.md)
|
||||||
- [Lifecycle, updates, and effects](interactivity/lifecycles.md)
|
- [Lifecycle, updates, and effects](interactivity/lifecycles.md)
|
||||||
- [Managing State](state/index.md)
|
- [Managing State](state/index.md)
|
||||||
|
|
|
@ -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).
|
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
|
## 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.
|
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
|
## 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.
|
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.
|
||||||
|
|
23
docs/guide/src/interactivity/importanthooks.md
Normal file
23
docs/guide/src/interactivity/importanthooks.md
Normal 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`
|
||||||
|
|
|
@ -64,7 +64,7 @@ The most common hook you'll use for storing state is `use_state`. `use_state` pr
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn App(cx: Scope)-> Element {
|
fn App(cx: Scope)-> Element {
|
||||||
let post = use_state(&cx, || {
|
let (post, set_post) = use_state(&cx, || {
|
||||||
PostData {
|
PostData {
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
score: 10,
|
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
|
```rust
|
||||||
post.set(PostData {
|
set_post(PostData {
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
score: 20,
|
score: 20,
|
||||||
comment_count: 0,
|
comment_count: 0,
|
||||||
|
@ -128,7 +128,13 @@ We'll dive much deeper into event listeners later.
|
||||||
|
|
||||||
### Updating state asynchronously
|
### 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.
|
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);
|
let mut sec_elapsed = use_state(&cx, || 0);
|
||||||
|
|
||||||
use_future(&cx, || {
|
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 {
|
async move {
|
||||||
loop {
|
loop {
|
||||||
TimeoutFuture::from_ms(1000).await;
|
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?
|
### 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.
|
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.
|
- 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.
|
- 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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue