Improve inline docs (#2460)

Improve inline docs

* improve incorrect event handler return error message

* Improve event handler docs

* document the eval functions

* document spawn and common spawn errors

* fix event handler docs

* add notes about how you use attributes and elements in rsx

* add doc aliases for attributes and events we rename

* add some more aliases for common search terms

* don't doc ignore any public examples in core

* don't ignore public doc examples in ssr

* don't ignore examples in the dioxus package readme

* add a warning when you launch without a renderer enabled

* fix some outdated element docs

* add a bunch of examples to resource

* add notes about desktop events

* add more docs for use_resource

* add on_unimplemented hint to Dependency

* fix some unresolved links

* add examples to each of the router traits

* add not implemented errors for router traits

* add an example to the routable trait

* expand rsx macro docs

* improve memo docs

* update the dioxus readme

* mention dioxus crate features in the docs

* fix a bunch of doc tests

* fix html doc tests

* fix router doc tests

* fix dioxus signals doc tests

* fix dioxus ssr doc tests

* fix use_future example in the hooks cheat sheet

* add a javascript alias for eval

* fix hook explanation values

* remove unused embed-doc-image dependency
This commit is contained in:
Evan Almloff authored and GitHub committed 2024-06-06 18:15:17 -07:00
1 parent 054351139f
commit 0127501dbf
71 files changed
+4132 -1162

No files matched your search

+147
View File
@@ -0,0 +1,147 @@
Memos are the result of computing a value from `use_memo`.
You may have noticed that this struct doesn't have many methods. Most methods for `Memo` are defined on the [`Readable`] and [`Writable`] traits.
# Reading a Memo
You can use the methods on the `Readable` trait to read a memo:
```rust, no_run
# use dioxus::prelude::*;
fn app() -> Element {
let mut count = use_signal(|| 0);
// The memo will rerun any time we write to the count signal
let halved = use_memo(move || count() / 2);
rsx! {
// When we read the value of memo, the current component will subscribe to the result of the memo. It will only rerun when the result of the memo changes.
"{halved}"
button {
onclick: move |_| {
count += 1;
},
"Increment"
}
}
}
```
Memo also includes helper methods just like [`Signal`]s to make it easier to use. Calling a memo like a function will clone the inner value:
```rust, no_run
# use dioxus::prelude::*;
fn app() -> Element {
let mut count = use_signal(|| 0);
// The memo will rerun any time we write to the count signal
let halved = use_memo(move || count() / 2);
// This will rerun any time the halved value changes
let doubled = use_memo(move || 2 * halved());
rsx! {
"{doubled}"
button {
onclick: move |_| {
count += 1;
},
"Increment"
}
}
}
```
For a full list of all the helpers available, check out the [`Readable`], [`ReadableVecExt`], and [`ReadableOptionExt`] traits.
# Memos with Async
Because Memos check borrows at runtime, you need to be careful when reading memos inside of async code. If you hold a read of a memo over an await point, that read may still be open when the memo reruns which will cause a panic:
```rust, no_run
# use dioxus::prelude::*;
# async fn sleep(delay: u32) {}
async fn double_me_async(value: &u32) -> u32 {
sleep(100).await;
*value * 2
}
let mut signal = use_signal(|| 0);
let halved = use_memo(move || signal() / 2);
let doubled = use_resource(move || async move {
// Don't hold reads over await points
let halved = halved.read();
// While the future is waiting for the async work to finish, the read will be open
double_me_async(&halved).await
});
rsx!{
"{doubled:?}"
button {
onclick: move |_| {
// When you write to signal, it will cause the memo to rerun which may panic because you are holding a read of the memo over an await point
signal += 1;
},
"Increment"
}
};
```
Instead of holding a read over an await point, you can clone whatever values you need out of your memo:
```rust, no_run
# use dioxus::prelude::*;
# async fn sleep(delay: u32) {}
async fn double_me_async(value: u32) -> u32 {
sleep(100).await;
value * 2
}
let mut signal = use_signal(|| 0);
let halved = use_memo(move || signal() / 2);
let doubled = use_resource(move || async move {
// Calling the memo will clone the inner value
let halved = halved();
double_me_async(halved).await;
});
rsx!{
"{doubled:?}"
button {
onclick: move |_| {
signal += 1;
},
"Increment"
}
};
```
# Memo lifecycle
Memos are implemented with [generational-box](https://crates.io/crates/generational-box) which makes all values Copy even if the inner value is not Copy.
This is incredibly convenient for UI development, but it does come with some tradeoffs. The lifetime of the memo is tied to the lifetime of the component it was created in. If you drop the component that created the memo, the memo will be dropped as well. You might run into this if you try to pass a memo from a child component to a parent component and drop the child component. To avoid this you can create your memo higher up in your component tree, or use global memos.
TLDR **Don't pass memos up in the component tree**. It will cause issues:
```rust
# use dioxus::prelude::*;
fn MyComponent() -> Element {
let child_signal = use_signal(|| None);
rsx! {
IncrementButton {
child_signal
}
}
}
#[component]
fn IncrementButton(mut child_signal: Signal<Option<Memo<i32>>>) -> Element {
let signal_owned_by_child = use_signal(|| 0);
let memo_owned_by_child = use_memo(move || signal_owned_by_child() * 2);
// Don't do this: it may cause issues if you drop the child component
child_signal.set(Some(memo_owned_by_child));
todo!()
}
```
+112
View File
@@ -0,0 +1,112 @@
# Reactivity
The core of dioxus relies on the concept of reactivity. Reactivity is the system that updates your app when state changes.
There are two parts to reactivity: **Reactive Contexts** and **Tracked Values**.
## Reactive Contexts
Reactive Contexts keep track of what state different parts of your app rely on. Reactive Context show up throughout dioxus: Component, use_effect, use_memo and use_resource all have their own reactive contexts:
```rust, no_run
# use dioxus::prelude::*;
let count = use_signal(|| 0);
// The reactive context in the memo knows that the memo depends on the count signal
use_memo(move || count() * 2);
```
## Tracked Values
Tracked values are values that reactive contexts know about. When you read a tracked value, the reactive context will rerun when the value changes. Signals, Memos, and Resources are all tracked values:
```rust, no_run
# use dioxus::prelude::*;
// The count signal is tracked
let count = use_signal(|| 0);
// When you read the count signal, the reactive context subscribes to the count signal
let double_count = use_memo(move || count() * 2);
```
## Reactivity
Reactivity is the system that combines reactive context and tracked values to update your app when state changes.
You can use reactivity to create derived state and update your app when state changes.
You can derive state from other state with [`use_memo`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_memo.html).
```rust, no_run
use dioxus::prelude::*;
let mut count = use_signal(|| 0);
let double_count = use_memo(move || count() * 2);
// Now whenever we read double_count, we know it is always twice the value of count
println!("{}", double_count); // Prints "2"
// After we write to count, the reactive context will rerun and double_count will be updated automatically
count += 1;
println!("{}", double_count); // Prints "4"
```
You can also use reactivity to create derive state asynchronously. For example, you can use [`use_resource`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_resource.html) to load data from a server:
```rust, no_run
use dioxus::prelude::*;
let count = use_signal(|| 0);
let double_count = use_resource(move || async move {
// Start a request to the server. We are reading the value of count to format it into the url
// Since we are reading count, this resource will "subscribe" to changes to count (when count changes, the resource will rerun)
let response = reqwest::get(format!("https://myserver.com/doubleme?count={count}")).await.unwrap();
response.text().await.unwrap()
});
```
## Non Reactive State
You can use plain Rust types in Dioxus, but you should be aware that they are not reactive. If you read the non-reactive state, reactive scopes will not subscribe to the state.
You can make non-reactive state reactive by using the `Signal` type instead of a plain Rust type or by using the `use_reactive` hook.
```rust, no_run
use dioxus::prelude::*;
// ❌ Don't create non-reactive state
let state = use_hook(|| std::cell::RefCell::new(0));
// Computed values will get out of date if the state they depend on is not reactive
let doubled = use_memo(move || *state.borrow() * 2);
// ✅ Create reactive state
let state = use_signal(|| 0);
// Computed values will automatically keep up to date with the latest reactive state
let doubled = use_memo(move || state() * 2);
// ❌ Don't depend on non-reactive prop state in memos/resources
#[component]
fn MyComponent(state: i32) -> Element {
let doubled = use_memo(move || state * 2);
todo!()
}
// ✅ Wrap your props in ReadOnlySignal to make them reactive
#[component]
fn MyReactiveComponent(state: ReadOnlySignal<i32>) -> Element {
let doubled = use_memo(move || state() * 2);
todo!()
}
```
If your state can't be reactive, you can use the `use_reactive` hook to make it reactive.
```rust, no_run
use dioxus::prelude::*;
let state = rand::random::<i32>();
// You can make the state reactive by wrapping it in use_reactive
let doubled = use_memo(use_reactive!(|state| state * 2));
```
+165
View File
@@ -0,0 +1,165 @@
Signals are a Copy state management solution with automatic dependency tracking.
You may have noticed that this struct doesn't have many methods. Most methods for Signal are defined on the [`Readable`] and [`Writable`] traits.
# Reading and Writing to a Signal
Signals are similar to a copy version of `Rc<RefCell<T>>` built for UIs. You can read and write to a signal like a RefCell:
```rust, no_run
# use dioxus::prelude::*;
let mut signal = use_signal(|| 0);
{
// This will read the value (we use a block to make sure the read is dropped before the write. You can read more about this in the next section)
let read = signal.read();
// Just like refcell, read you can deref the read to get the inner &T reference
match &*read {
&0 => println!("read is 0"),
&1 => println!("read is 1"),
_ => println!("read is something else ({read})"),
}
}
// This will write to the value
let mut write = signal.write();
// Again, we can deref the write to get the inner &mut T reference
*write += 1;
```
Signals also have a bunch of helper methods to make it easier to use. Calling it like a function will clone the inner value. You can also call a few traits like AddAssign on it directly without writing to it manually:
```rust, no_run
# use dioxus::prelude::*;
let mut signal = use_signal(|| 0);
// This will clone the value
let clone: i32 = signal();
// You can directly display the signal
println!("{}", signal);
let signal_vec = use_signal(|| vec![1, 2, 3]);
// And use vec methods like .get and .len without reading the signal explicitly
let first = signal_vec.get(0);
let last = signal_vec.last();
let len = signal_vec.len();
// You can also iterate over signals directly
for i in signal_vec.iter() {
println!("{}", i);
}
```
For a full list of all the helpers available, check out the [`Readable`], [`ReadableVecExt`], [`ReadableOptionExt`], [`Writable`], [`WritableVecExt`], and [`WritableOptionExt`] traits.
Just like `RefCell<T>`, Signal checks borrows at runtime. If you read and write to the signal at the same time, it will panic:
```rust, no_run
# use dioxus::prelude::*;
let mut signal = use_signal(|| 0);
// If you create a read and hold it while you write to the signal, it will panic
let read = signal.read_unchecked();
// This will panic
signal += 1;
println!("{}", read);
```
To avoid issues with overlapping reads and writes, you can use the `with_*` variants of methods to read and write to the signal in a single scope or wrap your reads and writes inside a block:
```rust, no_run
# use dioxus::prelude::*;
let mut signal = use_signal(|| 0);
{
// Since this read is inside a block that ends before we write to the signal, the signal will be dropped before the write and it will not panic
let read = signal.read();
println!("{}", read);
}
signal += 1;
// Or you can use the with and with_write methods which only read or write to the signal inside the closure
signal.with(|read| println!("{}", read));
// Since the read only lasts as long as the closure, this will not panic
signal.with_mut(|write| *write += 1);
```
# Signals with Async
Because signals check borrows at runtime, you need to be careful when reading and writing to signals inside of async code. If you hold a read or write to a signal over an await point, that read or write may still be open while you run other parts of your app:
```rust, no_run
# use dioxus::prelude::*;
# async fn sleep(delay: u32) {}
async fn double_me_async(value: &mut u32) {
sleep(100).await;
*value *= 2;
}
let mut signal = use_signal(|| 0);
use_future(move || async move {
// Don't hold reads or writes over await points
let mut write = signal.write();
// While the future is waiting for the async work to finish, the write will be open
double_me_async(&mut write).await;
});
rsx!{
// This read may panic because the write is still active while the future is waiting for the async work to finish
"{signal}"
};
```
Instead of holding a read or write over an await point, you can clone whatever values you need out of your signal and then set the signal to the result once the async work is done:
```rust, no_run
# use dioxus::prelude::*;
# async fn sleep(delay: u32) {}
async fn double_me_async(value: u32) -> u32 {
sleep(100).await;
value * 2
}
let mut signal = use_signal(|| 0);
use_future(move || async move {
// Clone the value out of the signal
let current_value = signal();
// Run the async work
let new_value = double_me_async(current_value).await;
// Set the signal to the new value
signal.set(new_value);
});
rsx! {
// This read will not panic because the write is never held over an await point
"{signal}"
};
```
# Signals lifecycle
Signals are implemented with [generational-box](https://crates.io/crates/generational-box) which makes all values Copy even if the inner value is not Copy.
This is incredibly convenient for UI development, but it does come with some tradeoffs. The lifetime of the signal is tied to the lifetime of the component it was created in. If you drop the component that created the signal, the signal will be dropped as well. You might run into this if you try to pass a signal from a child component to a parent component and drop the child component. To avoid this you can create your signal higher up in your component tree, use global signals, or create a signal in a specific scope (like the `ScopeId::ROOT`) with [`Signal::new_in_scope`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Signal.html#method.new_in_scope)
TLDR **Don't pass signals up in the component tree**. It will cause issues:
```rust
# use dioxus::prelude::*;
fn MyComponent() -> Element {
let child_signal = use_signal(|| None);
rsx! {
IncrementButton {
child_signal
}
}
}
#[component]
fn IncrementButton(mut child_signal: Signal<Option<Signal<i32>>>) -> Element {
let signal_owned_by_child = use_signal(|| 0);
// Don't do this: it may cause issues if you drop the child component
child_signal.set(Some(signal_owned_by_child));
todo!()
}
```