From 564284f4be9b2931cd49f7e5435e0e8368fddd53 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 28 Jan 2022 03:04:21 -0500 Subject: [PATCH] docs: add rules of hooks --- .vscode/spellright.dict | 1 + docs/guide/src/SUMMARY.md | 5 +- docs/guide/src/interactivity/hooks.md | 73 +++++++++++++++++++ .../guide/src/interactivity/importanthooks.md | 23 ++++++ docs/guide/src/interactivity/index.md | 20 +++-- 5 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 docs/guide/src/interactivity/importanthooks.md diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict index 3b67f08b..fb7eef77 100644 --- a/.vscode/spellright.dict +++ b/.vscode/spellright.dict @@ -72,3 +72,4 @@ VirtualDoms bootstrapper WebkitGtk laymans +iter diff --git a/docs/guide/src/SUMMARY.md b/docs/guide/src/SUMMARY.md index 9772f6b5..5a8fadb9 100644 --- a/docs/guide/src/SUMMARY.md +++ b/docs/guide/src/SUMMARY.md @@ -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) diff --git a/docs/guide/src/interactivity/hooks.md b/docs/guide/src/interactivity/hooks.md index adf43c6e..b9ec32e5 100644 --- a/docs/guide/src/interactivity/hooks.md +++ b/docs/guide/src/interactivity/hooks.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). + +## 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. diff --git a/docs/guide/src/interactivity/importanthooks.md b/docs/guide/src/interactivity/importanthooks.md new file mode 100644 index 00000000..91d50cab --- /dev/null +++ b/docs/guide/src/interactivity/importanthooks.md @@ -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` + diff --git a/docs/guide/src/interactivity/index.md b/docs/guide/src/interactivity/index.md index 0e1a6125..b71a3e73 100644 --- a/docs/guide/src/interactivity/index.md +++ b/docs/guide/src/interactivity/index.md @@ -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.