mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
docs: even more docs
This commit is contained in:
parent
a4ab2d9de0
commit
0f87ebb277
25 changed files with 986 additions and 174 deletions
|
@ -15,26 +15,39 @@
|
||||||
- [Children and Attributes](components/component_children.md)
|
- [Children and Attributes](components/component_children.md)
|
||||||
- [How Data Flows](components/composing.md)
|
- [How Data Flows](components/composing.md)
|
||||||
- [Adding Interactivity](interactivity/index.md)
|
- [Adding Interactivity](interactivity/index.md)
|
||||||
- [Hooks and Internal State](interactivity/hooks.md)
|
|
||||||
- [UseState and UseRef](interactivity/importanthooks.md)
|
|
||||||
- [Event Listeners](interactivity/event_handlers.md)
|
- [Event Listeners](interactivity/event_handlers.md)
|
||||||
- [User Input and Controlled Components](interactivity/user_input.md)
|
- [Hooks](interactivity/hooks.md)
|
||||||
- [Lifecycle, updates, and effects](interactivity/lifecycles.md)
|
- [UseState](interactivity/usestate.md)
|
||||||
|
- [UseRef](interactivity/useref.md)
|
||||||
|
- [User Input](interactivity/user_input.md)
|
||||||
|
<!-- - [Effects](interactivity/lifecycles.md) -->
|
||||||
- [Managing State](state/index.md)
|
- [Managing State](state/index.md)
|
||||||
- [Local State](state/localstate.md)
|
- [Local State](state/localstate.md)
|
||||||
- [Lifting State](state/liftingstate.md)
|
- [Lifting State](state/liftingstate.md)
|
||||||
- [Global State](state/sharedstate.md)
|
- [Global State](state/sharedstate.md)
|
||||||
|
- [Fanning Out](state/fanout.md)
|
||||||
- [Error handling](state/errorhandling.md)
|
- [Error handling](state/errorhandling.md)
|
||||||
|
- [Helper Crates](helpers/index.md)
|
||||||
|
- [Fermi](state/fermi.md)
|
||||||
|
- [Router](state/router.md)
|
||||||
- [Working with Async](async/index.md)
|
- [Working with Async](async/index.md)
|
||||||
- [Tasks](async/asynctasks.md)
|
- [Updating State](async/loading_state.md)
|
||||||
- [Fetching](async/fetching.md)
|
- [Fetching](async/fetching.md)
|
||||||
|
- [UseFuture](async/use_future.md)
|
||||||
|
- [UseCoroutine](async/coroutines.md)
|
||||||
|
- [WebSockets](async/sockets.md)
|
||||||
|
<!-- - [Tasks](async/asynctasks.md) -->
|
||||||
|
|
||||||
|
<!--
|
||||||
- [Putting it all together: Dog Search Engine](tutorial/index.md)
|
- [Putting it all together: Dog Search Engine](tutorial/index.md)
|
||||||
- [New app](tutorial/new_app.md)
|
- [New app](tutorial/new_app.md)
|
||||||
- [Structuring our app](tutorial/structure.md)
|
- [Structuring our app](tutorial/structure.md)
|
||||||
- [Defining State](tutorial/state.md)
|
- [Defining State](tutorial/state.md)
|
||||||
- [Defining Components](tutorial/components.md)
|
- [Defining Components](tutorial/components.md)
|
||||||
- [Styling](tutorial/styling.md)
|
- [Styling](tutorial/styling.md)
|
||||||
- [Bundling](tutorial/publishing.md)
|
- [Bundling](tutorial/publishing.md) -->
|
||||||
|
|
||||||
|
|
||||||
- [Next Steps and Advanced Topics](final.md)
|
- [Next Steps and Advanced Topics](final.md)
|
||||||
|
|
||||||
|
|
||||||
|
|
1
docs/guide/src/async/coroutines.md
Normal file
1
docs/guide/src/async/coroutines.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Coroutines
|
|
@ -4,19 +4,101 @@ Not all apps you'll build can be self-contained with synchronous code. You'll of
|
||||||
|
|
||||||
So far, we've only talked about building apps with synchronous code, so this chapter will focus integrating asynchronous code into your app.
|
So far, we've only talked about building apps with synchronous code, so this chapter will focus integrating asynchronous code into your app.
|
||||||
|
|
||||||
|
|
||||||
## The Runtime
|
## The Runtime
|
||||||
|
|
||||||
By default, Dioxus-Desktop ships with the `Tokio` runtime and automatically sets everything up for you.
|
By default, Dioxus-Desktop ships with the `Tokio` runtime and automatically sets everything up for you. This is currently not configurable, though it would be easy to write an integration for Dioxus desktop that uses a different asynchronous runtime.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Send/Sync
|
## Send/Sync
|
||||||
Writing apps that deal with Send/Sync can be frustrating at times. Under the hood, Dioxus is not currently thread-safe, so any async code you write does *not* need to be `Send/Sync`. That means Cell/Rc/RefCell are all fair game.
|
Writing apps that deal with Send/Sync can be frustrating at times. Under the hood, Dioxus is not currently thread-safe, so any async code you write does *not* need to be `Send/Sync`. That means that you can use non-thread-safe structures like `Cell`, `Rc`, and `RefCell`.
|
||||||
|
|
||||||
|
All async code in your app is polled on a `LocalSet`, so you can also use `tokio::spawn_local`.
|
||||||
|
|
||||||
|
## Spawning a future
|
||||||
|
|
||||||
|
Currently, all futures in Dioxus must be `'static`. To spawn a future, simply call `cx.spawn()`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
rsx!{
|
||||||
|
button {
|
||||||
|
onclick: move |_| cx.spawn(async move {
|
||||||
|
let result = fetch_thing().await;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Calling `spawn` will give you a `JoinHandle` which lets you cancel or pause the future.
|
||||||
|
|
||||||
|
|
||||||
|
## Setting state from within a future
|
||||||
|
|
||||||
All async code in your app is polled on a `LocalSet`, so any async code we w
|
If you start to write some async code, you'll quickly be greeted with the infamous error about borrowed items in static closures.
|
||||||
|
|
||||||
|
```
|
||||||
|
error[E0759]: `cx` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
|
||||||
|
--> examples/tasks.rs:13:27
|
||||||
|
|
|
||||||
|
12 | fn app(cx: Scope) -> Element {
|
||||||
|
| ----- this data with an anonymous lifetime `'_`...
|
||||||
|
13 | let count = use_state(&cx, || 0);
|
||||||
|
| ^^^ ...is used here...
|
||||||
|
14 |
|
||||||
|
15 | use_future(&cx, (), move |_| {
|
||||||
|
| ---------- ...and is required to live as long as `'static` here
|
||||||
|
|
|
||||||
|
note: `'static` lifetime requirement introduced by this bound
|
||||||
|
--> /Users/jonkelley/Development/dioxus/packages/hooks/src/usefuture.rs:25:29
|
||||||
|
|
|
||||||
|
25 | F: Future<Output = T> + 'static,
|
||||||
|
| ^^^^^^^
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0759`.
|
||||||
|
error: could not compile `dioxus` due to previous error
|
||||||
|
```
|
||||||
|
|
||||||
|
Rustc tends to provide great feedback in its errors, but this particular error is actually quite unhelpful. For reference, here's our code:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let count = use_state(&cx, || 0);
|
||||||
|
|
||||||
|
cx.spawn(async move {
|
||||||
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||||
|
count += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
button {
|
||||||
|
onclick: move |_| count.set(0),
|
||||||
|
"Reset the count"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Simple, right? We spawn a future that updates the value after one second has passed. Well, yes, and no. Our `count` value is only valid for the lifetime of this component, but our future could still be running even after the component re-renders. By default, Dioxus places a requirement on all futures to be `'static`, meaning they can't just borrow state from our hooks outright.
|
||||||
|
|
||||||
|
To get around this, we need to get a `'static` handle to our state. All Dioxus hooks implement `Clone`, so you simply need to call clone before spawning your future. Any time you get the particular error above, make sure you call `Clone` or `ToOwned`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
cx.spawn({
|
||||||
|
let mut count = count.clone();
|
||||||
|
async move {
|
||||||
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
To make this a little bit easier, Dioxus exports the `to_owned!` macro which will create a binding as shown above, which can be quite helpful when dealing with many values.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
cx.spawn({
|
||||||
|
to_owned![count, age, name, description, etc];
|
||||||
|
async move {
|
||||||
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
> This section is currently under construction! 🏗
|
|
||||||
|
|
8
docs/guide/src/async/loading_state.md
Normal file
8
docs/guide/src/async/loading_state.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Updating State
|
||||||
|
|
||||||
|
After your future has been spawned, you might want to update the state of our UI. Possible implementations might include:
|
||||||
|
|
||||||
|
- A loading state
|
||||||
|
- A transition state
|
||||||
|
- Showing failures
|
||||||
|
- Listening to sockets and updating the app
|
1
docs/guide/src/async/sockets.md
Normal file
1
docs/guide/src/async/sockets.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# WebSockets
|
1
docs/guide/src/async/use_future.md
Normal file
1
docs/guide/src/async/use_future.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# UseFuture
|
|
@ -137,6 +137,8 @@ fn clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Passing nodes through props means that they are immutable. If you find yourself needing to mutate nodes passed through props, consider creating a new node in its place that takes on its attributes, children, and listeners.
|
||||||
|
|
||||||
<!-- ## Passing attributes
|
<!-- ## Passing attributes
|
||||||
|
|
||||||
In the cases where you need to pass arbitrary element properties into a component - say to add more functionality to the `<a>` tag, Dioxus will accept any quoted fields. This is similar to adding arbitrary fields to regular elements using quotes.
|
In the cases where you need to pass arbitrary element properties into a component - say to add more functionality to the `<a>` tag, Dioxus will accept any quoted fields. This is similar to adding arbitrary fields to regular elements using quotes.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Thinking in React
|
# Thinking in Reactively
|
||||||
|
|
||||||
We've finally reached the point in our tutorial where we can talk about the theory of Reactive interfaces. We've talked about defining a declarative view, but not about the aspects that make our code *reactive*.
|
We've finally reached the point in our tutorial where we can talk about the theory of Reactivity. We've talked about defining a declarative view, but not about the aspects that make our code *reactive*.
|
||||||
|
|
||||||
Understanding the theory of reactive programming is essential to making sense of Dioxus and writing effective, performant UIs.
|
Understanding the theory of reactive programming is essential to making sense of Dioxus and writing effective, performant UIs.
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ This section is a bit long, but worth the read. We recommend coffee, tea, and/or
|
||||||
|
|
||||||
## Reactive Programming
|
## Reactive Programming
|
||||||
|
|
||||||
Dioxus is one the very few Rust libraries that provide a "Reactive Programming Model". The term "Reactive programming" is a classification of programming paradigm - much like functional or imperative programming. This is a very important distinction since it affects how we *think* about our code.
|
Dioxus is one of a handful of Rust libraries that provide a "Reactive Programming Model". The term "Reactive programming" is a classification of programming paradigm - much like functional or imperative programming. This is a very important distinction since it affects how we *think* about our code.
|
||||||
|
|
||||||
Reactive programming is a programming model concerned with deriving computations from asynchronous data flow. Most reactive programs are comprised of datasources, intermediate computations, and a final result.
|
Reactive programming is a programming model concerned with deriving computations from asynchronous data flow. Most reactive programs are comprised of datasources, intermediate computations, and a final result.
|
||||||
|
|
||||||
|
@ -216,7 +216,7 @@ However, for components that borrow data, it doesn't make sense to implement Par
|
||||||
You can technically override this behavior by implementing the `Props` trait manually, though it's unsafe and easy to mess up:
|
You can technically override this behavior by implementing the `Props` trait manually, though it's unsafe and easy to mess up:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
impl Properties for CustomProps {
|
unsafe impl Properties for CustomProps {
|
||||||
fn memoize(&self, other &Self) -> bool {
|
fn memoize(&self, other &Self) -> bool {
|
||||||
self != other
|
self != other
|
||||||
}
|
}
|
||||||
|
|
1
docs/guide/src/helpers/index.md
Normal file
1
docs/guide/src/helpers/index.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Helper Crates
|
|
@ -1,3 +1,118 @@
|
||||||
# Event handlers
|
# Event handlers
|
||||||
|
|
||||||
> This section is currently under construction! 🏗
|
To make our boring UIs less static and more interesting, we want to add the ability to interact to user input. To do this, we need to add some event handlers.
|
||||||
|
|
||||||
|
|
||||||
|
## The most basic events: clicks
|
||||||
|
|
||||||
|
If you've poked around in the Dioxus examples at all, you've definitely noticed the support for buttons and clicks. To add some basic action when a button is clicked, we need to define a button and then attach an "onclick" handler to it.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx!{
|
||||||
|
button {
|
||||||
|
onclick: move |evt| println!("I've been clicked!"),
|
||||||
|
"click me!"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're using the builder pattern, it's pretty simple too. `onclick` is a method for any builder with HTML elements.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
button(&cx)
|
||||||
|
.onclick(move |evt| println!("I've been clicked!"))
|
||||||
|
.text("click me!")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The event handler is different in Dioxus than other libraries. Event handlers in Dioxus may borrow any data that has a lifetime that matches the component's scope. This means we can save a value with `use_hook` and then use it in our handler.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let val = cx.use_hook(|_| 10);
|
||||||
|
|
||||||
|
button(&cx)
|
||||||
|
.onclick(move |evt| println!("Current number {val}"))
|
||||||
|
.text("click me!")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## The `Event` object
|
||||||
|
|
||||||
|
When the listener is fired, Dioxus will pass in any related data through the `event` parameter. This holds helpful state relevant to the event. For instance, on forms, Dioxus will fill in the "values" field.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// the FormEvent is roughly
|
||||||
|
struct FormEvent {
|
||||||
|
value: String,
|
||||||
|
values: HashMap<String, String>
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx!{
|
||||||
|
form {
|
||||||
|
onsubmit: move |evt| {
|
||||||
|
println!("Values of form are {evt.values:#?}");
|
||||||
|
}
|
||||||
|
input { id: "password", name: "password" }
|
||||||
|
input { id: "username", name: "username" }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Stopping propagation
|
||||||
|
|
||||||
|
With a complex enough UI, you might realize that listeners can actually be nested.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
div {
|
||||||
|
onclick: move |evt| {},
|
||||||
|
"outer",
|
||||||
|
div {
|
||||||
|
onclick: move |evt| {},
|
||||||
|
"inner"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this particular layout, a click on the inner div is transitively also a click on the outer div. If we didn't want the outer div to be triggered every time we trigger the inner div, then we'd want to call "cancel_bubble".
|
||||||
|
|
||||||
|
This will prevent any listeners above the current listener from being triggered.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
div {
|
||||||
|
onclick: move |evt| {},
|
||||||
|
"outer",
|
||||||
|
div {
|
||||||
|
onclick: move |evt| {
|
||||||
|
// now, outer won't be triggered
|
||||||
|
evt.cancel_bubble();
|
||||||
|
},
|
||||||
|
"inner"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prevent Default
|
||||||
|
|
||||||
|
With HTML based renderers, the browser will automatically perform some action. For text inputs, this would be entering the provided key. For forms, this might involve navigating the page.
|
||||||
|
|
||||||
|
In some instances, you don't want this default behavior. In these cases, instead of handling the event directly, you'd want to prevent any default handlers.
|
||||||
|
|
||||||
|
Normally, in React or JavaScript, you'd call "preventDefault" on the event in the callback. Dioxus does *not* currently support this behavior. Instead, you need to add an attribute to the element generating the event.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
form {
|
||||||
|
prevent_default: "onclick",
|
||||||
|
onclick: move |_|{
|
||||||
|
// handle the event without navigating the page.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -8,120 +8,3 @@ Most components you will write in Dioxus will need to store state somehow. For l
|
||||||
Both of these hooks are extremely powerful and flexible, so we've dedicated this section to understanding them properly.
|
Both of these hooks are extremely powerful and flexible, so we've dedicated this section to understanding them properly.
|
||||||
|
|
||||||
> These two hooks are not the only way to store state. You can always build your own hooks!
|
> These two hooks are not the only way to store state. You can always build your own hooks!
|
||||||
|
|
||||||
## `use_state`
|
|
||||||
|
|
||||||
The `use_state` hook is very similar to its React counterpart. When we use it, we get two values:
|
|
||||||
|
|
||||||
- The value itself as an `&T`
|
|
||||||
- A handle to set the value `&UseState<T>`
|
|
||||||
|
|
||||||
```rust
|
|
||||||
let (count, set_count) = use_state(&cx, || 0);
|
|
||||||
|
|
||||||
// then to set the count
|
|
||||||
set_count(count + 1)
|
|
||||||
```
|
|
||||||
|
|
||||||
However, the `set_count` object is more powerful than it looks. You can use it as a closure, but you can also call methods on it to do more powerful operations.
|
|
||||||
|
|
||||||
For instance, we can get a handle to the current value at any time:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
let current: Rc<T> = set_count.current();
|
|
||||||
```
|
|
||||||
|
|
||||||
Or, we can get the inner handle to set the value
|
|
||||||
|
|
||||||
```rust
|
|
||||||
let setter: Rc<dyn Fn(T)> = set_count.setter();
|
|
||||||
```
|
|
||||||
|
|
||||||
Or, we can set a new value using the current value as a reference:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
set_count.modify(|i| i + 1);
|
|
||||||
```
|
|
||||||
|
|
||||||
If the value is cheaply cloneable, then we can call `with_mut` to get a mutable reference to the value:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
set_count.with_mut(|i| *i += 1);
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, we can call `make_mut` which gives us a `RefMut` to the underlying value:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
*set_count.make_mut() += 1;
|
|
||||||
```
|
|
||||||
|
|
||||||
Plus, the `set_count` handle is cloneable into async contexts, so we can use it in futures.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// count up infinitely
|
|
||||||
cx.spawn({
|
|
||||||
to_owned![set_count];
|
|
||||||
async move {
|
|
||||||
loop {
|
|
||||||
wait_ms(100).await;
|
|
||||||
set_count.modify(|i| i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## `use_ref`
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
# Lifecycle, updates, and effects
|
# Effects
|
||||||
|
|
||||||
|
In theory, your UI code should be side-effect free. Whenever a component renders, all of its state should be prepared ahead of time. In reality, we often need to perform some sort of side effect. Possible effects include:
|
||||||
|
|
||||||
|
- Logging some data
|
||||||
|
- Pre-fetching some data
|
||||||
|
- Attaching code to native elements
|
||||||
|
- Cleaning up
|
||||||
|
|
||||||
|
This section is organized under interactivity because effects can be important to add things like transitions, videos, and other important media.
|
||||||
|
|
||||||
> This section is currently under construction! 🏗
|
|
||||||
|
|
|
@ -1,6 +1,84 @@
|
||||||
# User Input and Controlled Components
|
# User Input and Controlled Components
|
||||||
|
|
||||||
Handling user input is one of the most common things your app will do, but it can be tricky
|
Handling user input is one of the most common things your app will do, but it *can* be tricky.
|
||||||
|
|
||||||
|
The reactive paradigm and one-way-data-flow models were all derived to solve problems that can crop up around user input handling. This section will teach you about two effective patterns for handling user input: controlled and uncontrolled inputs.
|
||||||
|
|
||||||
|
## Controlled Inputs
|
||||||
|
|
||||||
|
The most common approach to handling input from elements is through "controlled" inputs. With this pattern, we drive the value of the input from our state, while simultaneously updating our state from new values.
|
||||||
|
|
||||||
|
This is the most common form of input handling and is widely used because it prevents the UI from being out of sync with your local state.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let name = use_state(&cx, || "bob".to_string());
|
||||||
|
|
||||||
|
cx.render(rsx!{
|
||||||
|
input {
|
||||||
|
value: "{name}",
|
||||||
|
oninput: move |evt| name.set(evt.value.clone()),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Why not just "bind" like in other frameworks?
|
||||||
|
|
||||||
|
In a handful of cases, you don't want the inputted value to match what's rendered to the screen. Let's say we want to implement an input that converts the input to uppercase when the input matches a certain value. With binding, we're forced to share the same input and state value. By explicitly handling the oninput case, we're given the opportunity to set a *new* value.
|
||||||
|
|
||||||
|
|
||||||
> This section is currently under construction! 🏗
|
```rust
|
||||||
|
let name = use_state(&cx, || "bob".to_string());
|
||||||
|
|
||||||
|
cx.render(rsx!{
|
||||||
|
input {
|
||||||
|
value: "{name}",
|
||||||
|
oninput: move |evt| {
|
||||||
|
if evt.value == "tim" {
|
||||||
|
name.set("TIM");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Binding
|
||||||
|
|
||||||
|
>! Note - binding is currently not implemented in Dioxus. This section represents a future in-progress feature.
|
||||||
|
|
||||||
|
Because the above pattern is so common, we have an additional attribute called "bind" which is essentially a shorthand for our code above.
|
||||||
|
|
||||||
|
Bind just connects an oninput to a `UseState` and is implemented through the signal system.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let name = use_state(&cx, || "bob".to_string());
|
||||||
|
|
||||||
|
cx.render(rsx!{
|
||||||
|
input { bind: name }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uncontrolled Inputs
|
||||||
|
|
||||||
|
When working with large sets of inputs, you might be quickly tired of creating `use_state` for each value. Additionally, the pattern of one use_state per interaction might deteriorate when you need to have a flexible number of inputs. In these cases, we use "uncontrolled" inputs. Here, we don't drive the value of the input from the use_state, choosing to leave it in an "uncontrolled" state.
|
||||||
|
|
||||||
|
This approach can be more performant, more flexible, but more prone to UI inconsistencies than its controlled counterpart.
|
||||||
|
|
||||||
|
To use the "uncontrolled" pattern, we simply omit setting the value of the input. Instead, we can react to the change directly on the input itself, or from a form element higher up in the tree.
|
||||||
|
|
||||||
|
|
||||||
|
For this example, we don't attach any `use_state` handles into the labels. Instead, we simply attach an "oninput" handler to the form element. This will run each time any of the child inputs change, allowing us to perform tasks like form validation.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
form {
|
||||||
|
oninput: move |evt| {
|
||||||
|
if !validate_input(evt.values) {
|
||||||
|
// display what errors validation resulted in
|
||||||
|
}
|
||||||
|
},
|
||||||
|
input { name: "name", }
|
||||||
|
input { name: "age", }
|
||||||
|
input { name: "date", }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
154
docs/guide/src/interactivity/useref.md
Normal file
154
docs/guide/src/interactivity/useref.md
Normal 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(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
114
docs/guide/src/interactivity/usestate.md
Normal file
114
docs/guide/src/interactivity/usestate.md
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
# Fundamental hook: `use_state`
|
||||||
|
|
||||||
|
The first fundamental hook for state management is `use_state`. This particular hook is designed to work well with the entire Dioxus ecosystem including futures, children, and memoization.
|
||||||
|
|
||||||
|
|
||||||
|
## Basic usage.
|
||||||
|
|
||||||
|
The simplest use case of `use_state` is a simple counter. The handle returned by `use_state` implements `Add` and `AddAssign`. Writing through `AddAssign` will automatically mark the component as dirty, forcing an update.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let count = use_state(&cx, || 0);
|
||||||
|
|
||||||
|
rsx!(cx, button { onclick: move |_| count += 1, })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
### Common ops
|
||||||
|
|
||||||
|
The `use_state` hook is very similar to its React counterpart. When we use it, we get a smart pointer - essentially an `Rc<T>` that tracks when we update it.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mut count = use_state(&cx, || 0);
|
||||||
|
|
||||||
|
// then to set the count
|
||||||
|
count += 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Current value
|
||||||
|
|
||||||
|
Our `count` value is more than just an integer. Because it's a smart pointer, it also implements other useful methods helpful in various contexts.
|
||||||
|
|
||||||
|
For instance, we can get a handle to the current value at any time:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let current: Rc<T> = count.current();
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, we can get the inner handle to set the value:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let setter: Rc<dyn Fn(T)> = count.setter();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Modify
|
||||||
|
|
||||||
|
Or, we can set a new value using the current value as a reference:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
count.modify(|i| i + 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
### `with_mut` and `make_mut`
|
||||||
|
|
||||||
|
If the value is cheaply cloneable, then we can call `with_mut` to get a mutable reference to the value:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
count.with_mut(|i| *i += 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, we can call `make_mut` which gives us a `RefMut` to the underlying value:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
*count.make_mut() += 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use in Async
|
||||||
|
|
||||||
|
Plus, the `set_count` handle is cloneable into async contexts, so we can use it in futures.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// count up infinitely
|
||||||
|
cx.spawn({
|
||||||
|
let count = count.clone();
|
||||||
|
async move {
|
||||||
|
loop {
|
||||||
|
wait_ms(100).await;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using UseState for simple data
|
||||||
|
|
||||||
|
The best use case of `use_state` is to manage "simple" data local to your component. This should be things like numbers, strings, small maps, and cheaply-clonable types.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let val = use_state(&cx, || 0);
|
||||||
|
let val = use_state(&cx, || "Hello!");
|
||||||
|
let val = use_state(&cx, || vec![1, 3, 3, 7]);
|
||||||
|
```
|
||||||
|
|
||||||
|
UseState can be sent down into child components too.
|
||||||
|
|
||||||
|
You can either pass by reference to always force the child to update when the value changes, or you can clone the handle to take advantage of automatic memoization. In these cases, calling "get" will return stale data - always prefer "current" when "cloning" the UseState. This automatic memoization of UseState solves a performance problem common in React.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let val = use_state(&cx, || 0);
|
||||||
|
|
||||||
|
cx.render(rsx!{
|
||||||
|
Child { val: val.clone() }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Child(cx: Scope, val: UseState<i32>) -> Element {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
1
docs/guide/src/state/channels.md
Normal file
1
docs/guide/src/state/channels.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Channels
|
61
docs/guide/src/state/fanout.md
Normal file
61
docs/guide/src/state/fanout.md
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
# Fanning Out
|
||||||
|
|
||||||
|
One of the most reliable state management patterns in large Dioxus apps is `fan-out`. The fan-out pattern is the ideal way to structure your app to maximize code reuse, testability, and deterministic rendering.
|
||||||
|
|
||||||
|
## The structure
|
||||||
|
|
||||||
|
With `fan-out`, our individual components at "leaf" position of our VirtualDom are "pure", making them easily reusable, testable, and deterministic. For instance, the "title" bar of our app might be a fairly complicated component.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Props, PartialEq)]
|
||||||
|
struct TitlebarProps {
|
||||||
|
title: String,
|
||||||
|
subtitle: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Titlebar(cx: Scope<TitlebarProps>) -> Element {
|
||||||
|
cx.render(rsx!{
|
||||||
|
div {
|
||||||
|
class: "titlebar"
|
||||||
|
h1 { "{cx.props.title}" }
|
||||||
|
h1 { "{cx.props.subtitle}" }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If we used global state like use_context or fermi, we might be tempted to inject our `use_read` directly into the component.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn Titlebar(cx: Scope<TitlebarProps>) -> Element {
|
||||||
|
let title = use_read(&cx, TITLE);
|
||||||
|
let subtitle = use_read(&cx, SUBTITLE);
|
||||||
|
|
||||||
|
cx.render(rsx!{/* ui */})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For many apps - this is a fine pattern, especially if the component is a one-off. However, if we want to reuse the component outside of this app, then we'll start to run into issues where our contexts are unavailable.
|
||||||
|
|
||||||
|
## Fanning Out
|
||||||
|
|
||||||
|
To enable our titlebar component to be used across apps, we want to lift our atoms upwards and out of the Titlebar component. We would organize a bunch of other components in this section of the app to share some of the same state.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn DocsiteTitlesection(cx: Scope) {
|
||||||
|
let title = use_read(&cx, TITLE);
|
||||||
|
let subtitle = use_read(&cx, SUBTITLE);
|
||||||
|
|
||||||
|
let username = use_read(&cx, USERNAME);
|
||||||
|
let points = use_read(&cx, POINTS);
|
||||||
|
|
||||||
|
cx.render(rsx!{
|
||||||
|
TitleBar { title: title, subtitle: subtitle }
|
||||||
|
UserBar { username: username, points: points }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This particular wrapper component unfortunately cannot be reused across apps. However, because it simply wraps other real elements, it doesn't have to. We are free to reuse our TitleBar and UserBar components across apps with ease. We also know that this particular component is plenty performant because the wrapper doesn't have any props and is always memoized. The only times this component re-renders is when any of the atoms change.
|
||||||
|
|
||||||
|
This is the beauty of Dioxus - we always know where our components are likely to be re-rendered. Our wrapper components can easily prevent any large re-renders by simply memoizing their components. This system might not be as elegant or precise as signal systems found in libraries like Sycamore or SolidJS, but it is quite ergonomic.
|
110
docs/guide/src/state/fermi.md
Normal file
110
docs/guide/src/state/fermi.md
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
# Fermi
|
||||||
|
|
||||||
|
After having covered local and global state, you're definitely ready to start building some complex Dioxus apps. Before you get too far, check out the Fermi crate. Fermi makes it dead-easy to manage global state and scales to the largest of apps.
|
||||||
|
|
||||||
|
## What is Fermi for?
|
||||||
|
|
||||||
|
If you're building an app that has scaled past a few small components, then it'll be worth sketching out and organizing your app's state. Fermi makes it easy to transition from a simple app that relies on `use_state` to an app with dozens of components.
|
||||||
|
|
||||||
|
> To put it simply - Fermi is the ideal crate for managing global state in your Dioxus app.
|
||||||
|
|
||||||
|
|
||||||
|
## How do I use it?
|
||||||
|
|
||||||
|
To add fermi to your project, simply add the "fermi" feature to your Dioxus dependency.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
dioxus = { version = "0.2", features = ["desktop", "fermi"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
Fermi is built on the concept of "atoms" of global state. Instead of bundling all our state together in a single mega struct, we actually chose to implement it piece-by-piece with functions.
|
||||||
|
|
||||||
|
Each piece of global state in your app is represented by an "atom."
|
||||||
|
|
||||||
|
```rust
|
||||||
|
static TITLE: Atom<String> = |_| "Defualt Title".to_string();
|
||||||
|
```
|
||||||
|
|
||||||
|
This atom can be then used the with the `use_atom` hook as a drop-in replacement for `use_state`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
static TITLE: Atom<String> = |_| "Defualt Title".to_string();
|
||||||
|
|
||||||
|
fn Title(cx: Scope) -> Element {
|
||||||
|
let title = use_atom(&cx, TITLE);
|
||||||
|
|
||||||
|
cx.render(rsx!{
|
||||||
|
button { onclick: move |_| title.set("new title".to_string()) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
However, Fermi really becomes useful when we want to share the value between two different components.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
static TITLE: Atom<String> = |_| "Defualt Title".to_string();
|
||||||
|
|
||||||
|
fn TitleBar(cx: Scope) -> Element {
|
||||||
|
let title = use_atom(&cx, TITLE);
|
||||||
|
|
||||||
|
rsx!{cx, button { onclick: move |_| title.set("titlebar title".to_string()) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn TitleCard(cx: Scope) -> Element {
|
||||||
|
let title = use_atom(&cx, TITLE);
|
||||||
|
|
||||||
|
rsx!{cx, button { onclick: move |_| title.set("title card".to_string()) } }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These two components can get and set the same value!
|
||||||
|
|
||||||
|
|
||||||
|
## Use with collections
|
||||||
|
|
||||||
|
Fermi gets *really* powerful when used to manage collections of data. Under the hood, Fermi uses immutable collections and tracks reads and writes of individual keys. This makes it easy to implement things like lists and infinite posts with little performance penalty. It also makes it really easy to refactor our app and add new fields.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
static NAMES: AtomRef<Uuid, String> = |builder| {};
|
||||||
|
static CHECKED: AtomRef<Uuid, bool> = |builder| {};
|
||||||
|
static CREATED: AtomRef<Uuid, Instant> = |builder| {};
|
||||||
|
```
|
||||||
|
|
||||||
|
To use these collections:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[inline_props]
|
||||||
|
fn Todo(cx: Scope, id: Uuid) -> Element {
|
||||||
|
let name = use_atom(&cx, NAMES.select(id));
|
||||||
|
let checked = use_atom(&cx, CHECKED.select(id));
|
||||||
|
let created = use_atom(&cx, CREATED.select(id));
|
||||||
|
|
||||||
|
// or
|
||||||
|
|
||||||
|
let (name, checked, created) = use_atom(&cx, (NAMES, CHECKED, CREATED).select(id));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This particular pattern might seem strange at first - "why isn't all of our state under one struct?" - but eventually shows its worth when dealing with large amounts of data. By composing collections together, we can get get the perks of the Entity-Component-System architecture in our Dioxus apps. Performance is quite predictable here and easy to trace.
|
||||||
|
|
||||||
|
## AtomRef
|
||||||
|
|
||||||
|
Much like `use_ref` can be used to manage complex state locally, `AtomRef` can be used to manage complex global state. `AtomRef` is basically a global `Rc<RefCell<T>>` with mutation tracking.
|
||||||
|
|
||||||
|
It too serves as a basic replacement for `use_ref`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn Todo(cx: Scope) -> Element {
|
||||||
|
let cfg = use_atom_ref(&cx, CFG);
|
||||||
|
|
||||||
|
cfg.write().enable_option();
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future Reading
|
||||||
|
|
||||||
|
This guide is meant just to be an overview of Fermi. This page is just a short advertisement for what we consider the best state management solution available today for Dioxus apps.
|
||||||
|
|
||||||
|
For further reading, check out the [crate itself](https://github.com/DioxusLabs/dioxus/tree/master/packages/fermi)!
|
|
@ -2,27 +2,38 @@
|
||||||
|
|
||||||
Every app you'll build with Dioxus will have some sort of state that needs to be maintained and updated as your users interact with it. However, managing state can be particular challenging at times, and is frequently the source of bugs in many GUI frameworks.
|
Every app you'll build with Dioxus will have some sort of state that needs to be maintained and updated as your users interact with it. However, managing state can be particular challenging at times, and is frequently the source of bugs in many GUI frameworks.
|
||||||
|
|
||||||
In this chapter, we'll cover the various ways to manage state, the appropriate terminology, various patterns, and then take an overview
|
In this chapter, we'll cover the various ways to manage state, the appropriate terminology, various patterns, and some problems you might run into.
|
||||||
|
|
||||||
## Terminology
|
|
||||||
|
|
||||||
|
|
||||||
> This section is currently under construction! 🏗
|
## The Problem
|
||||||
|
|
||||||
|
Why do people say state management is so difficult? What does it mean?
|
||||||
|
|
||||||
|
Generally, state management is the code you need to write to ensure that your app renders the *correct* content. If the user inputs a name, then you need to display the appropriate response - like alerts, validation, and disable/enable various elements on the page. Things can quickly become tricky if you need loading screens and cancellable tasks.
|
||||||
|
|
||||||
|
For the simplest of apps, all your state can enter the app from the root props. This is common in server-side rendering - we can collect all of the required state *before* rendering the content.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let all_content = get_all_content().await;
|
||||||
|
|
||||||
|
let output = dioxus::ssr::render_lazy(rsx!{
|
||||||
|
div {
|
||||||
|
RenderContent { content: all_content }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
With this incredibly simple setup, it is highly unlikely that you'll have rendering bugs. There simply is barely any state to manage.
|
||||||
|
|
||||||
|
However, most of your apps will store state inside of the Dioxus VirtualDom - either through local state or global state.
|
||||||
|
|
||||||
|
|
||||||
|
## Your options
|
||||||
|
|
||||||
|
To deal with complexity, you have a couple of options:
|
||||||
|
|
||||||
## Important hook: `use_state`
|
- Refactor state out of shared state and into reusable components and hooks.
|
||||||
|
- Lift state upwards to be spread across multiple components (fan out).
|
||||||
|
- Use the Context API to share state globally.
|
||||||
|
- Use a dedicated state management solution like Fermi.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Important hook: `use_ref`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## `provide_context` and `consume_context`
|
|
||||||
|
|
||||||
## Terminology
|
|
||||||
|
|
|
@ -1,4 +1,62 @@
|
||||||
# Local State
|
# Local State
|
||||||
|
|
||||||
|
The first step to dealing with complexity in your app is to refactor your state to be purely local. This encourages good code reuse and prevents leakage of abstractions across component boundaries.
|
||||||
|
|
||||||
> This section is currently under construction! 🏗
|
## What it looks like
|
||||||
|
|
||||||
|
Let's say we're managing the state for a list of Todos. Whenever we edit the todo, we want the list to update. We might've started building our app but putting everything into a single `use_ref`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct Todo {
|
||||||
|
contents: String,
|
||||||
|
is_hovered: bool,
|
||||||
|
is_editing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
let todos = use_ref(&cx, || vec![Todo::new()]);
|
||||||
|
|
||||||
|
cx.render(rsx!{
|
||||||
|
ul {
|
||||||
|
todos.read().iter().enumerate().map(|(id, todo)| rsx!{
|
||||||
|
li {
|
||||||
|
h1 { "{todo.contents}" }
|
||||||
|
onhover: move |_| *todos.write()[id].is_hovered = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
As shown above, whenever the todo is hovered, we want to set its state:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
todos.write()[0].is_hovered = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
As the amount of interactions goes up, so does the complexity of our state. Should the "hover" state really be baked into our data model?
|
||||||
|
|
||||||
|
Instead, let's refactor our Todo component to handle its own state:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[inline_props]
|
||||||
|
fn Todo<'a>(cx: Scope, todo: &'a Todo) -> Element {
|
||||||
|
let is_hovered = use_state(&cx, || false);
|
||||||
|
|
||||||
|
cx.render(rsx!{
|
||||||
|
li {
|
||||||
|
h1 { "{todo.contents}" }
|
||||||
|
onhover: move |_| is_hovered.set(true),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, we can simplify our Todo data model to get rid of local UI state:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct Todo {
|
||||||
|
contents: String,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is not only better for encapsulation and abstraction, but it's only more performant! Whenever a Todo is hovered, we won't need to re-render *every* Todo again - only the Todo that's currently being hovered.
|
||||||
|
|
86
docs/guide/src/state/router.md
Normal file
86
docs/guide/src/state/router.md
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
# Router
|
||||||
|
|
||||||
|
In many of your apps, you'll want to have different "scenes". For a webpage, these scenes might be the different webpages with their own content.
|
||||||
|
|
||||||
|
You could write your own scene management solution - quite simply too. However, to save you the effort, Dioxus supports a first-party solution for scene management called Dioxus Router.
|
||||||
|
|
||||||
|
|
||||||
|
## What is it?
|
||||||
|
|
||||||
|
For an app like the Dioxus landing page (https://dioxuslabs.com), we want to have different pages. A quick sketch of an app would be something like:
|
||||||
|
|
||||||
|
- Homepage
|
||||||
|
- Blog
|
||||||
|
- Example showcase
|
||||||
|
|
||||||
|
Each of these scenes is independent - we don't want to render both the homepage and blog at the same time.
|
||||||
|
|
||||||
|
This is where the router crates come in handy. To make sure we're using the router, simply add the `"router"` feature to your dioxus dependency.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
dioxus = { version = "0.2", features = ["desktop", "router"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Using the router
|
||||||
|
|
||||||
|
Unlike other routers in the Rust ecosystem, our router is built declaratively. This makes it possible to compose our app layout simply by arranging components.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
rsx!{
|
||||||
|
Router {
|
||||||
|
Route { to: "/home", Home {} }
|
||||||
|
Route { to: "/blog", Blog {} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Whenever we visit this app, we will get either the Home component or the Blog component rendered depending on which route we enter at. If neither of these routes match the current location, then nothing will render.
|
||||||
|
|
||||||
|
We can fix this one of two ways:
|
||||||
|
|
||||||
|
- A fallback 404 page
|
||||||
|
-
|
||||||
|
```rust
|
||||||
|
rsx!{
|
||||||
|
Router {
|
||||||
|
Route { to: "/home", Home {} }
|
||||||
|
Route { to: "/blog", Blog {} }
|
||||||
|
Route { to: "", NotFound {} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
- Redirect 404 to home
|
||||||
|
|
||||||
|
```rust
|
||||||
|
rsx!{
|
||||||
|
Router {
|
||||||
|
Route { to: "/home", Home {} }
|
||||||
|
Route { to: "/blog", Blog {} }
|
||||||
|
Redirect { from: "", to: "/home" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
For our app to navigate these routes, we can provide clickable elements called Links. These simply wrap `<a>` elements that, when clicked, navigate the app to the given location.
|
||||||
|
|
||||||
|
|
||||||
|
```rust
|
||||||
|
rsx!{
|
||||||
|
Link {
|
||||||
|
to: "/home",
|
||||||
|
"Go home!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## More reading
|
||||||
|
|
||||||
|
This page is just meant to be a very brief overview of the router to show you that there's a powerful solution already built for a very common problem. For more information about the router, definitely check out its book or check out some of the examples.
|
||||||
|
|
||||||
|
The router has its own documentation! [Available here](https://dioxuslabs.com/router_guide/).
|
|
@ -74,9 +74,9 @@ fn app(cx: Scope) -> Element {
|
||||||
class: "pure-button pure-button-primary",
|
class: "pure-button pure-button-primary",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
clients.write().push(Client {
|
clients.write().push(Client {
|
||||||
description: (**description).clone(),
|
description: description.to_string(),
|
||||||
first_name: (**firstname).clone(),
|
first_name: firstname.to_string(),
|
||||||
last_name: (**lastname).clone(),
|
last_name: lastname.to_string(),
|
||||||
});
|
});
|
||||||
description.set(String::new());
|
description.set(String::new());
|
||||||
firstname.set(String::new());
|
firstname.set(String::new());
|
||||||
|
|
|
@ -13,11 +13,11 @@ fn app(cx: Scope) -> Element {
|
||||||
let count = use_state(&cx, || 0);
|
let count = use_state(&cx, || 0);
|
||||||
|
|
||||||
use_future(&cx, (), move |_| {
|
use_future(&cx, (), move |_| {
|
||||||
let count = count.clone();
|
let mut count = count.clone();
|
||||||
async move {
|
async move {
|
||||||
loop {
|
loop {
|
||||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||||
count.modify(|f| f + 1);
|
count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,9 +4,9 @@ Render the Dioxus VirtualDom using the platform's native WebView implementation.
|
||||||
|
|
||||||
# Desktop
|
# Desktop
|
||||||
|
|
||||||
One of Dioxus' killer features is the ability to quickly build a native desktop app that looks and feels the same across platforms. Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory.
|
One of Dioxus' flagship features is the ability to quickly build a native desktop app that looks and feels the same across platforms. Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory.
|
||||||
|
|
||||||
Dioxus Desktop is built off Tauri. Right now there aren't any Dioxus abstractions over keyboard shortcuts, menubar, handling, etc, so you'll want to leverage Tauri - mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly. The next major release of Dioxus-Desktop will include components and hooks for notifications, global shortcuts, menubar, etc.
|
Dioxus Desktop is built off Tauri. Right now there aren't any Dioxus abstractions over keyboard shortcuts, menubar, handling, etc, so you'll want to leverage Tauri - mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao) directly. An upcoming release of Dioxus-Desktop will include components and hooks for notifications, global shortcuts, menubar, etc.
|
||||||
|
|
||||||
|
|
||||||
## Getting Set up
|
## Getting Set up
|
||||||
|
|
|
@ -414,6 +414,30 @@ impl<T: Div<Output = T> + Copy> std::ops::DivAssign<T> for &UseState<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Add<Output = T> + Copy> std::ops::AddAssign<T> for UseState<T> {
|
||||||
|
fn add_assign(&mut self, rhs: T) {
|
||||||
|
self.set((*self.current()) + rhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Sub<Output = T> + Copy> std::ops::SubAssign<T> for UseState<T> {
|
||||||
|
fn sub_assign(&mut self, rhs: T) {
|
||||||
|
self.set((*self.current()) - rhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Mul<Output = T> + Copy> std::ops::MulAssign<T> for UseState<T> {
|
||||||
|
fn mul_assign(&mut self, rhs: T) {
|
||||||
|
self.set((*self.current()) * rhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Div<Output = T> + Copy> std::ops::DivAssign<T> for UseState<T> {
|
||||||
|
fn div_assign(&mut self, rhs: T) {
|
||||||
|
self.set((*self.current()) / rhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn api_makes_sense() {
|
fn api_makes_sense() {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
|
Loading…
Reference in a new issue