This PR contains just the breaking changes required for the upcoming store implementation. It adds two new primitives: Mutable mapped signals (without a different subscriber list). This lets you scope a writable value and pass it to a child component. It requires two closures to map the immutable and mutable references to the inner signal. With stores, these closures will be derived Boxed readable and writable values (ReadSignal, and WriteSignal). Any readable or writable types can be converted to a read signal or write signal with the boxed method or built in types are automatically converted into the boxed variants when passing into props * map signals mutably * boxed signals * remove mutable borrow type from boxed writable * improve map mut example * restore boxed_mut helper * make boxed readable/writable types copy * Move away from the ReadOnlySignal alias in examples * automatically convert Write * Fix some tests * ReadSignal and WriteSignal * move read only signal alias * make maps copy * store prototype * implement derive macro * Handle generics in the derive macro * Make Selector boxable * derive boxed conversion * Get rid of store type * Remove hardcoded type from derive macro * selector -> store * Refactor store implementation * more vec methods * todo mvc store example * Implement Storable for HashMap * fix write * restore sorting logic in todomvc * derive readable and writable * improve todomvc store example * Result store * Option store * collect dead subscribers * fix clippy and formatting * recognize some common foreign types automatically * fix foreign stores in props * slice and array selectors * clean up store example * remove stores * writable is child owned * fix typo * fix clippy * fix doc tests
89 lines
3.1 KiB
Rust
89 lines
3.1 KiB
Rust
//! This example demonstrates a simple app that fetches a list of dog breeds and displays a random dog.
|
|
//!
|
|
//! The app uses the `use_signal` and `use_resource` hooks to manage state and fetch data from the Dog API.
|
|
//! `use_resource` is basically an async version of use_memo - it will track dependencies between .await points
|
|
//! and then restart the future if any of the dependencies change.
|
|
//!
|
|
//! You should generally throttle requests to an API - either client side or server side. This example doesn't do that
|
|
//! since it's unlikely the user will rapidly cause new fetches, but it's something to keep in mind.
|
|
|
|
use dioxus::prelude::*;
|
|
use std::collections::HashMap;
|
|
|
|
fn main() {
|
|
dioxus::launch(app);
|
|
}
|
|
|
|
fn app() -> Element {
|
|
// Breed is a signal that will be updated when the user clicks a breed in the list
|
|
// `shiba` is just a default that we know will exist. We could also use a `None` instead
|
|
let mut breed = use_signal(|| "shiba".to_string());
|
|
|
|
// Fetch the list of breeds from the Dog API
|
|
// Since there are no dependencies, this will never restart
|
|
let breed_list = use_resource(move || async move {
|
|
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
|
struct ListBreeds {
|
|
message: HashMap<String, Vec<String>>,
|
|
}
|
|
|
|
let list = reqwest::get("https://dog.ceo/api/breeds/list/all")
|
|
.await
|
|
.unwrap()
|
|
.json::<ListBreeds>()
|
|
.await;
|
|
|
|
let Ok(breeds) = list else {
|
|
return rsx! { "error fetching breeds" };
|
|
};
|
|
|
|
rsx! {
|
|
for cur_breed in breeds.message.keys().take(20).cloned() {
|
|
button { onclick: move |_| breed.set(cur_breed.clone()),
|
|
"{cur_breed}"
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// We can use early returns in dioxus!
|
|
// Traditional signal-based libraries can't do this since the scope is by default non-reactive
|
|
let Some(breed_list) = breed_list() else {
|
|
return rsx! { "loading breeds..." };
|
|
};
|
|
|
|
rsx! {
|
|
h1 { "Select a dog breed: {breed}" }
|
|
BreedPic { breed }
|
|
div { width: "400px", {breed_list} }
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
fn BreedPic(breed: WriteSignal<String>) -> Element {
|
|
// This resource will restart whenever the breed changes
|
|
let mut fut = use_resource(move || async move {
|
|
#[derive(serde::Deserialize, Debug)]
|
|
struct DogApi {
|
|
message: String,
|
|
}
|
|
|
|
reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
|
|
.await
|
|
.unwrap()
|
|
.json::<DogApi>()
|
|
.await
|
|
});
|
|
|
|
match fut.read_unchecked().as_ref() {
|
|
Some(Ok(resp)) => rsx! {
|
|
div {
|
|
button { onclick: move |_| fut.restart(), padding: "5px", background_color: "gray", color: "white", border_radius: "5px", "Click to fetch another doggo" }
|
|
img { max_width: "500px", max_height: "500px", src: "{resp.message}" }
|
|
}
|
|
},
|
|
Some(Err(_)) => rsx! { "loading image failed" },
|
|
None => rsx! { "loading image..." },
|
|
}
|
|
}
|