update the dioxus cratesio readme, add many more examples (#5501)
* update the dioxus cratesio readme * fix example * fix header * add a bunch more examples
This commit is contained in:
1 parent
74e7d20f80
commit
cd54f5953b
30 files changed
+1403
-235
No files matched your search
@@ -0,0 +1,59 @@
|
||||
//! Writing your own hook.
|
||||
//!
|
||||
//! A hook is just a function that calls other hooks. By convention it starts with `use_`
|
||||
//! and returns whatever state handle the caller needs. Extracting hooks is how you reuse
|
||||
//! stateful logic across components — like a counter, a timer, or a data fetcher.
|
||||
//!
|
||||
//! Hooks rely on being called in the same order every render, so never call them inside
|
||||
//! `if`, `match`, or loops.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
// Each call to our custom hook produces an independent counter
|
||||
let mut counter_a = use_counter(0);
|
||||
let mut counter_b = use_counter(100);
|
||||
|
||||
// And a more complex hook that exposes multiple actions
|
||||
let (name, set_name, reverse) = use_reversible_name("Ada");
|
||||
|
||||
rsx! {
|
||||
h2 { "Two counters from one hook" }
|
||||
p { "A: {counter_a}" }
|
||||
button { onclick: move |_| counter_a += 1, "Increment A" }
|
||||
|
||||
p { "B: {counter_b}" }
|
||||
button { onclick: move |_| counter_b += 1, "Increment B" }
|
||||
|
||||
h2 { "Reversible name" }
|
||||
input {
|
||||
value: "{name}",
|
||||
oninput: move |evt| set_name.call(evt.value()),
|
||||
}
|
||||
button { onclick: move |_| reverse.call(()), "Reverse" }
|
||||
p { "Name: {name}" }
|
||||
}
|
||||
}
|
||||
|
||||
// A minimal hook — returns a signal initialized with a value
|
||||
fn use_counter(initial: i32) -> Signal<i32> {
|
||||
use_signal(|| initial)
|
||||
}
|
||||
|
||||
// Hooks can return any combination of signals and callbacks
|
||||
fn use_reversible_name(initial: &'static str) -> (Signal<String>, Callback<String>, Callback<()>) {
|
||||
let mut name = use_signal(|| initial.to_string());
|
||||
|
||||
// `use_callback` memoizes a closure so children don't think it changed every render
|
||||
let set_name = use_callback(move |value: String| name.set(value));
|
||||
let reverse = use_callback(move |_| {
|
||||
let reversed = name.read().chars().rev().collect::<String>();
|
||||
name.set(reversed);
|
||||
});
|
||||
|
||||
(name, set_name, reverse)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//! Sharing state between sibling components by lifting it up.
|
||||
//!
|
||||
//! When two components need to read or edit the same value, put the signal in their
|
||||
//! common parent and pass it down as a prop. This keeps the data flow obvious: the parent
|
||||
//! owns the state, the children receive a `Signal<T>` handle, and mutations show up
|
||||
//! everywhere the signal is read.
|
||||
//!
|
||||
//! For state that needs to be shared across many layers, reach for `use_context_provider`
|
||||
//! instead — see `context_api.rs`.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
// The parent owns the signal; both children share it
|
||||
let counter = use_signal(|| 0);
|
||||
|
||||
rsx! {
|
||||
h1 { "Two views of the same counter" }
|
||||
Controls { counter }
|
||||
Display { counter }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Controls(mut counter: Signal<i32>) -> Element {
|
||||
rsx! {
|
||||
button { onclick: move |_| counter += 1, "Increment" }
|
||||
button { onclick: move |_| counter -= 1, "Decrement" }
|
||||
button { onclick: move |_| counter.set(0), "Reset" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Display(counter: Signal<i32>) -> Element {
|
||||
rsx! {
|
||||
p { "Count is: {counter}" }
|
||||
p {
|
||||
if counter() > 0 { "Positive" }
|
||||
else if counter() < 0 { "Negative" }
|
||||
else { "Zero" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//! Passing a read-only signal to a child component.
|
||||
//!
|
||||
//! `ReadSignal<T>` is a signal that can only be read, not written. Taking it as a prop is
|
||||
//! the idiomatic way to tell a child "you can subscribe to this value, but you can't change
|
||||
//! it." Any `Signal<T>` or `Memo<T>` coerces into a `ReadSignal<T>` automatically, so the
|
||||
//! parent passes them in unchanged.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
let doubled = use_memo(move || count() * 2);
|
||||
|
||||
rsx! {
|
||||
h1 { "Parent" }
|
||||
button { onclick: move |_| count += 1, "Increment" }
|
||||
|
||||
// Both Signal and Memo implement Into<ReadSignal<T>>
|
||||
Display { label: "Count", value: count }
|
||||
Display { label: "Doubled", value: doubled }
|
||||
|
||||
// You can also pass a plain value — it'll be wrapped automatically
|
||||
Display { label: "Constant", value: 42 }
|
||||
}
|
||||
}
|
||||
|
||||
// Taking `ReadSignal<i32>` means the child can subscribe but can't mutate the source
|
||||
#[component]
|
||||
fn Display(label: String, value: ReadSignal<i32>) -> Element {
|
||||
rsx! {
|
||||
p { "{label}: {value}" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//! A signal that holds a struct.
|
||||
//!
|
||||
//! Signals don't care what they hold — you can put any `'static` type inside. To mutate
|
||||
//! a field on a struct signal, call `.write()` and the guard derefs to the inner value.
|
||||
//! Any read of the signal in the UI will re-render when you drop the write guard.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct User {
|
||||
name: String,
|
||||
age: u32,
|
||||
admin: bool,
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut user = use_signal(|| User {
|
||||
name: "Ada".to_string(),
|
||||
age: 36,
|
||||
admin: false,
|
||||
});
|
||||
|
||||
rsx! {
|
||||
h1 { "User profile" }
|
||||
p { "Name: {user.read().name}" }
|
||||
p { "Age: {user.read().age}" }
|
||||
p { "Admin: {user.read().admin}" }
|
||||
|
||||
button {
|
||||
// .write() gives a mutable guard — assign to a single field
|
||||
onclick: move |_| user.write().age += 1,
|
||||
"Happy birthday!"
|
||||
}
|
||||
button {
|
||||
onclick: move |_| {
|
||||
let mut guard = user.write();
|
||||
guard.admin = !guard.admin;
|
||||
},
|
||||
"Toggle admin"
|
||||
}
|
||||
|
||||
input {
|
||||
value: "{user.read().name}",
|
||||
oninput: move |evt| user.write().name = evt.value(),
|
||||
}
|
||||
|
||||
// Replacing the whole value works too
|
||||
button {
|
||||
onclick: move |_| user.set(User {
|
||||
name: "Grace".to_string(),
|
||||
age: 85,
|
||||
admin: true,
|
||||
}),
|
||||
"Reset to Grace"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//! Running side effects when state changes.
|
||||
//!
|
||||
//! `use_effect` runs a closure after every render where one of the signals it reads has
|
||||
//! changed. It's the escape hatch for talking to code outside of Dioxus — logging, syncing
|
||||
//! to `localStorage`, updating the `document.title`, tweaking imperative APIs, and so on.
|
||||
//!
|
||||
//! For pure derivations of other signals, reach for `use_memo` instead — effects are meant
|
||||
//! for side effects, not for computing values used in the UI.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
let mut name = use_signal(|| "world".to_string());
|
||||
|
||||
// Runs after every render where `count` changes — use this for side effects
|
||||
use_effect(move || {
|
||||
println!("count is now {count}");
|
||||
});
|
||||
|
||||
// An effect can read multiple signals. It re-runs whenever any of them change.
|
||||
use_effect(move || {
|
||||
println!("greeting changed: hello, {name} — count is {count}");
|
||||
});
|
||||
|
||||
rsx! {
|
||||
h1 { "Hello, {name}! ({count})" }
|
||||
|
||||
button { onclick: move |_| count += 1, "Increment count" }
|
||||
input {
|
||||
value: "{name}",
|
||||
oninput: move |evt| name.set(evt.value()),
|
||||
}
|
||||
p { "Check your terminal — each change fires an effect." }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//! Deriving values with `use_memo`.
|
||||
//!
|
||||
//! `use_memo` caches the result of a closure and re-runs it only when one of the signals
|
||||
//! it reads changes. It's how you compute values derived from state without doing the work
|
||||
//! on every render, and without introducing another `use_signal` that you have to keep
|
||||
//! in sync manually.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut first = use_signal(|| "Ada".to_string());
|
||||
let mut last = use_signal(|| "Lovelace".to_string());
|
||||
|
||||
// Recomputes only when `first` or `last` change
|
||||
let full_name = use_memo(move || format!("{first} {last}"));
|
||||
|
||||
// Memos compose — derive from other memos or signals
|
||||
let initials = use_memo(move || {
|
||||
let name = full_name();
|
||||
name.split_whitespace()
|
||||
.filter_map(|w| w.chars().next())
|
||||
.collect::<String>()
|
||||
});
|
||||
|
||||
rsx! {
|
||||
h1 { "{full_name}" }
|
||||
p { "Initials: {initials}" }
|
||||
|
||||
label { "First: " }
|
||||
input { value: "{first}", oninput: move |e| first.set(e.value()) }
|
||||
|
||||
label { "Last: " }
|
||||
input { value: "{last}", oninput: move |e| last.set(e.value()) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//! Signals that hold a collection.
|
||||
//!
|
||||
//! `Signal<Vec<T>>` is the workhorse for dynamic lists. Dioxus exposes `push`, `pop`,
|
||||
//! `remove`, `clear`, and `retain` directly on the signal so you don't have to call
|
||||
//! `.write()` for the common cases. For anything else, `.write()` returns a guard that
|
||||
//! derefs to the underlying `Vec`.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut todos = use_signal(|| vec!["learn dioxus".to_string(), "build an app".to_string()]);
|
||||
let mut draft = use_signal(String::new);
|
||||
|
||||
rsx! {
|
||||
h1 { "Todo list" }
|
||||
|
||||
input {
|
||||
placeholder: "New todo...",
|
||||
value: "{draft}",
|
||||
oninput: move |evt| draft.set(evt.value()),
|
||||
}
|
||||
button {
|
||||
onclick: move |_| {
|
||||
if !draft.read().is_empty() {
|
||||
todos.push(draft());
|
||||
draft.set(String::new());
|
||||
}
|
||||
},
|
||||
"Add"
|
||||
}
|
||||
|
||||
ul {
|
||||
for (index, todo) in todos.read().iter().enumerate() {
|
||||
li { key: "{index}",
|
||||
"{todo} "
|
||||
button {
|
||||
onclick: move |_| { todos.remove(index); },
|
||||
"×"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button { onclick: move |_| todos.clear(), "Clear all" }
|
||||
button {
|
||||
// `.retain` accepts a predicate — here we drop any todo containing "x"
|
||||
onclick: move |_| todos.retain(|t| !t.contains('x')),
|
||||
"Drop todos with 'x'"
|
||||
}
|
||||
button {
|
||||
// For anything custom, `.write()` gives a mutable guard to the Vec
|
||||
onclick: move |_| todos.write().sort(),
|
||||
"Sort alphabetically"
|
||||
}
|
||||
|
||||
p { "{todos.len()} todo(s)" }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user