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

+45
View File
@@ -0,0 +1,45 @@
//! Checkboxes and radio groups.
//!
//! Checkboxes expose their checked state through the FormEvent — read `evt.checked()`.
//! Radio groups share a `name` attribute so only one option can be selected at a time;
//! the selected value comes through `evt.value()`.
use dioxus::prelude::*;
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
let mut subscribed = use_signal(|| false);
let mut color = use_signal(|| "red".to_string());
rsx! {
h2 { "Checkbox" }
label {
input {
r#type: "checkbox",
checked: subscribed(),
oninput: move |evt| subscribed.set(evt.checked()),
}
" Subscribe to updates"
}
p { "Subscribed: {subscribed}" }
h2 { "Radio group" }
// Radio inputs sharing a `name` are mutually exclusive
for value in ["red", "green", "blue"] {
label {
input {
r#type: "radio",
name: "color",
value,
checked: color() == value,
oninput: move |evt| color.set(evt.value()),
}
" {value}"
}
}
p { color: "{color}", "Chosen color: {color}" }
}
}
+52
View File
@@ -0,0 +1,52 @@
//! Accepting children on a component.
//!
//! Any component can take a `children: Element` prop. Anything nested inside the component
//! in `rsx!` is passed in as `children`. This is how you build reusable layout and container
//! components like cards, modals, or page shells.
use dioxus::prelude::*;
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
rsx! {
Card {
h2 { "A card" }
p { "Any rsx! can go inside — including other components." }
}
Card {
p { "A second card with different children." }
button { "Click me" }
}
Section { title: "Reusable layout",
p { "The section component takes both a title prop and children." }
}
}
}
#[component]
fn Card(children: Element) -> Element {
rsx! {
div {
border: "1px solid #ccc",
border_radius: "8px",
padding: "12px",
margin: "8px 0",
{children}
}
}
}
#[component]
fn Section(title: String, children: Element) -> Element {
rsx! {
section {
h3 { "{title}" }
{children}
}
}
}
+46
View File
@@ -0,0 +1,46 @@
//! Defining and using components.
//!
//! A component is just a function annotated with `#[component]` that returns an `Element`.
//! Its arguments are its props — Dioxus generates a builder so you can construct the
//! component inside `rsx!` with a struct-like syntax.
use dioxus::prelude::*;
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
rsx! {
h1 { "Greetings" }
Greeting { name: "Alice" }
Greeting { name: "Bob", excited: true }
Greeting { name: "Charlie", excited: false }
h2 { "User cards" }
UserCard { name: "Ada Lovelace", role: "Mathematician" }
UserCard { name: "Grace Hopper", role: "Computer Scientist" }
}
}
// Components must start with an uppercase letter.
// Props are taken as named arguments — `name` is required, and `excited` has a default.
#[component]
fn Greeting(name: String, #[props(default)] excited: bool) -> Element {
let punctuation = if excited { "!" } else { "." };
rsx! {
p { "Hello, {name}{punctuation}" }
}
}
// Components can take any Clone + PartialEq type as a prop.
#[component]
fn UserCard(name: String, role: String) -> Element {
rsx! {
div { border: "1px solid #ccc", padding: "8px", margin: "4px",
strong { "{name}" }
" — "
em { "{role}" }
}
}
}
@@ -0,0 +1,52 @@
//! Conditional rendering.
//!
//! `if`, `else`, and `match` all work directly inside `rsx!`. You can also return different
//! `rsx!` blocks early from a component function. Elements outside of a conditional will
//! render normally — only the branched portion swaps as state changes.
use dioxus::prelude::*;
fn main() {
dioxus::launch(app);
}
#[derive(PartialEq, Clone, Copy)]
enum Status {
Loading,
Ready,
Error,
}
fn app() -> Element {
let mut logged_in = use_signal(|| false);
let mut status = use_signal(|| Status::Loading);
rsx! {
h1 { "Conditional rendering" }
// `if/else` chains render the matching branch
if logged_in() {
p { "Welcome back!" }
} else {
p { "Please log in." }
}
button { onclick: move |_| logged_in.toggle(), "Toggle login" }
// `match` works too — each arm returns rsx!
div {
match status() {
Status::Loading => rsx! { p { "⏳ Loading..." } },
Status::Ready => rsx! { p { "✅ Ready!" } },
Status::Error => rsx! { p { "❌ Something went wrong." } },
}
}
button { onclick: move |_| status.set(Status::Loading), "Loading" }
button { onclick: move |_| status.set(Status::Ready), "Ready" }
button { onclick: move |_| status.set(Status::Error), "Error" }
// `if` without an `else` is also valid — nothing renders when false
if logged_in() && status() == Status::Ready {
p { "You are logged in and the app is ready." }
}
}
}
+22
View File
@@ -0,0 +1,22 @@
//! The classic counter — the "hello world" of reactive UI.
//!
//! `use_signal` creates a reactive value. Whenever the signal changes, any part of the UI
//! that reads it will re-render automatically. Signals are copy-cheap, so you can freely
//! move them into event handlers and closures.
use dioxus::prelude::*;
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
let mut count = use_signal(|| 0);
rsx! {
h1 { "Count: {count}" }
button { onclick: move |_| count += 1, "Increment" }
button { onclick: move |_| count -= 1, "Decrement" }
button { onclick: move |_| count.set(0), "Reset" }
}
}
@@ -0,0 +1,54 @@
//! Applying classes and styles based on state.
//!
//! Any attribute can interpolate signals and expressions using `"{value}"` formatting.
//! For `class:`, you can also build the class string inline with if/else, and individual
//! CSS properties can be set as named attributes like `color:` or `background_color:`.
use dioxus::prelude::*;
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
let mut is_active = use_signal(|| false);
let mut size = use_signal(|| 16);
// Build a class string from state
let classes = if is_active() { "btn active" } else { "btn" };
rsx! {
// CSS properties are plain named attributes — snake_case in Rust, kebab-case in CSS
style { {STYLE} }
button {
class: classes,
onclick: move |_| is_active.toggle(),
"Toggle active (currently {is_active})"
}
// Or compute the class string ahead of time for anything more complex
div {
class: if is_active() { "box highlight" } else { "box" },
"This box changes class with the button."
}
hr {}
// Style values can be driven directly by signals
p {
font_size: "{size}px",
color: if size() > 24 { "crimson" } else { "black" },
"Font size: {size}px"
}
button { onclick: move |_| size += 2, "Bigger" }
button { onclick: move |_| size -= 2, "Smaller" }
}
}
const STYLE: &str = r#"
.btn { padding: 6px 12px; border: 1px solid #888; border-radius: 4px; }
.btn.active { background: #4CAF50; color: white; border-color: #2E7D32; }
.box { padding: 12px; margin-top: 12px; border: 1px dashed #999; }
.box.highlight { background: #fff3cd; border-color: #d39e00; }
"#;
@@ -0,0 +1,45 @@
//! Passing event handlers as props.
//!
//! Components can accept callbacks using `EventHandler<T>`, which is Dioxus's equivalent
//! of React's `onClick` prop or a closure. Call the handler with `.call(value)` from the
//! child; the parent decides what happens when the event fires.
use dioxus::prelude::*;
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
let mut log = use_signal(Vec::<String>::new);
rsx! {
h1 { "Custom event handlers" }
// The parent owns the handler — the child just fires it
FancyButton {
label: "Say hi",
onpress: move |name: String| log.write().push(format!("Hi, {name}!")),
}
FancyButton {
label: "Say bye",
onpress: move |name: String| log.write().push(format!("Bye, {name}!")),
}
ul {
for entry in log.iter() {
li { "{entry}" }
}
}
}
}
#[component]
fn FancyButton(label: String, onpress: EventHandler<String>) -> Element {
rsx! {
button {
onclick: move |_| onpress.call(label.clone()),
"{label}"
}
}
}
+88
View File
@@ -0,0 +1,88 @@
//! Handling form submission and deserializing into a struct.
//!
//! `evt.parsed_values::<T>()` takes the form's named inputs and deserializes them straight
//! into your own type via serde. This skips the chore of pulling each field out of the
//! values map by hand — just define a struct whose fields match the `name` attributes,
//! derive `Deserialize`, and you're done.
use dioxus::prelude::*;
use serde::Deserialize;
fn main() {
dioxus::launch(app);
}
// Field names must match the `name` attribute on each input
#[derive(Deserialize, Debug, Clone)]
struct Signup {
username: String,
email: String,
role: String,
// Checkboxes are present in the form data only when checked, so model optional fields
// with Option<String>. Use `#[serde(default)]` so the field doesn't fail to deserialize
// when the input is missing.
#[serde(default)]
newsletter: Option<String>,
}
fn app() -> Element {
let mut submitted = use_signal(|| None::<Signup>);
rsx! {
h1 { "Sign up" }
form {
onsubmit: move |evt| {
// prevent_default stops the browser from navigating the page on submit
evt.prevent_default();
// Deserialize directly into our struct
match evt.parsed_values::<Signup>() {
Ok(signup) => submitted.set(Some(signup)),
Err(err) => eprintln!("failed to parse form: {err}"),
}
},
div {
label { r#for: "username", "Username: " }
input { r#type: "text", id: "username", name: "username", required: true }
}
div {
label { r#for: "email", "Email: " }
input { r#type: "email", id: "email", name: "email", required: true }
}
div {
label { r#for: "role", "Role: " }
select { id: "role", name: "role",
option { value: "developer", "Developer" }
option { value: "designer", "Designer" }
option { value: "manager", "Manager" }
}
}
div {
label { r#for: "newsletter",
input { r#type: "checkbox", id: "newsletter", name: "newsletter", value: "yes" }
" Subscribe to newsletter"
}
}
button { r#type: "submit", "Submit" }
}
if let Some(signup) = submitted() {
h2 { "Welcome, {signup.username}!" }
ul {
li { "Email: {signup.email}" }
li { "Role: {signup.role}" }
li { "Newsletter: {signup.newsletter.is_some()}" }
}
details {
summary { "Raw struct" }
pre { "{signup:#?}" }
}
}
}
}
+43
View File
@@ -0,0 +1,43 @@
//! Controlled text inputs.
//!
//! To read text from an input, attach an `oninput` handler and call `evt.value()` to get the
//! current value as a `String`. Binding the `value:` attribute to a signal makes the input
//! "controlled" — the signal is the single source of truth, so clearing it clears the input.
use dioxus::prelude::*;
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
let mut name = use_signal(String::new);
let mut message = use_signal(|| "Type something below.".to_string());
rsx! {
h1 { "Hello, {name}!" }
input {
r#type: "text",
placeholder: "Your name",
// Binding `value` to a signal makes this a controlled input
value: "{name}",
oninput: move |evt| name.set(evt.value()),
}
button {
onclick: move |_| name.set(String::new()),
"Clear"
}
hr {}
p { "{message}" }
textarea {
rows: 3,
cols: 40,
value: "{message}",
oninput: move |evt| message.set(evt.value()),
}
}
}
+53
View File
@@ -0,0 +1,53 @@
//! Rendering lists.
//!
//! Use a `for` loop inside `rsx!` to render a list from an iterator. When list items can
//! be reordered, added, or removed, provide a stable `key` so Dioxus can efficiently
//! reuse existing elements rather than re-rendering them all.
use dioxus::prelude::*;
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
let mut items = use_signal(|| {
vec![
"apple".to_string(),
"banana".to_string(),
"cherry".to_string(),
]
});
let mut next_id = use_signal(|| 0);
rsx! {
h1 { "Grocery list" }
button {
onclick: move |_| {
let n = next_id();
next_id += 1;
items.push(format!("item {n}"));
},
"Add item"
}
button {
onclick: move |_| { items.pop(); },
"Remove last"
}
ul {
// The `key` is a stable identifier used to match up items across renders.
// Here we use the item's text, but in real apps prefer a database id.
for item in items.iter() {
li { key: "{item}", "{item}" }
}
}
// You can also use iterator adapters with .map() for anything more complex than a for loop
p { "Uppercased:" }
ul {
{items.iter().map(|item| rsx! { li { "{item.to_uppercase()}" } })}
}
}
}
@@ -0,0 +1,27 @@
//! Setting the page title.
//!
//! The `Title` component renders a `<title>` tag into the document head. On the web it
//! sets the browser tab title; on desktop it becomes the window title. Because it's a
//! regular component, its contents can be driven by signals and update live.
use dioxus::prelude::*;
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
let mut unread = use_signal(|| 0);
rsx! {
// The title updates every time the signal changes
Title {
if unread() > 0 { "({unread}) Inbox" } else { "Inbox" }
}
h1 { "Inbox" }
p { "Unread messages: {unread}" }
button { onclick: move |_| unread += 1, "New message" }
button { onclick: move |_| unread.set(0), "Mark all read" }
}
}
@@ -0,0 +1,62 @@
//! Styling driven by state.
//!
//! Style attributes are interpolated the same way text is — read from signals inside `"{...}"`
//! or pass a conditional expression directly. The element re-renders only when the read
//! signals change, so you can freely animate colors, sizes, and transforms this way.
use dioxus::prelude::*;
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
let mut hue = use_signal(|| 200.0_f32);
let mut size = use_signal(|| 100.0_f32);
let mut rotated = use_signal(|| false);
rsx! {
h1 { "Dynamic styling" }
div {
width: "{size}px",
height: "{size}px",
background_color: "hsl({hue}, 80%, 60%)",
transition: "transform 300ms ease",
transform: if rotated() { "rotate(45deg)" } else { "rotate(0deg)" },
margin: "20px 0",
}
div {
label { "Hue: {hue:.0}" }
input {
r#type: "range",
min: "0",
max: "360",
value: "{hue}",
oninput: move |evt| {
if let Ok(v) = evt.value().parse::<f32>() {
hue.set(v);
}
},
}
}
div {
label { "Size: {size:.0}px" }
input {
r#type: "range",
min: "40",
max: "300",
value: "{size}",
oninput: move |evt| {
if let Ok(v) = evt.value().parse::<f32>() {
size.set(v);
}
},
}
}
button { onclick: move |_| rotated.toggle(), "Toggle rotation" }
}
}
+23
View File
@@ -0,0 +1,23 @@
//! Setting a favicon.
//!
//! Use `document::Link` with `rel: "icon"` to point the browser at a favicon. Combined with
//! `asset!`, the favicon is bundled into the app and served with the same path-mangling
//! and cache-busting as any other asset.
use dioxus::prelude::*;
const FAVICON: Asset = asset!("/examples/assets/logo.png");
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
rsx! {
// Link is a generic head element — for a favicon set rel="icon"
document::Link { rel: "icon", href: FAVICON }
h1 { "Check the browser tab!" }
p { "The favicon is loaded from examples/assets/logo.png." }
}
}
+44
View File
@@ -0,0 +1,44 @@
//! Loading fonts — from a CDN and bundled with the app.
//!
//! Two common ways to get a custom typeface onto the page:
//!
//! 1. Point `Stylesheet` at a hosted CSS file like Google Fonts — the browser fetches the
//! font files on demand.
//! 2. Bundle a font file into the app with `asset!`, then declare an `@font-face` rule
//! that points at it. This avoids the network round-trip and makes the font available
//! offline.
use dioxus::prelude::*;
// A local font, bundled into the app. The asset macro fingerprints and copies the file
// into the final build; at runtime `INTER` formats to the public URL.
const INTER: Asset = asset!("/examples/assets/Inter-Regular.woff2");
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
rsx! {
// Approach 1 — request a stylesheet from the Google Fonts CDN
Stylesheet { href: "https://fonts.googleapis.com/css2?family=Lobster&family=Roboto+Mono&display=swap" }
// Approach 2 — register a bundled font with an inline @font-face rule.
// `{INTER}` interpolates to the asset's resolved URL at runtime.
style { "@font-face {{ font-family: 'Inter'; src: url('{INTER}') format('woff2'); font-display: swap; }}" }
h1 { font_family: "'Lobster', cursive", font_size: "48px",
"From Google Fonts"
}
p { font_family: "'Roboto Mono', monospace",
"This paragraph also comes from the CDN."
}
hr {}
h2 { font_family: "'Inter', sans-serif", "Bundled Inter font" }
p { font_family: "'Inter', sans-serif",
"This paragraph uses Inter, shipped with the app — no network request needed."
}
}
}
@@ -0,0 +1,44 @@
//! Inline styles.
//!
//! For quick one-off styling, every CSS property is available as an attribute on HTML
//! elements — use snake_case in Rust (`background_color`) for the kebab-case CSS property
//! (`background-color`). You can also set the raw `style:` attribute with a full string.
use dioxus::prelude::*;
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
rsx! {
// Each CSS property is just a named attribute
div {
background_color: "#1e3a8a",
color: "white",
padding: "20px",
border_radius: "8px",
font_family: "system-ui, sans-serif",
h1 { "Inline styling" }
p { "Property names are snake_case here, kebab-case in real CSS." }
}
// The raw `style` attribute accepts any CSS string
div {
style: "margin-top: 12px; border: 2px dashed tomato; padding: 10px;",
"Use `style:` when you want a plain CSS string."
}
// Interpolation works too — great for values driven by Rust expressions
for i in 1..=5 {
span {
display: "inline-block",
width: "32px",
height: "32px",
margin: "4px",
background_color: "hsl({i * 60}, 70%, 55%)",
}
}
}
}
@@ -1,20 +0,0 @@
//! This example shows how to add metadata to the page with the Meta component
use dioxus::prelude::*;
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
rsx! {
// You can use the Meta component to render a meta tag into the head of the page
// Meta tags are useful to provide information about the page to search engines and social media sites
// This example sets up meta tags for the open graph protocol for social media previews
Meta { property: "og:title", content: "My Site" }
Meta { property: "og:type", content: "website" }
Meta { property: "og:url", content: "https://www.example.com" }
Meta { property: "og:image", content: "https://example.com/image.jpg" }
Meta { name: "description", content: "My Site is a site" }
}
}
+34
View File
@@ -0,0 +1,34 @@
//! Adding a script to the document head.
//!
//! `document::Script` injects a `<script>` tag into the page. Use it to embed an inline
//! snippet via children, or load an external script by passing a `src`. Scripts are
//! deduplicated by their `src`, so mounting the same script from multiple components is safe.
//!
//! Prefer `Script` over `link {}` for better integration with SSR and pre-rendering.
use dioxus::prelude::*;
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
rsx! {
// Inline script — children must be a single text node
document::Script { "console.log('Hello from an inline script!');" }
// External script, loaded with `defer` so it runs after parsing
document::Script { src: "https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js", defer: true }
h1 { "Script example" }
p { "Open your browser's console to see the inline script's output." }
button {
onclick: move |_| {
// Call out to the confetti library loaded above
document::eval("confetti && confetti();");
},
"🎉 Confetti"
}
}
}
+24
View File
@@ -0,0 +1,24 @@
//! Linking a CSS stylesheet.
//!
//! `Stylesheet` renders a `<link rel="stylesheet">` into the document head. Pairing it with
//! the `asset!` macro is the most common way to style a Dioxus app: the CSS file is bundled,
//! fingerprinted for cache-busting, and can be hot-reloaded during development.
use dioxus::prelude::*;
const STYLE: Asset = asset!("/examples/assets/stylesheet.css");
fn main() {
dioxus::launch(app);
}
fn app() -> Element {
rsx! {
// Placing the Stylesheet anywhere in the tree is fine — it's collected into <head>
Stylesheet { href: STYLE }
h1 { "Styled with a CSS file" }
p { "All styling comes from stylesheet.css, loaded via the asset! macro." }
button { "A styled button" }
}
}
+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)" }
}
}
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
body {
font-family: system-ui, sans-serif;
background: #fafafa;
color: #222;
max-width: 640px;
margin: 40px auto;
padding: 0 16px;
}
h1 { color: #1e3a8a; }
button {
padding: 6px 14px;
border: 1px solid #1e3a8a;
border-radius: 4px;
background: white;
color: #1e3a8a;
cursor: pointer;
}
button:hover { background: #1e3a8a; color: white; }