Merge branch 'master' into master

This commit is contained in:
Jonathan Kelley authored and GitHub committed 2023-07-14 15:36:28 -07:00
commit af49cb4ec6
313 files changed
+23988 -2334

No files matched your search

+3 -3
View File
@@ -2,11 +2,11 @@
The `use_future` and `use_coroutine` hooks are useful if you want to unconditionally spawn the future. Sometimes, though, you'll want to only spawn a future in response to an event, such as a mouse click. For example, suppose you need to send a request when the user clicks a "log in" button. For this, you can use `cx.spawn`:
```rust
```rust, no_run
{{#include ../../../examples/spawn.rs:spawn}}
```
> Note: `spawn` will always spawn a *new* future. You most likely don't want to call it on every render.
> Note: `spawn` will always spawn a _new_ future. You most likely don't want to call it on every render.
Calling `spawn` will give you a `JoinHandle` which lets you cancel or pause the future.
@@ -14,6 +14,6 @@ Calling `spawn` will give you a `JoinHandle` which lets you cancel or pause the
Sometimes, you might want to spawn a background task that needs multiple threads or talk to hardware that might block your app code. In these cases, we can directly spawn a Tokio task from our future. For Dioxus-Desktop, your task will be spawned onto Tokio's Multithreaded runtime:
```rust
```rust, no_run
{{#include ../../../examples/spawn.rs:tokio}}
```
+11 -14
View File
@@ -8,7 +8,7 @@ Like regular futures, code in a coroutine will run until the next `await` point
The `use_coroutine` hook allows you to create a coroutine. Most coroutines we write will be polling loops using async/await.
```rust
```rust, no_run
fn app(cx: Scope) -> Element {
let ws: &UseCoroutine<()> = use_coroutine(cx, |rx| async move {
// Connect to some sort of service
@@ -26,7 +26,7 @@ For many services, a simple async loop will handle the majority of use cases.
However, if we want to temporarily disable the coroutine, we can "pause" it using the `pause` method, and "resume" it using the `resume` method:
```rust
```rust, no_run
let sync: &UseCoroutine<()> = use_coroutine(cx, |rx| async move {
// code for syncing
});
@@ -58,7 +58,7 @@ The future must be `'static` – so any values captured by the task cannot carry
You can use [to_owned](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned) to create a clone of the hook handle which can be moved into the async closure.
```rust
```rust, no_run
let sync_status = use_state(cx, || Status::Launching);
let sync_task = use_coroutine(cx, |rx: UnboundedReceiver<SyncAction>| {
let sync_status = sync_status.to_owned();
@@ -73,7 +73,7 @@ let sync_task = use_coroutine(cx, |rx: UnboundedReceiver<SyncAction>| {
To make this a bit less verbose, Dioxus exports the `to_owned!` macro which will create a binding as shown above, which can be quite helpful when dealing with many values.
```rust
```rust, no_run
let sync_status = use_state(cx, || Status::Launching);
let load_status = use_state(cx, || Status::Launching);
let sync_task = use_coroutine(cx, |rx: UnboundedReceiver<SyncAction>| {
@@ -88,10 +88,9 @@ let sync_task = use_coroutine(cx, |rx: UnboundedReceiver<SyncAction>| {
You might've noticed the `use_coroutine` closure takes an argument called `rx`. What is that? Well, a common pattern in complex apps is to handle a bunch of async code at once. With libraries like Redux Toolkit, managing multiple promises at once can be challenging and a common source of bugs.
With Coroutines, we can centralize our async logic. The `rx` parameter is an Channel that allows code external to the coroutine to send data *into* the coroutine. Instead of looping on an external service, we can loop on the channel itself, processing messages from within our app without needing to spawn a new future. To send data into the coroutine, we would call "send" on the handle.
With Coroutines, we can centralize our async logic. The `rx` parameter is an Channel that allows code external to the coroutine to send data _into_ the coroutine. Instead of looping on an external service, we can loop on the channel itself, processing messages from within our app without needing to spawn a new future. To send data into the coroutine, we would call "send" on the handle.
```rust
```rust, no_run
use futures_util::stream::StreamExt;
enum ProfileUpdate {
@@ -119,13 +118,11 @@ cx.render(rsx!{
})
```
> Note: In order to use/run the `rx.next().await` statement you will need to extend the [`Stream`] trait (used by [`UnboundedReceiver`]) by adding 'futures_util' as a dependency to your project and adding the `use futures_util::stream::StreamExt;`.
For sufficiently complex apps, we could build a bunch of different useful "services" that loop on channels to update the app.
```rust
```rust, no_run
let profile = use_coroutine(cx, profile_service);
let editor = use_coroutine(cx, editor_service);
let sync = use_coroutine(cx, sync_service);
@@ -143,9 +140,9 @@ async fn editor_service(rx: UnboundedReceiver<EditorCommand>) {
}
```
We can combine coroutines with [Fermi](https://docs.rs/fermi/latest/fermi/index.html) to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state *within* a task and then simply update the "view" values stored in Atoms. It cannot be understated how powerful this technique is: we get all the perks of native Rust tasks with the optimizations and ergonomics of global state. This means your *actual* state does not need to be tied up in a system like Fermi or Redux – the only Atoms that need to exist are those that are used to drive the display/UI.
We can combine coroutines with [Fermi](https://docs.rs/fermi/latest/fermi/index.html) to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state _within_ a task and then simply update the "view" values stored in Atoms. It cannot be understated how powerful this technique is: we get all the perks of native Rust tasks with the optimizations and ergonomics of global state. This means your _actual_ state does not need to be tied up in a system like Fermi or Redux – the only Atoms that need to exist are those that are used to drive the display/UI.
```rust
```rust, no_run
static USERNAME: Atom<String> = Atom(|_| "default".to_string());
fn app(cx: Scope) -> Element {
@@ -169,7 +166,7 @@ fn Banner(cx: Scope) -> Element {
Now, in our sync service, we can structure our state however we want. We only need to update the view values when ready.
```rust
```rust, no_run
use futures_util::stream::StreamExt;
enum SyncAction {
@@ -198,7 +195,7 @@ async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
Coroutine handles are automatically injected through the context API. You can use the `use_coroutine_handle` hook with the message type as a generic to fetch a handle.
```rust
```rust, no_run
fn Child(cx: Scope) -> Element {
let sync_task = use_coroutine_handle::<SyncAction>(cx);
+41
View File
@@ -0,0 +1,41 @@
# UseEffect
[`use_effect`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_effect.html) lets you run a callback that returns a future, which will be re-run when it's [dependencies](#dependencies) change. This is useful to syncrhonize with external events.
## Dependencies
You can make the callback re-run when some value changes. For example, you might want to fetch a user's data only when the user id changes. You can provide a tuple of "dependencies" to the hook. It will automatically re-run it when any of those dependencies change.
## Example
```rust, no_run
#[inline_props]
fn Profile(cx: Scope, id: usize) -> Element {
let name = use_state(cx, || None);
// Only fetch the user data when the id changes.
use_effect(cx, (id,), |(id,)| {
to_owned![name];
async move {
let user = fetch_user(id).await;
name.set(user.name);
}
});
// Because the dependencies are empty, this will only run once.
// An empty tuple is always equal to an empty tuple.
use_effect(cx, (), |()| async move {
println!("Hello, World!");
});
let name = name.get().clone().unwrap_or("Loading...".to_string());
render!(
p { "{name}" }
)
}
fn app(cx: Scope) -> Element {
render!(Profile { id: 0 })
}
```
+4 -6
View File
@@ -4,21 +4,20 @@
For example, we can make an API request (using [reqwest](https://docs.rs/reqwest/latest/reqwest/index.html)) inside `use_future`:
```rust
```rust, no_run
{{#include ../../../examples/use_future.rs:use_future}}
```
The code inside `use_future` will be submitted to the Dioxus scheduler once the component has rendered.
We can use `.value()` to get the result of the future. On the first run, since there's no data ready when the component loads, its value will be `None`. However, once the future is finished, the component will be re-rendered and the value will now be `Some(...)`, containing the return value of the closure.
We can use `.value()` to get the result of the future. On the first run, since there's no data ready when the component loads, its value will be `None`. However, once the future is finished, the component will be re-rendered and the value will now be `Some(...)`, containing the return value of the closure.
We can then render that result:
```rust
```rust, no_run
{{#include ../../../examples/use_future.rs:render}}
```
## Restarting the Future
The `UseFuture` handle provides a `restart` method. It can be used to execute the future again, producing a new value.
@@ -27,7 +26,6 @@ The `UseFuture` handle provides a `restart` method. It can be used to execute th
Often, you will need to run the future again every time some value (e.g. a prop) changes. Rather than calling `restart` manually, you can provide a tuple of "dependencies" to the hook. It will automatically re-run the future when any of those dependencies change. Example:
```rust
```rust, no_run
{{#include ../../../examples/use_future.rs:dependency}}
```