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:
1 parent
054351139f
commit
0127501dbf
71 files changed
+4132
-1162
No files matched your search
@@ -23,6 +23,7 @@ slab = { workspace = true }
|
||||
dioxus-debug-cell = "0.1.1"
|
||||
futures-util = { workspace = true}
|
||||
generational-box.workspace = true
|
||||
rustversion = "1.0.17"
|
||||
|
||||
[dev-dependencies]
|
||||
futures-util = { workspace = true, default-features = false }
|
||||
@@ -30,6 +31,7 @@ dioxus-core = { workspace = true }
|
||||
dioxus = { workspace = true }
|
||||
web-sys = { version = "0.3.64", features = ["Document", "Window", "Element"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
reqwest = { workspace = true }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
## Overview
|
||||
|
||||
`dioxus-hooks` includes some basic useful hooks for dioxus such as:
|
||||
`dioxus-hooks` includes some basic useful hooks for Dioxus such as:
|
||||
|
||||
- use_signal
|
||||
- use_effect
|
||||
@@ -29,8 +29,40 @@
|
||||
- use_memo
|
||||
- use_coroutine
|
||||
|
||||
Unlike React, none of these hooks are foundational since they all build off the primitive `use_hook`. You can extend these hooks with [custom hooks](https://dioxuslabs.com/learn/0.5/cookbook/state/custom_hooks) in your own code. If you think they would be useful for the broader community, you can open a PR to add your hook to the [Dioxus Awesome](https://github.com/DioxusLabs/awesome-dioxus) list.
|
||||
|
||||
Unlike React, none of these hooks are foundational since they all build off the primitive `use_hook`.
|
||||
## State Cheat Sheet
|
||||
|
||||
If you aren't sure what hook to use, you can use this cheat sheet to help you decide:
|
||||
|
||||
### State Location
|
||||
|
||||
Depending on where you need to access the state, you can put your state in one of three places:
|
||||
|
||||
| Location | Where can you access the state? | Recommended for Libraries? | Examples |
|
||||
| ---------------------------------------------------------------------------------------- | ------------------------------- | -------------------------- | --------------------------------------------------------------------------- |
|
||||
| [Hooks](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_signal.html) | Any components you pass it to | ✅ | `use_signal(\|\| 0)`, `use_memo(\|\| state() * 2)` |
|
||||
| [Context](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html) | Any child components | ✅ | `use_context_provider(\|\| Signal::new(0))`, `use_context::<Signal<i32>>()` |
|
||||
| [Global](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Signal.html#method.global) | Anything in your app | ❌ | `Signal::global(\|\| 0)` |
|
||||
|
||||
### Derived State
|
||||
|
||||
If you don't have an initial value for your state, you can derive your state from other states with a closure or asynchronous function:
|
||||
|
||||
| Hook | Reactive (reruns when dependencies change) | Async | Memorizes Output | Example |
|
||||
| ----------------------------------------------------------------------------------- | ------------------------------------------ | ----- | ---------------- | ----------------------------------------------------------------------------------- |
|
||||
| [`use_memo`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_memo.html) | ✅ | ❌ | ✅ | `use_memo(move \|\| count() * 2)` |
|
||||
| [`use_resource`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_resource.html) | ✅ | ✅ | ❌ | `use_resource(move \|\| reqwest::get(format!("/users/{user_id}")))` |
|
||||
| [`use_future`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_future.html) | ❌ | ✅ | ❌ | `use_future(move \|\| println!("{:?}", reqwest::get(format!("/users/{user_id}"))))` |
|
||||
|
||||
### Persistent State
|
||||
|
||||
The core hooks library doesn't provide hooks for persistent state, but you can extend the core hooks with hooks from [dioxus-sdk](https://crates.io/crates/dioxus-sdk) and the [dioxus-router](https://crates.io/crates/dioxus-router) to provide persistent state management.
|
||||
|
||||
| State | Sharable | Example |
|
||||
| ---------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------- |
|
||||
| [`use_persistent`](https://github.com/DioxusLabs/sdk/tree/master/examples/storage) | ❌ | `use_persistent("unique_key", move \|\| initial_state)` |
|
||||
| [`Router<Route> {}`](https://dioxuslabs.com/learn/0.5/router) | ✅ | `#[derive(Routable, Clone, PartialEq)] enum Route { #[route("/user/:id")] Homepage { id: u32 } }` |
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
Creates a new Memo. The memo will be run immediately and whenever any signal it reads is written to. Memos can be used to efficiently compute derived data from signals.
|
||||
|
||||
```rust
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_signals::*;
|
||||
|
||||
fn App() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
// the double memo will always be equal to two times the value of count, even after count changes
|
||||
let double = use_memo(move || count * 2);
|
||||
|
||||
rsx! {
|
||||
"{double}"
|
||||
button {
|
||||
// When count changes, the memo will rerun and double will be updated
|
||||
// memos rerun any time you write to a signal they read. They will only rerun values/component that depend on them if the value of the memo changes
|
||||
onclick: move |_| count += 1,
|
||||
"Increment"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The closure you pass into memos will be called whenever the state you read inside the memo is written to even if the value hasn't actually changed, but the memo you get will not rerun other parts of your app unless the output changes (`PartialEq` returns false).
|
||||
|
||||
Lets dig into some examples to see how this works:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
let mut count = use_signal(|| 1);
|
||||
// double_count will rerun when state we read inside the memo changes (count)
|
||||
let double_count = use_memo(move || count() * 2);
|
||||
|
||||
// memos act a lot like a read only version of a signal. You can read them, display them, and move them around like any other signal
|
||||
println!("{}", double_count); // Prints "2"
|
||||
|
||||
// But you can't write to them directly
|
||||
// Instead, any time you write to a value the memo reads, the memo will rerun
|
||||
count += 1;
|
||||
|
||||
println!("{}", double_count); // Prints "4"
|
||||
|
||||
// Lets create another memo that reads the value of double_count
|
||||
let double_count_plus_one = use_memo(move || double_count() + 1);
|
||||
|
||||
println!("{}", double_count_plus_one); // Prints "5"
|
||||
|
||||
// Now if we write to count the double_count memo will rerun
|
||||
// If that the output of double_count changes, then it will cause double_count_plus_one to rerun
|
||||
count += 1;
|
||||
|
||||
println!("{}", double_count); // Prints "6"
|
||||
println!("{}", double_count_plus_one); // Prints "7"
|
||||
|
||||
// However if the value of double_count doesn't change after a write, then it won't trigger double_count_plus_one to rerun
|
||||
// Since we just write the same value, the doubled value is still 6 and we don't rerun double_count_plus_one
|
||||
*count.write() = 3;
|
||||
|
||||
println!("{}", double_count); // Prints "6"
|
||||
println!("{}", double_count_plus_one); // Prints "7"
|
||||
```
|
||||
|
||||
## With non-reactive dependencies
|
||||
|
||||
To add non-reactive dependencies, you can use the [`crate::use_reactive()`] hook.
|
||||
|
||||
Signals will automatically be added as dependencies, so you don't need to call this method for them.
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
#[component]
|
||||
fn Comp(count: u32) -> Element {
|
||||
// Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.
|
||||
let new_count = use_memo(use_reactive((&count,), |(count,)| count + 1));
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,96 @@
|
||||
<details>
|
||||
<summary>How should I move around state? (Click here to learn more about moving around and sharing state in Dioxus)</summary>
|
||||
|
||||
You will often need to move state around between your components. Dioxus provides three different ways to pass around state:
|
||||
|
||||
1. Just pass your values as [props](https://dioxuslabs.com/learn/0.5/reference/component_props):
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn MyComponent() -> Element {
|
||||
let count = use_signal(|| 0);
|
||||
|
||||
rsx! {
|
||||
IncrementButton {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn IncrementButton(mut count: Signal<i32>) -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
onclick: move |_| count += 1,
|
||||
"Increment"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is the most common way to pass state around. It is the most explicit and local to your component. Use this when it isn't overly annoying to pass around a value.
|
||||
|
||||
2. Use [use_context](https://dioxuslabs.com/learn/0.5/reference/context) to pass state from a parent component to all children:
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
#[derive(Clone, Copy)]
|
||||
struct MyState {
|
||||
count: Signal<i32>
|
||||
}
|
||||
|
||||
fn ParentComponent() -> Element {
|
||||
// Use context provider provides an unique type to all children of this component
|
||||
use_context_provider(|| MyState { count: Signal::new(0) });
|
||||
|
||||
rsx! {
|
||||
// IncrementButton will have access to the count without explicitly passing it through props
|
||||
IncrementButton {}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn IncrementButton() -> Element {
|
||||
// Use context gets the value from a parent component
|
||||
let mut count = use_context::<MyState>().count;
|
||||
|
||||
rsx! {
|
||||
button {
|
||||
onclick: move |_| count += 1,
|
||||
"Increment"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is slightly less explicit than passing it as a prop, but it is still local to the component. This is really great if you want state that is global to part of your app. It lets you create multiple global-ish states while still making state different when you reuse components. If I create a new `ParentComponent`, it will have a new `MyState`.
|
||||
|
||||
3. Globals let you share state with your whole app with rust statics:
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
// Count will be created the first time you access it with the closure you pass to Signal::global
|
||||
static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
|
||||
|
||||
fn ParentComponent() -> Element {
|
||||
rsx! {
|
||||
IncrementButton {}
|
||||
}
|
||||
}
|
||||
|
||||
fn IncrementButton() -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
// You don't need to pass anything around or get anything out of the context because COUNT is global
|
||||
onclick: move |_| *COUNT.write() += 1,
|
||||
"Increment"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Global state can be very ergonomic if your state is truly global, but you shouldn't use it if you need state to be different for different instances of your component. If I create another `IncrementButton` it will use the same `COUNT`. Libraries should generally avoid this to make components more reusable.
|
||||
|
||||
> Note: Even though it is in a static, `COUNT` will be different for each app instance (this is generally only reliant on the server).
|
||||
|
||||
</details>
|
||||
@@ -0,0 +1,152 @@
|
||||
## Additional Information that may be useful
|
||||
|
||||
<details>
|
||||
<summary>This function is a hook which means you need to <b>follow the rules of hooks</b> when you call it. You can click here to learn more about the rules of hooks.</summary>
|
||||
|
||||
Hooks in dioxus need to run in the same order every time you run the component. To make sure you run hooks in a consistent order, you should follow the rules of hooks:
|
||||
|
||||
1. Hooks should only be called from the root of a component or another hook
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn App() -> Element {
|
||||
// ✅ You can call hooks from the body of a component
|
||||
let number = use_signal(|| 1);
|
||||
if number() == 1 {
|
||||
// ❌ You can run into issues if you can hooks inside other expressions inside your component
|
||||
// If number changes from 0 to 1, the order of the hooks will be different and your app may panic
|
||||
let string = use_signal(|| "hello world".to_string());
|
||||
}
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn use_my_hook() -> Signal<i32> {
|
||||
// ✅ You can call hooks from the body of other hooks
|
||||
let number = use_signal(|| 1);
|
||||
// ❌ Again, creating hooks inside expressions inside other hooks can cause issues
|
||||
if number() == 1 {
|
||||
let string = use_signal(|| "hello world".to_string());
|
||||
}
|
||||
|
||||
number
|
||||
}
|
||||
```
|
||||
|
||||
2. Hooks should always start with `use_` to make it clear that you need to call them in a consistent order
|
||||
|
||||
Because hooks should only be called from the root of a component or another hook, you shouldn't call hooks inside of:
|
||||
|
||||
- ❌ Conditionals
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn App() -> Element {
|
||||
let number = use_signal(|| 1);
|
||||
// ❌ Changing the condition will change the order of the hooks
|
||||
if number() == 1 {
|
||||
let string = use_signal(|| "hello world".to_string());
|
||||
}
|
||||
|
||||
// ❌ Changing the value you are matching will change the order of the hooks
|
||||
match number() {
|
||||
1 => {
|
||||
let string = use_signal(|| "hello world".to_string());
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
- ❌ Loops
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn App() -> Element {
|
||||
let number = use_signal(|| 1);
|
||||
// ❌ Changing the loop will change the order of the hooks
|
||||
for i in 0..number() {
|
||||
let string = use_signal(|| "hello world".to_string());
|
||||
}
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
- ❌ Event Handlers
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn App() -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
onclick: move |_| {
|
||||
// ❌ Calling the event handler will change the order of the hooks
|
||||
use_signal(|| "hello world".to_string());
|
||||
},
|
||||
"Click me"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- ❌ Initialization closures in other hooks
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn App() -> Element {
|
||||
let number = use_signal(|| {
|
||||
// ❌ This closure will only be called when the component is first created. Running the component will change the order of the hooks
|
||||
let string = use_signal(|| "hello world".to_string());
|
||||
string()
|
||||
});
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Why do hooks need to run in a consistent order?</summary>
|
||||
|
||||
Hooks need to be run in a consistent order because dioxus stores hooks in a list and uses the order you run hooks in to determine what part of the state belongs to which hook.
|
||||
|
||||
Lets look at an example component:
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn App() -> Element {
|
||||
let number = use_signal(|| 1); // Hook 1
|
||||
let string = use_signal(|| "hello world".to_string()); // Hook 2
|
||||
let doubled = use_memo(move || number() * 2); // Hook 3
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
When we first create the component, we run the hooks in the order they are defined and store the state in the component in a list.
|
||||
|
||||
```rust, ignore
|
||||
[
|
||||
Box::new(1),
|
||||
Box::new("hello world".to_string()),
|
||||
Box::new(2),
|
||||
]
|
||||
```
|
||||
|
||||
Next time we run the component, we return items from the state list instead of creating state again.
|
||||
|
||||
```rust, ignore
|
||||
[
|
||||
Box::new(1), // Hook 1 returns 1
|
||||
Box::new("hello world".to_string()), // Hook 2 returns "hello world"
|
||||
Box::new(2), // Hook 3 returns 2
|
||||
]
|
||||
```
|
||||
|
||||
The order the hooks are run it must be the same because the order determines which hook gets what state! If you run the hooks in a different order, dioxus may panic because it can't turn the state back into the right type or you may just get the wrong state for your hook.
|
||||
|
||||
</details>
|
||||
|
||||
</details>
|
||||
@@ -0,0 +1,102 @@
|
||||
Effects are reactive closures that run **after the component has finished rendering**. Effects are useful for things like manually updating the DOM after it is rendered with web-sys or javascript. Or reading a value from the rendered DOM.
|
||||
|
||||
**Effects are specifically created for side effects. If you are trying to derive state, use a [memo](#derived-state), or [resource](#derived-async-state) instead.**
|
||||
|
||||
If you are trying to update the DOM, you can use the [`use_effect`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_effect.html) hook to run an effect after the component has finished rendering.
|
||||
|
||||
`use_effect` will subscribe to any changes in the signal values it captures effects will always run after first mount and then whenever the signal values change. If the use_effect call was skipped due to an early return, the effect will no longer activate.
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn MyComponent() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
|
||||
use_effect(move || {
|
||||
// Effects are reactive like memos, and resources. If you read a value inside the effect, the effect will rerun when that value changes
|
||||
let count = count.read();
|
||||
|
||||
// You can use the count value to update the DOM manually
|
||||
eval(&format!(
|
||||
r#"var c = document.getElementById("dioxus-canvas");
|
||||
var ctx = c.getContext("2d");
|
||||
ctx.font = "30px Arial";
|
||||
ctx.fillText("{count}", 10, 50);"#
|
||||
));
|
||||
});
|
||||
|
||||
rsx! {
|
||||
button {
|
||||
// When you click the button, count will be incremented and the effect will rerun
|
||||
onclick: move |_| count += 1,
|
||||
"Increment"
|
||||
}
|
||||
canvas {
|
||||
id: "dioxus-canvas",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## With non-reactive dependencies
|
||||
|
||||
To add non-reactive dependencies, you can use the [`crate::use_reactive()`] hook.
|
||||
|
||||
Signals will automatically be added as dependencies, so you don't need to call this method for them.
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
# async fn sleep(delay: u32) {}
|
||||
|
||||
#[component]
|
||||
fn Comp(count: u32) -> Element {
|
||||
// Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.
|
||||
use_effect(use_reactive((&count,), |(count,)| println!("Manually manipulate the dom") ));
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
## Modifying mounted nodes
|
||||
|
||||
One of the most common use cases for effects is modifying or reading something from the rendered DOM. Dioxus provides access to the DOM with the [`onmounted`](https://docs.rs/dioxus/latest/dioxus/events/fn.onmounted.html) event.
|
||||
|
||||
You can combine `use_effect` with `onmounted` to run an effect with access to a DOM element after all rendering is finished:
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn MyComponent() -> Element {
|
||||
let mut current_text = use_signal(String::new);
|
||||
let mut mounted_text_div: Signal<Option<MountedEvent>> = use_signal(|| None);
|
||||
let mut rendered_size = use_signal(String::new);
|
||||
|
||||
use_effect(move || {
|
||||
// If we have mounted the text div, we can read the width of the div
|
||||
if let Some(div) = mounted_text_div() {
|
||||
// We read the current text here inside of the effect instead of the spawn so the effect subscribes to the signal
|
||||
let text = current_text();
|
||||
spawn(async move {
|
||||
let bounding_box = div.get_client_rect().await;
|
||||
rendered_size.set(format!("{text} is {bounding_box:?}"));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
input {
|
||||
// When you enter text into the input, the effect will rerun because it subscribes to the current_text signal
|
||||
oninput: move |evt| current_text.set(evt.value()),
|
||||
placeholder: "Enter text here",
|
||||
value: "{current_text}"
|
||||
}
|
||||
// When text changes, it will change the size of this div
|
||||
div {
|
||||
onmounted: move |element| {
|
||||
mounted_text_div.set(Some(element.clone()));
|
||||
},
|
||||
"{current_text}"
|
||||
}
|
||||
|
||||
"{rendered_size}"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,149 @@
|
||||
[`use_resource()`] is a reactive hook that resolves to the result of a future. It will rerun when you write to any signals you read inside the future.
|
||||
|
||||
## Example
|
||||
|
||||
```rust
|
||||
use dioxus::prelude::*;
|
||||
|
||||
async fn get_weather(location: &WeatherLocation) -> Result<String, String> {
|
||||
Ok("Sunny".to_string())
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let country = use_signal(|| WeatherLocation {
|
||||
city: "Berlin".to_string(),
|
||||
country: "Germany".to_string(),
|
||||
coordinates: (52.5244, 13.4105),
|
||||
});
|
||||
|
||||
// Because the resource's future subscribes to `country` by reading it (`country.read()`),
|
||||
// every time `country` changes the resource's future will run again and thus provide a new value.
|
||||
let current_weather = use_resource(move || async move { get_weather(&country()).await });
|
||||
|
||||
rsx! {
|
||||
// the value of the resource can be polled to
|
||||
// conditionally render elements based off if it's future
|
||||
// finished (Some(Ok(_)), errored Some(Err(_)),
|
||||
// or is still running (None)
|
||||
match &*current_weather.read_unchecked() {
|
||||
Some(Ok(weather)) => rsx! { WeatherElement { weather } },
|
||||
Some(Err(e)) => rsx! { p { "Loading weather failed, {e}" } },
|
||||
None => rsx! { p { "Loading..." } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct WeatherLocation {
|
||||
city: String,
|
||||
country: String,
|
||||
coordinates: (f64, f64),
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn WeatherElement(weather: String) -> Element {
|
||||
rsx! { p { "The weather is {weather}" } }
|
||||
}
|
||||
```
|
||||
|
||||
## Reactivity
|
||||
|
||||
`use_resource` is reactive which just means that it will rerun when you write to any signals you read inside the future. This means that any time you change something the future depends on, the resource automatically knows to rerun. Lets take a look at some examples:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
// Create a new count signal
|
||||
let mut count = use_signal(|| 1);
|
||||
// Create a new resource that doubles the value of count
|
||||
let double_count = use_resource(move || async move {
|
||||
// Start a request to the server. We are reading the value of count in the format macro
|
||||
// Reading the value of count makes the resource "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()
|
||||
});
|
||||
|
||||
// Resource can be read in a way that is similar to signals, but they have a bit of extra information about the state of the resource future.
|
||||
|
||||
// Calling .state() on a resource will return a Signal<UseResourceState> with information about the current status of the resource
|
||||
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Pending"
|
||||
|
||||
// You can also try to get the last resolved value of the resource with the .value() method
|
||||
println!("{:?}", double_count.read()); // Prints "None"
|
||||
|
||||
// Wait for the resource to finish and get the value
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
|
||||
// Now if we read the state, we will see that it is done
|
||||
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Done"
|
||||
|
||||
// And we can get the value
|
||||
println!("{:?}", double_count.read()); // Prints "Some(2)"
|
||||
|
||||
// Now if we write to count, the resource will rerun
|
||||
count += 1; // count is now 2
|
||||
|
||||
// Wait for the resource to finish and get the value
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
|
||||
// Now if we read the state, we will see that it is done
|
||||
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Done"
|
||||
|
||||
// And we can get the value
|
||||
println!("{:?}", double_count.read()); // Prints "Some(4)"
|
||||
|
||||
// One more case, what happens if we write to the resource while it is in progress?
|
||||
// The resource will rerun and the value will be None
|
||||
count += 1; // count is now 3
|
||||
|
||||
// If we write to a value the resource subscribes to again, it will cancel the current future and start a new one
|
||||
count += 1; // count is now 4
|
||||
|
||||
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Stopped"
|
||||
println!("{:?}", double_count.read()); // Prints the last resolved value "Some(4)"
|
||||
|
||||
// After we wait for the resource to finish, we will get the value of only the latest future
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
|
||||
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Done"
|
||||
|
||||
println!("{:?}", double_count.read()); // Prints "Some(8)"
|
||||
```
|
||||
|
||||
## With non-reactive dependencies
|
||||
|
||||
`use_resource` can determine dependencies automatically with any reactive value ([`Signal`]s, [`ReadOnlySignal`]s, [`Memo`]s, [`Resource`]s, etc). If you need to rerun the future when a normal rust value changes, you can add it as a dependency with the [`crate::use_reactive()`] hook:
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
# async fn sleep(delay: u32) {}
|
||||
#[component]
|
||||
fn Comp(count: u32) -> Element {
|
||||
// We manually add the resource to the dependencies list with the `use_reactive` hook
|
||||
// Any time `count` changes, the resource will rerun
|
||||
let new_count = use_resource(use_reactive!(|(count,)| async move {
|
||||
sleep(100).await;
|
||||
count + 1
|
||||
}));
|
||||
rsx! { "{new_count:?}" }
|
||||
}
|
||||
|
||||
// If your value is already reactive, you never need to call `use_reactive` manually
|
||||
// Instead of manually adding count to the dependencies list, you can make your prop reactive by wrapping it in `ReadOnlySignal`
|
||||
#[component]
|
||||
fn ReactiveComp(count: ReadOnlySignal<u32>) -> Element {
|
||||
// Because `count` is reactive, the resource knows to rerun when `count` changes automatically
|
||||
let new_count = use_resource(move || async move {
|
||||
sleep(100).await;
|
||||
count() + 1
|
||||
});
|
||||
rsx! { "{new_count:?}" }
|
||||
}
|
||||
```
|
||||
|
||||
## Differences from `use_future` and `use_memo`
|
||||
|
||||
Just like [`crate::use_future()`], `use_resource` spawns an async task in a component. However, unlike [`crate::use_future()`], `use_resource` returns the result of the future and will rerun when any dependencies change.
|
||||
|
||||
Resources return a value based on some existing state just like memos, but unlike memos, resources do not memorize the output of the closure. They will always rerun any parts of your app that read the value of the resource when the future resolves even if the output doesn't change.
|
||||
|
||||
See also: [`Resource`]
|
||||
@@ -9,6 +9,7 @@ use dioxus_signals::Writable;
|
||||
/// There is *currently* no signal tracking on the Callback so anything reading from it will not be updated.
|
||||
///
|
||||
/// This API is in flux and might not remain.
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
pub fn use_callback<O>(f: impl FnMut() -> O + 'static) -> UseCallback<O> {
|
||||
// Create a copyvalue with no contents
|
||||
// This copyvalue is generic over F so that it can be sized properly
|
||||
@@ -35,11 +36,24 @@ pub fn use_callback<O>(f: impl FnMut() -> O + 'static) -> UseCallback<O> {
|
||||
/// This callback is not generic over a return type so you can hold a bunch of callbacks at once
|
||||
///
|
||||
/// If you need a callback that returns a value, you can simply wrap the closure you pass in that sets a value in its scope
|
||||
#[derive(PartialEq)]
|
||||
pub struct UseCallback<O: 'static + ?Sized> {
|
||||
inner: CopyValue<Box<dyn FnMut() -> O>>,
|
||||
}
|
||||
|
||||
impl<O: 'static + ?Sized> PartialEq for UseCallback<O> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.inner == other.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: 'static + ?Sized> std::fmt::Debug for UseCallback<O> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("UseCallback")
|
||||
.field("inner", &self.inner.value())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: 'static + ?Sized> Clone for UseCallback<O> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { inner: self.inner }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
a form of use_state explicitly for map-style collections (BTreeMap, HashMap, etc).
|
||||
a form of use_signal explicitly for map-style collections (BTreeMap, HashMap, etc).
|
||||
|
||||
Why?
|
||||
---
|
||||
|
||||
@@ -6,6 +6,8 @@ use dioxus_core::{
|
||||
/// Consume some context in the tree, providing a sharable handle to the value
|
||||
///
|
||||
/// Does not regenerate the value if the value is changed at the parent.
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[must_use]
|
||||
pub fn try_use_context<T: 'static + Clone>() -> Option<T> {
|
||||
use_hook(|| try_consume_context::<T>())
|
||||
@@ -29,6 +31,8 @@ pub fn try_use_context<T: 'static + Clone>() -> Option<T> {
|
||||
/// rsx! { "user using dark mode: {user_theme == Theme::Dark}" }
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[must_use]
|
||||
pub fn use_context<T: 'static + Clone>() -> T {
|
||||
use_hook(|| consume_context::<T>())
|
||||
@@ -56,6 +60,8 @@ pub fn use_context<T: 'static + Clone>() -> T {
|
||||
/// }
|
||||
///}
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
pub fn use_context_provider<T: 'static + Clone>(f: impl FnOnce() -> T) -> T {
|
||||
use_hook(|| provide_context(f()))
|
||||
}
|
||||
@@ -6,11 +6,11 @@ use std::future::Future;
|
||||
|
||||
/// Maintain a handle over a future that can be paused, resumed, and canceled.
|
||||
///
|
||||
/// This is an upgraded form of [`use_future`] with an integrated channel system.
|
||||
/// Specifically, the coroutine generated here comes with an [`UnboundedChannel`]
|
||||
/// This is an upgraded form of [`crate::use_future()`] with an integrated channel system.
|
||||
/// Specifically, the coroutine generated here comes with an [`futures_channel::mpsc::UnboundedSender`]
|
||||
/// built into it - saving you the hassle of building your own.
|
||||
///
|
||||
/// Addititionally, coroutines are automatically injected as shared contexts, so
|
||||
/// Additionally, coroutines are automatically injected as shared contexts, so
|
||||
/// downstream components can tap into a coroutine's channel and send messages
|
||||
/// into a singular async event loop.
|
||||
///
|
||||
@@ -33,7 +33,7 @@ use std::future::Future;
|
||||
/// ## UseCallback instead
|
||||
///
|
||||
/// However, you must plan out your own concurrency and synchronization. If you
|
||||
/// don't care about actions in your app being synchronized, you can use [`use_callback`]
|
||||
/// don't care about actions in your app being synchronized, you can use [`crate::use_callback()`]
|
||||
/// hook to spawn multiple tasks and run them concurrently.
|
||||
///
|
||||
/// ### Notice
|
||||
@@ -42,7 +42,9 @@ use std::future::Future;
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// use futures_util::StreamExt;
|
||||
/// enum Action {
|
||||
/// Start,
|
||||
/// Stop,
|
||||
@@ -63,8 +65,9 @@ use std::future::Future;
|
||||
/// onclick: move |_| chat_client.send(Action::Start),
|
||||
/// "Start Chat Service"
|
||||
/// }
|
||||
/// })
|
||||
/// };
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
pub fn use_coroutine<M, G, F>(init: G) -> Coroutine<M>
|
||||
where
|
||||
M: 'static,
|
||||
@@ -96,6 +99,7 @@ where
|
||||
/// Analagous to use_context_provider and use_context,
|
||||
/// but used for coroutines specifically
|
||||
/// See the docs for [`use_coroutine`] for more details.
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[must_use]
|
||||
pub fn use_coroutine_handle<M: 'static>() -> Coroutine<M> {
|
||||
use_hook(consume_context::<Coroutine<M>>)
|
||||
|
||||
@@ -4,41 +4,8 @@ use futures_util::StreamExt;
|
||||
|
||||
use crate::use_callback;
|
||||
|
||||
/// `use_effect` will subscribe to any changes in the signal values it captures
|
||||
/// effects will always run after first mount and then whenever the signal values change
|
||||
/// If the use_effect call was skipped due to an early return, the effect will no longer activate.
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn app() -> Element {
|
||||
/// let mut count = use_signal(|| 0);
|
||||
/// //the effect runs again each time count changes
|
||||
/// use_effect(move || println!("Count changed to {count}"));
|
||||
///
|
||||
/// rsx! {
|
||||
/// h1 { "High-Five counter: {count}" }
|
||||
/// button { onclick: move |_| count += 1, "Up high!" }
|
||||
/// button { onclick: move |_| count -= 1, "Down low!" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## With non-reactive dependencies
|
||||
/// To add non-reactive dependencies, you can use the `use_reactive` hook.
|
||||
///
|
||||
/// Signals will automatically be added as dependencies, so you don't need to call this method for them.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # async fn sleep(delay: u32) {}
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Comp(count: u32) -> Element {
|
||||
/// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.
|
||||
/// use_effect(use_reactive((&count,), |(count,)| println!("Manually manipulate the dom") ));
|
||||
///
|
||||
/// todo!()
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/side_effects.md")]
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[track_caller]
|
||||
pub fn use_effect(callback: impl FnMut() + 'static) -> Effect {
|
||||
let callback = use_callback(callback);
|
||||
|
||||
@@ -5,12 +5,16 @@ use dioxus_signals::*;
|
||||
use std::future::Future;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// A hook that allows you to spawn a future.
|
||||
/// This future will **not** run on the server
|
||||
/// The future is spawned on the next call to `wait_for_next_render` which means that it will not run on the server.
|
||||
/// To run a future on the server, you should use `spawn` directly.
|
||||
/// `use_future` **won't return a value**.
|
||||
/// If you want to return a value from a future, use `use_resource` instead.
|
||||
/// A hook that allows you to spawn a future the first time you render a component.
|
||||
///
|
||||
///
|
||||
/// This future will **not** run on the server. To run a future on the server, you should use [`spawn_isomorphic`] directly.
|
||||
///
|
||||
///
|
||||
/// `use_future` **won't return a value**. If you want to return a value from a future, use [`crate::use_resource()`] instead.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
@@ -35,6 +39,9 @@ use std::ops::Deref;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[doc(alias = "use_async")]
|
||||
pub fn use_future<F>(mut future: impl FnMut() -> F + 'static) -> UseFuture
|
||||
where
|
||||
F: Future + 'static,
|
||||
|
||||
@@ -2,6 +2,8 @@ use dioxus_core::prelude::*;
|
||||
use dioxus_signals::{CopyValue, Writable};
|
||||
|
||||
/// A hook that uses before/after lifecycle hooks to determine if the hook was run
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
pub fn use_hook_did_run(mut handler: impl FnMut(bool) + 'static) {
|
||||
let mut did_run_ = use_hook(|| CopyValue::new(false));
|
||||
|
||||
|
||||
@@ -2,41 +2,9 @@ use crate::use_callback;
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_signals::{Memo, Signal};
|
||||
|
||||
/// Creates a new Memo. The memo will be run immediately and whenever any signal it reads changes.
|
||||
///
|
||||
/// Memos can be used to efficiently compute derived data from signals.
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
/// use dioxus_signals::*;
|
||||
///
|
||||
/// fn App() -> Element {
|
||||
/// let mut count = use_signal(|| 0);
|
||||
/// let double = use_memo(move || count * 2);
|
||||
/// count += 1;
|
||||
/// assert_eq!(double(), count * 2);
|
||||
///
|
||||
/// rsx! { "{double}" }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## With non-reactive dependencies
|
||||
/// To add non-reactive dependencies, you can use the `use_reactive` hook.
|
||||
///
|
||||
/// Signals will automatically be added as dependencies, so you don't need to call this method for them.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # async fn sleep(delay: u32) {}
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Comp(count: u32) -> Element {
|
||||
/// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.
|
||||
/// let new_count = use_memo(use_reactive((&count,), |(count,)| count + 1));
|
||||
///
|
||||
/// todo!()
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/derived_state.md")]
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[track_caller]
|
||||
pub fn use_memo<R: PartialEq>(f: impl FnMut() -> R + 'static) -> Memo<R> {
|
||||
let callback = use_callback(f);
|
||||
|
||||
@@ -3,6 +3,14 @@ use dioxus_signals::{Readable, Writable};
|
||||
use crate::use_signal;
|
||||
|
||||
/// A dependency is a trait that can be used to determine if a effect or selector should be re-run.
|
||||
#[rustversion::attr(
|
||||
since(1.78.0),
|
||||
diagnostic::on_unimplemented(
|
||||
message = "`Dependency` is not implemented for `{Self}`",
|
||||
label = "Dependency",
|
||||
note = "Dependency is automatically implemented for all tuples with less than 8 references to element that implement `PartialEq` and `Clone`. For example, `(&A, &B, &C)` implements `Dependency` automatically as long as `A`, `B`, and `C` implement `PartialEq` and `Clone`.",
|
||||
)
|
||||
)]
|
||||
pub trait Dependency: Sized + Clone {
|
||||
/// The output of the dependency
|
||||
type Out: Clone + PartialEq + 'static;
|
||||
@@ -20,6 +28,14 @@ impl Dependency for () {
|
||||
}
|
||||
|
||||
/// A dependency is a trait that can be used to determine if a effect or selector should be re-run.
|
||||
#[rustversion::attr(
|
||||
since(1.78.0),
|
||||
diagnostic::on_unimplemented(
|
||||
message = "`DependencyElement` is not implemented for `{Self}`",
|
||||
label = "dependency element",
|
||||
note = "DependencyElement is automatically implemented for types that implement `PartialEq` and `Clone`",
|
||||
)
|
||||
)]
|
||||
pub trait DependencyElement: 'static + PartialEq + Clone {}
|
||||
impl<T> DependencyElement for T where T: 'static + PartialEq + Clone {}
|
||||
|
||||
@@ -83,6 +99,7 @@ impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G =
|
||||
/// println!("Data changed: {}", data);
|
||||
/// }));
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
pub fn use_reactive<O, D: Dependency>(
|
||||
non_reactive_data: D,
|
||||
mut closure: impl FnMut(D::Out) -> O + 'static,
|
||||
@@ -113,6 +130,7 @@ pub fn use_reactive<O, D: Dependency>(
|
||||
/// println!("Data changed: {}", data);
|
||||
/// }));
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[macro_export]
|
||||
macro_rules! use_reactive {
|
||||
(|| $($rest:tt)*) => { use_reactive( (), move |_| $($rest)* ) };
|
||||
|
||||
@@ -7,66 +7,11 @@ use futures_util::{future, pin_mut, FutureExt, StreamExt};
|
||||
use std::ops::Deref;
|
||||
use std::{cell::Cell, future::Future, rc::Rc};
|
||||
|
||||
/// A memo that resolves to a value asynchronously.
|
||||
/// Similar to `use_future` but `use_resource` returns a value.
|
||||
/// See [`Resource`] for more details.
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # #[derive(Clone)]
|
||||
/// # struct WeatherLocation {
|
||||
/// # city: String,
|
||||
/// # country: String,
|
||||
/// # coordinates: (f64, f64),
|
||||
/// # }
|
||||
/// # async fn get_weather(location: &WeatherLocation) -> Result<String, String> {
|
||||
/// # Ok("Sunny".to_string())
|
||||
/// # }
|
||||
/// # #[component]
|
||||
/// # fn WeatherElement(weather: String) -> Element {
|
||||
/// # rsx! { p { "The weather is {weather}" } }
|
||||
/// # }
|
||||
/// fn app() -> Element {
|
||||
/// let country = use_signal(|| WeatherLocation {
|
||||
/// city: "Berlin".to_string(),
|
||||
/// country: "Germany".to_string(),
|
||||
/// coordinates: (52.5244, 13.4105),
|
||||
/// });
|
||||
/// ///
|
||||
/// // Because the resource's future subscribes to `country` by reading it (`country.read()`),
|
||||
/// // every time `country` changes the resource's future will run again and thus provide a new value.
|
||||
/// let current_weather = use_resource(move || async move { get_weather(&country()).await });
|
||||
///
|
||||
/// rsx! {
|
||||
/// // the value of the resource can be polled to
|
||||
/// // conditionally render elements based off if it's future
|
||||
/// // finished (Some(Ok(_)), errored Some(Err(_)),
|
||||
/// // or is still running (None)
|
||||
/// match &*current_weather.read() {
|
||||
/// Some(Ok(weather)) => rsx! { WeatherElement { weather } },
|
||||
/// Some(Err(e)) => rsx! { p { "Loading weather failed, {e}" } },
|
||||
/// None => rsx! { p { "Loading..." } }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## With non-reactive dependencies
|
||||
/// To add non-reactive dependencies, you can use the `use_reactive` hook.
|
||||
///
|
||||
/// Signals will automatically be added as dependencies, so you don't need to call this method for them.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # async fn sleep(delay: u32) {}
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Comp(count: u32) -> Element {
|
||||
/// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.
|
||||
/// let new_count = use_resource(use_reactive((&count,), |(count,)| async move {count + 1} ));
|
||||
///
|
||||
/// todo!()
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/use_resource.md")]
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[doc(alias = "use_async_memo")]
|
||||
#[doc(alias = "use_memo_async")]
|
||||
#[must_use = "Consider using `cx.spawn` to run a future without reading its value"]
|
||||
#[track_caller]
|
||||
pub fn use_resource<T, F>(mut future: impl FnMut() -> F + 'static) -> Resource<T>
|
||||
@@ -135,7 +80,32 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
/// A handle to a reactive future spawned with [`use_resource`] that can be used to modify or read the result of the future.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// Reading the result of a resource:
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// // Since our resource may not be ready yet, the value is an Option. Our request may also fail, so the get function returns a Result
|
||||
/// // The complete type we need to match is `Option<Result<String, reqwest::Error>>`
|
||||
/// // We can use `read_unchecked` to keep our matching code in one statement while avoiding a temporary variable error (this is still completely safe because dioxus checks the borrows at runtime)
|
||||
/// match &*resource.read_unchecked() {
|
||||
/// Some(Ok(value)) => rsx! { "{value:?}" },
|
||||
/// Some(Err(err)) => rsx! { "Error: {err}" },
|
||||
/// None => rsx! { "Loading..." },
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Resource<T: 'static> {
|
||||
value: Signal<Option<T>>,
|
||||
task: Signal<Task>,
|
||||
@@ -143,6 +113,15 @@ pub struct Resource<T: 'static> {
|
||||
callback: UseCallback<Task>,
|
||||
}
|
||||
|
||||
impl<T> PartialEq for Resource<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value == other.value
|
||||
&& self.state == other.state
|
||||
&& self.task == other.task
|
||||
&& self.callback == other.callback
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Resource<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
@@ -172,6 +151,28 @@ impl<T> Resource<T> {
|
||||
///
|
||||
/// Will not cancel the previous future, but will ignore any values that it
|
||||
/// generates.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// // We can get a signal with the value of the resource with the `value` method
|
||||
/// onclick: move |_| resource.restart(),
|
||||
/// "Restart resource"
|
||||
/// }
|
||||
/// "{resource:?}"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn restart(&mut self) {
|
||||
self.task.write().cancel();
|
||||
let new_task = self.callback.call();
|
||||
@@ -179,18 +180,93 @@ impl<T> Resource<T> {
|
||||
}
|
||||
|
||||
/// Forcefully cancel the resource's future.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// // We can cancel the resource before it finishes with the `cancel` method
|
||||
/// onclick: move |_| resource.cancel(),
|
||||
/// "Cancel resource"
|
||||
/// }
|
||||
/// "{resource:?}"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn cancel(&mut self) {
|
||||
self.state.set(UseResourceState::Stopped);
|
||||
self.task.write().cancel();
|
||||
}
|
||||
|
||||
/// Pause the resource's future.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// // We can pause the future with the `pause` method
|
||||
/// onclick: move |_| resource.pause(),
|
||||
/// "Pause"
|
||||
/// }
|
||||
/// button {
|
||||
/// // And resume it with the `resume` method
|
||||
/// onclick: move |_| resource.resume(),
|
||||
/// "Resume"
|
||||
/// }
|
||||
/// "{resource:?}"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn pause(&mut self) {
|
||||
self.state.set(UseResourceState::Paused);
|
||||
self.task.write().pause();
|
||||
}
|
||||
|
||||
/// Resume the resource's future.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// // We can pause the future with the `pause` method
|
||||
/// onclick: move |_| resource.pause(),
|
||||
/// "Pause"
|
||||
/// }
|
||||
/// button {
|
||||
/// // And resume it with the `resume` method
|
||||
/// onclick: move |_| resource.resume(),
|
||||
/// "Resume"
|
||||
/// }
|
||||
/// "{resource:?}"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn resume(&mut self) {
|
||||
if self.finished() {
|
||||
return;
|
||||
@@ -200,7 +276,29 @@ impl<T> Resource<T> {
|
||||
self.task.write().resume();
|
||||
}
|
||||
|
||||
/// Clear the resource's value.
|
||||
/// Clear the resource's value. This will just reset the value. It will not modify any running tasks.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// // We clear the value without modifying any running tasks with the `clear` method
|
||||
/// onclick: move |_| resource.clear(),
|
||||
/// "Clear"
|
||||
/// }
|
||||
/// "{resource:?}"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn clear(&mut self) {
|
||||
self.value.write().take();
|
||||
}
|
||||
@@ -214,6 +312,30 @@ impl<T> Resource<T> {
|
||||
/// Is the resource's future currently finished running?
|
||||
///
|
||||
/// Reading this does not subscribe to the future's state
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// // We can use the `finished` method to check if the future is finished
|
||||
/// if resource.finished() {
|
||||
/// rsx! {
|
||||
/// "The resource is finished"
|
||||
/// }
|
||||
/// } else {
|
||||
/// rsx! {
|
||||
/// "The resource is still running"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn finished(&self) -> bool {
|
||||
matches!(
|
||||
*self.state.peek(),
|
||||
@@ -221,12 +343,67 @@ impl<T> Resource<T> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the current state of the resource's future.
|
||||
/// Get the current state of the resource's future. This method returns a [`ReadOnlySignal`] which can be read to get the current state of the resource or passed to other hooks and components.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// // We can read the current state of the future with the `state` method
|
||||
/// match resource.state().cloned() {
|
||||
/// UseResourceState::Pending => rsx! {
|
||||
/// "The resource is still pending"
|
||||
/// },
|
||||
/// UseResourceState::Paused => rsx! {
|
||||
/// "The resource has been paused"
|
||||
/// },
|
||||
/// UseResourceState::Stopped => rsx! {
|
||||
/// "The resource has been stopped"
|
||||
/// },
|
||||
/// UseResourceState::Ready => rsx! {
|
||||
/// "The resource is ready!"
|
||||
/// },
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn state(&self) -> ReadOnlySignal<UseResourceState> {
|
||||
self.state.into()
|
||||
}
|
||||
|
||||
/// Get the current value of the resource's future.
|
||||
/// Get the current value of the resource's future. This method returns a [`ReadOnlySignal`] which can be read to get the current value of the resource or passed to other hooks and components.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// // We can get a signal with the value of the resource with the `value` method
|
||||
/// let value = resource.value();
|
||||
///
|
||||
/// // Since our resource may not be ready yet, the value is an Option. Our request may also fail, so the get function returns a Result
|
||||
/// // The complete type we need to match is `Option<Result<String, reqwest::Error>>`
|
||||
/// // We can use `read_unchecked` to keep our matching code in one statement while avoiding a temporary variable error (this is still completely safe because dioxus checks the borrows at runtime)
|
||||
/// match &*value.read_unchecked() {
|
||||
/// Some(Ok(value)) => rsx! { "{value:?}" },
|
||||
/// Some(Err(err)) => rsx! { "Error: {err}" },
|
||||
/// None => rsx! { "Loading..." },
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn value(&self) -> ReadOnlySignal<Option<T>> {
|
||||
self.value.into()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
use dioxus_core::{prelude::provide_root_context, prelude::try_consume_context, use_hook};
|
||||
|
||||
/// Try to get a value from the root of the virtual dom, if it doesn't exist, create a new one with the closure provided.
|
||||
///
|
||||
/// This is useful for global context inside of libraries. Instead of having the user provide context in the root of their app, you can use this hook to create a context at the root automatically.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # #[derive(Clone)]
|
||||
/// # struct Logger;
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// fn use_logger() -> Logger {
|
||||
/// // We want one logger per app in the root. Instead of forcing the user to always provide a logger, we can insert a default logger if one doesn't exist.
|
||||
/// use_root_context(|| Logger)
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
pub fn use_root_context<T: 'static + Clone>(new: impl FnOnce() -> T) -> T {
|
||||
use_hook(|| {
|
||||
try_consume_context::<T>()
|
||||
|
||||
@@ -5,7 +5,7 @@ use dioxus_signals::{ReadOnlySignal, SetCompare};
|
||||
|
||||
/// Creates a new SetCompare which efficiently tracks when a value changes to check if it is equal to a set of values.
|
||||
///
|
||||
/// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_memo`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to.
|
||||
/// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_memo()`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to.
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
@@ -38,12 +38,16 @@ use dioxus_signals::{ReadOnlySignal, SetCompare};
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[must_use]
|
||||
pub fn use_set_compare<R: Eq + Hash>(f: impl FnMut() -> R + 'static) -> SetCompare<R> {
|
||||
use_hook(move || SetCompare::new(f))
|
||||
}
|
||||
|
||||
/// A hook that returns true if the value is equal to the value in the set compare.
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[must_use]
|
||||
pub fn use_set_compare_equal<R: Eq + Hash>(
|
||||
value: R,
|
||||
|
||||
@@ -30,6 +30,10 @@ use dioxus_signals::{Signal, SignalData, Storage, SyncStorage, UnsyncStorage};
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[doc(alias = "use_state")]
|
||||
#[track_caller]
|
||||
#[must_use]
|
||||
pub fn use_signal<T: 'static>(f: impl FnOnce() -> T) -> Signal<T, UnsyncStorage> {
|
||||
@@ -68,6 +72,7 @@ pub fn use_signal<T: 'static>(f: impl FnOnce() -> T) -> Signal<T, UnsyncStorage>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[doc(alias = "use_rw")]
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
pub fn use_signal_sync<T: Send + Sync + 'static>(f: impl FnOnce() -> T) -> Signal<T, SyncStorage> {
|
||||
|
||||
Reference in new issue
Block a user