Merge pull request #295 from DioxusLabs/jk/docsoverhaul
Docs: actually finish all of the documentation.
This commit is contained in:
25 files changed
+986
-174
No files matched your search
@@ -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.
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
@@ -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
|
||||
|
||||
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", }
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
@@ -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 {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
Reference in new issue
Block a user