docs: even more docs

This commit is contained in:
Jonathan Kelley 2022-03-06 20:37:57 -05:00
parent a4ab2d9de0
commit 0f87ebb277
25 changed files with 986 additions and 174 deletions

View file

@ -15,26 +15,39 @@
- [Children and Attributes](components/component_children.md)
- [How Data Flows](components/composing.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)
- [User Input and Controlled Components](interactivity/user_input.md)
- [Lifecycle, updates, and effects](interactivity/lifecycles.md)
- [Hooks](interactivity/hooks.md)
- [UseState](interactivity/usestate.md)
- [UseRef](interactivity/useref.md)
- [User Input](interactivity/user_input.md)
<!-- - [Effects](interactivity/lifecycles.md) -->
- [Managing State](state/index.md)
- [Local State](state/localstate.md)
- [Lifting State](state/liftingstate.md)
- [Global State](state/sharedstate.md)
- [Fanning Out](state/fanout.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)
- [Tasks](async/asynctasks.md)
- [Updating State](async/loading_state.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)
- [New app](tutorial/new_app.md)
- [Structuring our app](tutorial/structure.md)
- [Defining State](tutorial/state.md)
- [Defining Components](tutorial/components.md)
- [Styling](tutorial/styling.md)
- [Bundling](tutorial/publishing.md)
- [Bundling](tutorial/publishing.md) -->
- [Next Steps and Advanced Topics](final.md)

View file

@ -0,0 +1 @@
# Coroutines

View file

@ -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.
## 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
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! 🏗

View 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

View file

@ -0,0 +1 @@
# WebSockets

View file

@ -0,0 +1 @@
# UseFuture

View file

@ -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
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.

View file

@ -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.
@ -15,7 +15,7 @@ This section is a bit long, but worth the read. We recommend coffee, tea, and/or
## 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.
@ -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:
```rust
impl Properties for CustomProps {
unsafe impl Properties for CustomProps {
fn memoize(&self, other &Self) -> bool {
self != other
}

View file

@ -0,0 +1 @@
# Helper Crates

View file

@ -1,3 +1,118 @@
# 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.
}
}
```

View file

@ -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.
> 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");
}
}
})
```

View file

@ -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! 🏗

View file

@ -1,6 +1,84 @@
# 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", }
}
```

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(),
}
})
```

View 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 {
// ...
}
```

View file

@ -0,0 +1 @@
# Channels

View 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.

View 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)!

View file

@ -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.
In this chapter, we'll cover the various ways to manage state, the appropriate terminology, various patterns, and then take an overview
## Terminology
In this chapter, we'll cover the various ways to manage state, the appropriate terminology, various patterns, and some problems you might run into.
> 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

View file

@ -1,4 +1,62 @@
# 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.

View 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/).

View file

@ -74,9 +74,9 @@ fn app(cx: Scope) -> Element {
class: "pure-button pure-button-primary",
onclick: move |_| {
clients.write().push(Client {
description: (**description).clone(),
first_name: (**firstname).clone(),
last_name: (**lastname).clone(),
description: description.to_string(),
first_name: firstname.to_string(),
last_name: lastname.to_string(),
});
description.set(String::new());
firstname.set(String::new());

View file

@ -13,11 +13,11 @@ fn app(cx: Scope) -> Element {
let count = use_state(&cx, || 0);
use_future(&cx, (), move |_| {
let count = count.clone();
let mut count = count.clone();
async move {
loop {
tokio::time::sleep(Duration::from_millis(1000)).await;
count.modify(|f| f + 1);
count += 1;
}
}
});

View file

@ -4,9 +4,9 @@ Render the Dioxus VirtualDom using the platform's native WebView implementation.
# 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

View file

@ -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]
fn api_makes_sense() {
#[allow(unused)]