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:
Jonathan Kelley authored and GitHub committed 2026-04-21 17:07:48 -07:00
1 parent 74e7d20f80
commit cd54f5953b
30 files changed
+1403 -235

No files matched your search

+59
View File
@@ -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" }
}
}
}
+37
View File
@@ -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"
}
}
}
+40
View File
@@ -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." }
}
}
+39
View File
@@ -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()) }
}
}
+62
View File
@@ -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)" }
}
}