Rework Guide (#486)
* Move getting started to Guide * Clean up summary and getting started * Shorten intro by moving stuff to individual platform docs * Make intro even shorter, summarize features * Further cleanup; move development-related docs to separate section * Make `guide` a crate. Turn code samples into examples so that we can check if they compile * Rewrite "Describing the UI": * Focus on RSX syntax (interactivity covered in later chapters); make sure samples are tested; concise language * Move some "special attribute" samples to the `examples` directory * Simplify introduction to components * Simplify introduction to component props * Document Prop features; add code samples * Simplify component children docs * Interactivity: better introduction to events * Hooks: better introduction * Remove outdated doc * Introducs use_ref * Simplify User Input chapter * Document event handler props * Meme editor example * Meme editor walkthrough * Add dark mode example * Guide for context; dark mode example * Guide: custom hooks * Guide: conditional rendering * Guide: rendering lists * Guide: rendering lists + keys * Move remaining infor from Reference to guide * Delete reference book
This commit is contained in:
1 parent
92a8b7c75f
commit
6c323e9fc5
152 files changed
+2827
-5052
No files matched your search
@@ -0,0 +1 @@
|
||||
Some of these examples (e.g. web) cannot be run. The code samples are here mostly so that we can easily check that they compile using `cargo test`.
|
||||
@@ -0,0 +1,71 @@
|
||||
#![allow(non_snake_case, unused)]
|
||||
|
||||
//! This example shows what *not* to do
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {}
|
||||
|
||||
fn AntipatternNestedFragments(cx: Scope<()>) -> Element {
|
||||
// ANCHOR: nested_fragments
|
||||
// ❌ Don't unnecessarily nest fragments
|
||||
let _ = cx.render(rsx!(
|
||||
Fragment {
|
||||
Fragment {
|
||||
Fragment {
|
||||
Fragment {
|
||||
Fragment {
|
||||
div { "Finally have a real node!" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
// ✅ Render shallow structures
|
||||
cx.render(rsx!(
|
||||
div { "Finally have a real node!" }
|
||||
))
|
||||
// ANCHOR_END: nested_fragments
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct NoKeysProps {
|
||||
data: HashMap<u32, String>,
|
||||
}
|
||||
|
||||
fn AntipatternNoKeys(cx: Scope<NoKeysProps>) -> Element {
|
||||
// ANCHOR: iter_keys
|
||||
let data: &HashMap<_, _> = &cx.props.data;
|
||||
|
||||
// ❌ No keys
|
||||
cx.render(rsx! {
|
||||
ul {
|
||||
data.values().map(|value| rsx!(
|
||||
li { "List item: {value}" }
|
||||
))
|
||||
}
|
||||
});
|
||||
|
||||
// ❌ Using index as keys
|
||||
cx.render(rsx! {
|
||||
ul {
|
||||
cx.props.data.values().enumerate().map(|(index, value)| rsx!(
|
||||
li { key: "{index}", "List item: {value}" }
|
||||
))
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ Using unique IDs as keys:
|
||||
cx.render(rsx! {
|
||||
ul {
|
||||
cx.props.data.iter().map(|(key, value)| rsx!(
|
||||
li { key: "{key}", "List item: {value}" }
|
||||
))
|
||||
}
|
||||
})
|
||||
// ANCHOR_END: iter_keys
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
// ANCHOR: boolean_attribute
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
hidden: "false",
|
||||
"hello"
|
||||
}
|
||||
})
|
||||
// ANCHOR_END: boolean_attribute
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
// ANCHOR: App
|
||||
fn App(cx: Scope) -> Element {
|
||||
let hello = "Hello Dioxus!";
|
||||
|
||||
cx.render(rsx!(TitleCard { title: hello }))
|
||||
}
|
||||
// ANCHOR_END: App
|
||||
|
||||
// ANCHOR: TitleCard
|
||||
#[derive(Props)]
|
||||
struct TitleCardProps<'a> {
|
||||
title: &'a str,
|
||||
}
|
||||
|
||||
fn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element {
|
||||
cx.render(rsx! {
|
||||
h1 { "{cx.props.title}" }
|
||||
})
|
||||
}
|
||||
// ANCHOR_END: TitleCard
|
||||
@@ -0,0 +1,36 @@
|
||||
// ANCHOR: all
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
// ANCHOR: Clickable_usage
|
||||
cx.render(rsx! {
|
||||
Clickable {
|
||||
href: "https://www.youtube.com/watch?v=C-M2hs3sXGo",
|
||||
"How to " i {"not"} " be seen"
|
||||
}
|
||||
})
|
||||
// ANCHOR_END: Clickable_usage
|
||||
}
|
||||
|
||||
// ANCHOR: Clickable
|
||||
#[derive(Props)]
|
||||
struct ClickableProps<'a> {
|
||||
href: &'a str,
|
||||
children: Element<'a>,
|
||||
}
|
||||
|
||||
fn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
|
||||
cx.render(rsx!(
|
||||
a {
|
||||
href: "{cx.props.href}",
|
||||
class: "fancy-button",
|
||||
&cx.props.children
|
||||
}
|
||||
))
|
||||
}
|
||||
// ANCHOR_END: Clickable
|
||||
@@ -0,0 +1,37 @@
|
||||
// ANCHOR: all
|
||||
#![allow(non_snake_case, unused)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
// ANCHOR: Clickable_usage
|
||||
cx.render(rsx! {
|
||||
Clickable {
|
||||
href: "https://www.youtube.com/watch?v=C-M2hs3sXGo",
|
||||
"How to " i {"not"} " be seen"
|
||||
}
|
||||
})
|
||||
// ANCHOR_END: Clickable_usage
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct ClickableProps<'a> {
|
||||
href: &'a str,
|
||||
children: Element<'a>,
|
||||
}
|
||||
|
||||
// ANCHOR: Clickable
|
||||
fn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
|
||||
match cx.props.children {
|
||||
Some(VNode::Text(_)) => {
|
||||
todo!("render some stuff")
|
||||
}
|
||||
_ => {
|
||||
todo!("render some other stuff")
|
||||
}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: Clickable
|
||||
@@ -0,0 +1,36 @@
|
||||
// ANCHOR: all
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
// ANCHOR: Clickable_usage
|
||||
cx.render(rsx! {
|
||||
Clickable {
|
||||
href: "https://www.youtube.com/watch?v=C-M2hs3sXGo",
|
||||
body: cx.render(rsx!("How to " i {"not"} " be seen")),
|
||||
}
|
||||
})
|
||||
// ANCHOR_END: Clickable_usage
|
||||
}
|
||||
|
||||
// ANCHOR: Clickable
|
||||
#[derive(Props)]
|
||||
struct ClickableProps<'a> {
|
||||
href: &'a str,
|
||||
body: Element<'a>,
|
||||
}
|
||||
|
||||
fn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
|
||||
cx.render(rsx!(
|
||||
a {
|
||||
href: "{cx.props.href}",
|
||||
class: "fancy-button",
|
||||
&cx.props.body
|
||||
}
|
||||
))
|
||||
}
|
||||
// ANCHOR_END: Clickable
|
||||
@@ -0,0 +1,34 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
// ANCHOR: App
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Likes {
|
||||
score: 42,
|
||||
},
|
||||
})
|
||||
}
|
||||
// ANCHOR_END: App
|
||||
|
||||
// ANCHOR: Likes
|
||||
// Remember: Owned props must implement `PartialEq`!
|
||||
#[derive(PartialEq, Props)]
|
||||
struct LikesProps {
|
||||
score: i32,
|
||||
}
|
||||
|
||||
fn Likes(cx: Scope<LikesProps>) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
"This post has ",
|
||||
b { "{cx.props.score}" },
|
||||
" likes"
|
||||
}
|
||||
})
|
||||
}
|
||||
// ANCHOR_END: Likes
|
||||
@@ -0,0 +1,113 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
// ANCHOR: OptionalProps_usage
|
||||
Title {
|
||||
title: "Some Title",
|
||||
},
|
||||
Title {
|
||||
title: "Some Title",
|
||||
subtitle: "Some Subtitle",
|
||||
},
|
||||
// Providing an Option explicitly won't compile though:
|
||||
// Title {
|
||||
// title: "Some Title",
|
||||
// subtitle: None,
|
||||
// },
|
||||
// ANCHOR_END: OptionalProps_usage
|
||||
|
||||
// ANCHOR: ExplicitOption_usage
|
||||
ExplicitOption {
|
||||
title: "Some Title",
|
||||
subtitle: None,
|
||||
},
|
||||
ExplicitOption {
|
||||
title: "Some Title",
|
||||
subtitle: Some("Some Title"),
|
||||
},
|
||||
// This won't compile:
|
||||
// ExplicitOption {
|
||||
// title: "Some Title",
|
||||
// },
|
||||
// ANCHOR_END: ExplicitOption_usage
|
||||
|
||||
// ANCHOR: DefaultComponent_usage
|
||||
DefaultComponent {
|
||||
number: 5,
|
||||
},
|
||||
DefaultComponent {},
|
||||
// ANCHOR_END: DefaultComponent_usage
|
||||
|
||||
// ANCHOR: IntoComponent_usage
|
||||
IntoComponent {
|
||||
string: "some &str",
|
||||
},
|
||||
// ANCHOR_END: IntoComponent_usage
|
||||
})
|
||||
}
|
||||
|
||||
// ANCHOR: OptionalProps
|
||||
#[derive(Props)]
|
||||
struct OptionalProps<'a> {
|
||||
title: &'a str,
|
||||
subtitle: Option<&'a str>,
|
||||
}
|
||||
|
||||
fn Title<'a>(cx: Scope<'a, OptionalProps>) -> Element<'a> {
|
||||
return cx.render(rsx!(h1{
|
||||
"{cx.props.title}: ",
|
||||
[cx.props.subtitle.unwrap_or("No subtitle provided")],
|
||||
}));
|
||||
}
|
||||
// ANCHOR_END: OptionalProps
|
||||
|
||||
// ANCHOR: ExplicitOption
|
||||
#[derive(Props)]
|
||||
struct ExplicitOptionProps<'a> {
|
||||
title: &'a str,
|
||||
#[props(!optional)]
|
||||
subtitle: Option<&'a str>,
|
||||
}
|
||||
|
||||
fn ExplicitOption<'a>(cx: Scope<'a, ExplicitOptionProps>) -> Element<'a> {
|
||||
return cx.render(rsx!(h1{
|
||||
"{cx.props.title}: ",
|
||||
[cx.props.subtitle.unwrap_or("No subtitle provided")],
|
||||
}));
|
||||
}
|
||||
// ANCHOR_END: ExplicitOption
|
||||
|
||||
// ANCHOR: DefaultComponent
|
||||
#[derive(PartialEq, Props)]
|
||||
struct DefaultProps {
|
||||
// default to 42 when not provided
|
||||
#[props(default = 42)]
|
||||
number: i64,
|
||||
}
|
||||
|
||||
fn DefaultComponent(cx: Scope<DefaultProps>) -> Element {
|
||||
return cx.render(rsx!(h1{
|
||||
"{cx.props.number}",
|
||||
}));
|
||||
}
|
||||
// ANCHOR_END: DefaultComponent
|
||||
|
||||
// ANCHOR: IntoComponent
|
||||
#[derive(PartialEq, Props)]
|
||||
struct IntoProps {
|
||||
#[props(into)]
|
||||
string: String,
|
||||
}
|
||||
|
||||
fn IntoComponent(cx: Scope<IntoProps>) -> Element {
|
||||
return cx.render(rsx!(h1{
|
||||
"{cx.props.string}",
|
||||
}));
|
||||
}
|
||||
// ANCHOR_END: IntoComponent
|
||||
@@ -0,0 +1,24 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
// ANCHOR: App
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
About {},
|
||||
About {},
|
||||
})
|
||||
}
|
||||
// ANCHOR_END: App
|
||||
|
||||
// ANCHOR: About
|
||||
pub fn About(cx: Scope) -> Element {
|
||||
cx.render(rsx!(p {
|
||||
b {"Dioxus Labs"}
|
||||
" An Open Source project dedicated to making Rust UI wonderful."
|
||||
}))
|
||||
}
|
||||
// ANCHOR_END: About
|
||||
@@ -0,0 +1,60 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
pub fn App(cx: Scope) -> Element {
|
||||
let is_logged_in = use_state(&cx, || false);
|
||||
|
||||
cx.render(rsx!(LogIn {
|
||||
is_logged_in: **is_logged_in,
|
||||
on_log_in: |_| is_logged_in.set(true),
|
||||
on_log_out: |_| is_logged_in.set(false),
|
||||
}))
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn LogIn<'a>(
|
||||
cx: Scope<'a>,
|
||||
is_logged_in: bool,
|
||||
on_log_in: EventHandler<'a>,
|
||||
on_log_out: EventHandler<'a>,
|
||||
) -> Element<'a> {
|
||||
// ANCHOR: if_else
|
||||
if *is_logged_in {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
"Welcome!",
|
||||
button {
|
||||
onclick: move |_| on_log_out.call(()),
|
||||
"Log Out",
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
onclick: move |_| on_log_in.call(()),
|
||||
"Log In",
|
||||
}
|
||||
})
|
||||
}
|
||||
// ANCHOR_END: if_else
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn LogInWarning(cx: Scope, is_logged_in: bool) -> Element {
|
||||
// ANCHOR: conditional_none
|
||||
if *is_logged_in {
|
||||
return None;
|
||||
}
|
||||
|
||||
cx.render(rsx! {
|
||||
a {
|
||||
"You must be logged in to comment"
|
||||
}
|
||||
})
|
||||
// ANCHOR_END: conditional_none
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
// ANCHOR: dangerous_inner_html
|
||||
// this should come from a trusted source
|
||||
let contents = "live <b>dangerously</b>";
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
dangerous_inner_html: "{contents}",
|
||||
}
|
||||
})
|
||||
// ANCHOR_END: dangerous_inner_html
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
// ANCHOR: rsx
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
onclick: move |event| println!("Clicked! Event: {event:?}"),
|
||||
"click me!"
|
||||
}
|
||||
})
|
||||
// ANCHOR_END: rsx
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::events::MouseEvent;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
// ANCHOR: usage
|
||||
cx.render(rsx! {
|
||||
FancyButton {
|
||||
on_click: move |event| println!("Clicked! {event:?}")
|
||||
}
|
||||
})
|
||||
// ANCHOR_END: usage
|
||||
}
|
||||
|
||||
// ANCHOR: component_with_handler
|
||||
#[derive(Props)]
|
||||
pub struct FancyButtonProps<'a> {
|
||||
on_click: EventHandler<'a, MouseEvent>,
|
||||
}
|
||||
|
||||
pub fn FancyButton<'a>(cx: Scope<'a, FancyButtonProps<'a>>) -> Element<'a> {
|
||||
cx.render(rsx!(button {
|
||||
class: "fancy-button",
|
||||
onclick: move |evt| cx.props.on_click.call(evt),
|
||||
"click me pls."
|
||||
}))
|
||||
}
|
||||
// ANCHOR_END: component_with_handler
|
||||
@@ -0,0 +1,24 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
// ANCHOR: rsx
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
onclick: move |_event| {},
|
||||
"outer",
|
||||
button {
|
||||
onclick: move |event| {
|
||||
// now, outer won't be triggered
|
||||
event.cancel_bubble();
|
||||
},
|
||||
"inner"
|
||||
}
|
||||
}
|
||||
})
|
||||
// ANCHOR_END: rsx
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
// ANCHOR: prevent_default
|
||||
cx.render(rsx! {
|
||||
input {
|
||||
prevent_default: "oninput",
|
||||
prevent_default: "onclick",
|
||||
}
|
||||
})
|
||||
// ANCHOR_END: prevent_default
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// ANCHOR: all
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
// ANCHOR: component
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
"Hello, world!"
|
||||
}
|
||||
})
|
||||
}
|
||||
// ANCHOR_END: component
|
||||
// ANCHOR_END: all
|
||||
@@ -0,0 +1,20 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::tui::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
height: "10px",
|
||||
background_color: "red",
|
||||
justify_content: "center",
|
||||
align_items: "center",
|
||||
|
||||
"Hello world!"
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// todo remove deprecated
|
||||
#![allow(non_snake_case, deprecated)]
|
||||
|
||||
use dioxus::events::{KeyCode, KeyboardEvent};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus::tui::TuiContext;
|
||||
|
||||
fn main() {
|
||||
dioxus::tui::launch_cfg(
|
||||
App,
|
||||
dioxus::tui::Config::new()
|
||||
.without_ctrl_c_quit()
|
||||
// Some older terminals only support 16 colors or ANSI colors
|
||||
// If your terminal is one of these, change this to BaseColors or ANSI
|
||||
.with_rendering_mode(dioxus::tui::RenderingMode::Rgb),
|
||||
);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
let tui_ctx: TuiContext = cx.consume_context().unwrap();
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
height: "10px",
|
||||
background_color: "red",
|
||||
justify_content: "center",
|
||||
align_items: "center",
|
||||
onkeydown: move |k: KeyboardEvent| if let KeyCode::Q = k.data.key_code {
|
||||
tui_ctx.quit();
|
||||
},
|
||||
|
||||
"Hello world!"
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::web::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
"Hello, world!"
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
let you_are_happy = true;
|
||||
let you_know_it = false;
|
||||
|
||||
// ANCHOR: conditional
|
||||
// ❌ don't call hooks in conditionals!
|
||||
// We must ensure that the same hooks will be called every time
|
||||
// But `if` statements only run if the conditional is true!
|
||||
// So we might violate rule 2.
|
||||
if you_are_happy && you_know_it {
|
||||
let something = use_state(&cx, || "hands");
|
||||
println!("clap your {something}")
|
||||
}
|
||||
|
||||
// ✅ instead, *always* call use_state
|
||||
// You can put other stuff in the conditional though
|
||||
let something = use_state(&cx, || "hands");
|
||||
if you_are_happy && you_know_it {
|
||||
println!("clap your {something}")
|
||||
}
|
||||
// ANCHOR_END: conditional
|
||||
|
||||
// ANCHOR: closure
|
||||
// ❌ don't call hooks inside closures!
|
||||
// We can't guarantee that the closure, if used, will be called at the same time every time
|
||||
let _a = || {
|
||||
let b = use_state(&cx, || 0);
|
||||
b.get()
|
||||
};
|
||||
|
||||
// ✅ instead, move hook `b` outside
|
||||
let b = use_state(&cx, || 0);
|
||||
let _a = || b.get();
|
||||
// ANCHOR_END: closure
|
||||
|
||||
let names: Vec<&str> = vec![];
|
||||
|
||||
// ANCHOR: loop
|
||||
// `names` is a Vec<&str>
|
||||
|
||||
// ❌ Do not use hooks in loops!
|
||||
// In this case, if the length of the Vec changes, we break rule 2
|
||||
for _name in &names {
|
||||
let is_selected = use_state(&cx, || false);
|
||||
println!("selected: {is_selected}");
|
||||
}
|
||||
|
||||
// ✅ Instead, use a hashmap with use_ref
|
||||
let selection_map = use_ref(&cx, || HashMap::<&str, bool>::new());
|
||||
|
||||
for name in &names {
|
||||
let is_selected = selection_map.read()[name];
|
||||
println!("selected: {is_selected}");
|
||||
}
|
||||
// ANCHOR_END: loop
|
||||
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {}
|
||||
|
||||
struct AppSettings {}
|
||||
|
||||
// ANCHOR: wrap_context
|
||||
fn use_settings(cx: &ScopeState) -> UseSharedState<AppSettings> {
|
||||
use_context::<AppSettings>(&cx).expect("App settings not provided")
|
||||
}
|
||||
// ANCHOR_END: wrap_context
|
||||
@@ -0,0 +1,18 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
// ANCHOR: component
|
||||
fn App(cx: Scope) -> Element {
|
||||
let mut count = use_state(&cx, || 0);
|
||||
|
||||
cx.render(rsx!(
|
||||
h1 { "High-Five counter: {count}" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
))
|
||||
}
|
||||
// ANCHOR_END: component
|
||||
@@ -0,0 +1,24 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
// ANCHOR: component
|
||||
fn App(cx: Scope) -> Element {
|
||||
// ANCHOR: use_state_calls
|
||||
let mut count_a = use_state(&cx, || 0);
|
||||
let mut count_b = use_state(&cx, || 0);
|
||||
// ANCHOR_END: use_state_calls
|
||||
|
||||
cx.render(rsx!(
|
||||
h1 { "Counter_a: {count_a}" }
|
||||
button { onclick: move |_| count_a += 1, "a++" }
|
||||
button { onclick: move |_| count_a -= 1, "a--" }
|
||||
h1 { "Counter_b: {count_b}" }
|
||||
button { onclick: move |_| count_b += 1, "b++" }
|
||||
button { onclick: move |_| count_b -= 1, "b--" }
|
||||
))
|
||||
}
|
||||
// ANCHOR_END: component
|
||||
@@ -0,0 +1,23 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
// ANCHOR: component
|
||||
fn App(cx: Scope) -> Element {
|
||||
let list = use_ref(&cx, || Vec::new());
|
||||
let list_formatted = format!("{:?}", *list.read());
|
||||
|
||||
cx.render(rsx!(
|
||||
p { "Current list: {list_formatted}" }
|
||||
button {
|
||||
onclick: move |event| {
|
||||
list.write().push(event)
|
||||
},
|
||||
"Click me!"
|
||||
}
|
||||
))
|
||||
}
|
||||
// ANCHOR_END: component
|
||||
@@ -0,0 +1,21 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
// ANCHOR: component
|
||||
fn App(cx: Scope) -> Element {
|
||||
let name = use_state(&cx, || "bob".to_string());
|
||||
|
||||
cx.render(rsx! {
|
||||
input {
|
||||
// we tell the component what to render
|
||||
value: "{name}",
|
||||
// and what to do when the value changes
|
||||
oninput: move |evt| name.set(evt.value.clone()),
|
||||
}
|
||||
})
|
||||
}
|
||||
// ANCHOR_END: component
|
||||
@@ -0,0 +1,22 @@
|
||||
#![allow(non_snake_case)]
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
// ANCHOR: component
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
form {
|
||||
onsubmit: move |event| {
|
||||
println!("Submitted! {event:?}")
|
||||
},
|
||||
input { name: "name", },
|
||||
input { name: "age", },
|
||||
input { name: "date", },
|
||||
input { r#type: "submit", },
|
||||
}
|
||||
})
|
||||
}
|
||||
// ANCHOR_END: component
|
||||
@@ -0,0 +1,105 @@
|
||||
// ANCHOR: all
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::events::FormEvent;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(MemeEditor);
|
||||
}
|
||||
|
||||
// ANCHOR: meme_editor
|
||||
fn MemeEditor(cx: Scope) -> Element {
|
||||
let container_style = r"
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin: 0 auto;
|
||||
width: fit-content;
|
||||
";
|
||||
|
||||
let caption = use_state(&cx, || "me waiting for my rust code to compile".to_string());
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
style: "{container_style}",
|
||||
h1 { "Meme Editor" },
|
||||
Meme {
|
||||
caption: caption,
|
||||
},
|
||||
CaptionEditor {
|
||||
caption: caption,
|
||||
on_input: move |event: FormEvent| {caption.set(event.value.clone());},
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
// ANCHOR_END: meme_editor
|
||||
|
||||
// ANCHOR: meme_component
|
||||
#[inline_props]
|
||||
fn Meme<'a>(cx: Scope<'a>, caption: &'a str) -> Element<'a> {
|
||||
let container_style = r#"
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
"#;
|
||||
|
||||
let caption_container_style = r#"
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 16px 8px;
|
||||
"#;
|
||||
|
||||
let caption_style = r"
|
||||
font-size: 32px;
|
||||
margin: 0;
|
||||
color: white;
|
||||
text-align: center;
|
||||
";
|
||||
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
style: "{container_style}",
|
||||
img {
|
||||
src: "https://i.imgflip.com/2zh47r.jpg",
|
||||
height: "500px",
|
||||
},
|
||||
div {
|
||||
style: "{caption_container_style}",
|
||||
p {
|
||||
style: "{caption_style}",
|
||||
"{caption}"
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
// ANCHOR_END: meme_component
|
||||
|
||||
// ANCHOR: caption_editor
|
||||
#[inline_props]
|
||||
fn CaptionEditor<'a>(
|
||||
cx: Scope<'a>,
|
||||
caption: &'a str,
|
||||
on_input: EventHandler<'a, FormEvent>,
|
||||
) -> Element<'a> {
|
||||
let input_style = r"
|
||||
border: none;
|
||||
background: cornflowerblue;
|
||||
padding: 8px 16px;
|
||||
margin: 0;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
";
|
||||
|
||||
cx.render(rsx!(input {
|
||||
style: "{input_style}",
|
||||
value: "{caption}",
|
||||
oninput: move |event| on_input.call(event),
|
||||
}))
|
||||
}
|
||||
// ANCHOR_END: caption_editor
|
||||
|
||||
// ANCHOR_END: all
|
||||
@@ -0,0 +1,184 @@
|
||||
// ANCHOR: all
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::events::FormEvent;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
// ANCHOR: DarkMode_struct
|
||||
struct DarkMode(bool);
|
||||
// ANCHOR_END: DarkMode_struct
|
||||
|
||||
pub fn App(cx: Scope) -> Element {
|
||||
// ANCHOR: context_provider
|
||||
use_context_provider(&cx, || DarkMode(false));
|
||||
// ANCHOR_END: context_provider
|
||||
|
||||
let is_dark_mode = use_is_dark_mode(&cx);
|
||||
|
||||
let wrapper_style = if is_dark_mode {
|
||||
r"
|
||||
background: #222;
|
||||
min-height: 100vh;
|
||||
"
|
||||
} else {
|
||||
r""
|
||||
};
|
||||
|
||||
cx.render(rsx!(div {
|
||||
style: "{wrapper_style}",
|
||||
DarkModeToggle {},
|
||||
MemeEditor {},
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn use_is_dark_mode(cx: &ScopeState) -> bool {
|
||||
// ANCHOR: use_context
|
||||
let dark_mode_context = use_context::<DarkMode>(&cx);
|
||||
// ANCHOR_END: use_context
|
||||
|
||||
dark_mode_context
|
||||
.map(|context| context.read().0)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
// ANCHOR: toggle
|
||||
pub fn DarkModeToggle(cx: Scope) -> Element {
|
||||
let dark_mode = use_context::<DarkMode>(&cx)?;
|
||||
|
||||
let style = if dark_mode.read().0 {
|
||||
"color:white"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
cx.render(rsx!(label {
|
||||
style: "{style}",
|
||||
"Dark Mode",
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
oninput: move |event| {
|
||||
let is_enabled = event.value == "true";
|
||||
dark_mode.write().0 = is_enabled;
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
// ANCHOR_END: toggle
|
||||
|
||||
// ANCHOR: meme_editor
|
||||
fn MemeEditor(cx: Scope) -> Element {
|
||||
let is_dark_mode = use_is_dark_mode(&cx);
|
||||
let heading_style = if is_dark_mode { "color: white" } else { "" };
|
||||
|
||||
let container_style = r"
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin: 0 auto;
|
||||
width: fit-content;
|
||||
";
|
||||
|
||||
let caption = use_state(&cx, || "me waiting for my rust code to compile".to_string());
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
style: "{container_style}",
|
||||
h1 {
|
||||
style: "{heading_style}",
|
||||
"Meme Editor"
|
||||
},
|
||||
Meme {
|
||||
caption: caption,
|
||||
},
|
||||
CaptionEditor {
|
||||
caption: caption,
|
||||
on_input: move |event: FormEvent| {caption.set(event.value.clone());},
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
// ANCHOR_END: meme_editor
|
||||
|
||||
// ANCHOR: meme_component
|
||||
#[inline_props]
|
||||
fn Meme<'a>(cx: Scope<'a>, caption: &'a str) -> Element<'a> {
|
||||
let container_style = r"
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
";
|
||||
|
||||
let caption_container_style = r"
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 16px 8px;
|
||||
";
|
||||
|
||||
let caption_style = r"
|
||||
font-size: 32px;
|
||||
margin: 0;
|
||||
color: white;
|
||||
text-align: center;
|
||||
";
|
||||
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
style: "{container_style}",
|
||||
img {
|
||||
src: "https://i.imgflip.com/2zh47r.jpg",
|
||||
height: "500px",
|
||||
},
|
||||
div {
|
||||
style: "{caption_container_style}",
|
||||
p {
|
||||
style: "{caption_style}",
|
||||
"{caption}"
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
// ANCHOR_END: meme_component
|
||||
|
||||
// ANCHOR: caption_editor
|
||||
#[inline_props]
|
||||
fn CaptionEditor<'a>(
|
||||
cx: Scope<'a>,
|
||||
caption: &'a str,
|
||||
on_input: EventHandler<'a, FormEvent>,
|
||||
) -> Element<'a> {
|
||||
let is_dark_mode = use_is_dark_mode(&cx);
|
||||
|
||||
let colors = if is_dark_mode {
|
||||
r"
|
||||
background: cornflowerblue;
|
||||
color: white;
|
||||
"
|
||||
} else {
|
||||
r"
|
||||
background: #def;
|
||||
color: black;
|
||||
"
|
||||
};
|
||||
|
||||
let input_style = r"
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
margin: 0;
|
||||
border-radius: 4px;
|
||||
";
|
||||
|
||||
cx.render(rsx!(input {
|
||||
style: "{input_style}{colors}",
|
||||
value: "{caption}",
|
||||
oninput: move |event| on_input.call(event),
|
||||
}))
|
||||
}
|
||||
// ANCHOR_END: caption_editor
|
||||
|
||||
// ANCHOR_END: all
|
||||
@@ -0,0 +1,60 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
struct Comment {
|
||||
content: String,
|
||||
id: usize,
|
||||
}
|
||||
|
||||
pub fn App(cx: Scope) -> Element {
|
||||
// ANCHOR: render_list
|
||||
let comment_field = use_state(&cx, || String::new());
|
||||
let mut next_id = use_state(&cx, || 0);
|
||||
let comments = use_ref(&cx, || Vec::<Comment>::new());
|
||||
|
||||
let comments_lock = comments.read();
|
||||
let comments_rendered = comments_lock.iter().map(|comment| {
|
||||
cx.render(rsx!(CommentComponent {
|
||||
key: "{comment.id}",
|
||||
comment: comment.clone(),
|
||||
}))
|
||||
});
|
||||
|
||||
cx.render(rsx!(
|
||||
form {
|
||||
onsubmit: move |_| {
|
||||
comments.write().push(Comment {
|
||||
content: comment_field.get().clone(),
|
||||
id: *next_id.get(),
|
||||
});
|
||||
next_id += 1;
|
||||
|
||||
comment_field.set(String::new());
|
||||
},
|
||||
input {
|
||||
value: "{comment_field}",
|
||||
oninput: |event| comment_field.set(event.value.clone()),
|
||||
}
|
||||
input {
|
||||
r#type: "submit",
|
||||
}
|
||||
},
|
||||
comments_rendered,
|
||||
))
|
||||
// ANCHOR_END: render_list
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn CommentComponent(cx: Scope, comment: Comment) -> Element {
|
||||
cx.render(rsx!(div {
|
||||
"Comment by anon:",
|
||||
p { "{comment.content}" }
|
||||
button { "Reply" },
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
pub fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
Empty {},
|
||||
Children {},
|
||||
Fragments {},
|
||||
Attributes {},
|
||||
VariableAttributes {},
|
||||
CustomAttributes {},
|
||||
Formatting {},
|
||||
Expression {},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn Empty(cx: Scope) -> Element {
|
||||
// ANCHOR: empty
|
||||
cx.render(rsx!(div {}))
|
||||
// ANCHOR_END: empty
|
||||
}
|
||||
|
||||
pub fn Children(cx: Scope) -> Element {
|
||||
// ANCHOR: children
|
||||
cx.render(rsx!(ol {
|
||||
li {"First Item"}
|
||||
li {"Second Item"}
|
||||
li {"Third Item"}
|
||||
}))
|
||||
// ANCHOR_END: children
|
||||
}
|
||||
|
||||
pub fn Fragments(cx: Scope) -> Element {
|
||||
// ANCHOR: fragments
|
||||
cx.render(rsx!(
|
||||
p {"First Item"},
|
||||
p {"Second Item"},
|
||||
Fragment {
|
||||
span { "a group" },
|
||||
span { "of three" },
|
||||
span { "items" },
|
||||
}
|
||||
))
|
||||
// ANCHOR_END: fragments
|
||||
}
|
||||
|
||||
pub fn Attributes(cx: Scope) -> Element {
|
||||
// ANCHOR: attributes
|
||||
cx.render(rsx!(a {
|
||||
href: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||
class: "primary_button",
|
||||
"Log In"
|
||||
}))
|
||||
// ANCHOR_END: attributes
|
||||
}
|
||||
|
||||
pub fn VariableAttributes(cx: Scope) -> Element {
|
||||
// ANCHOR: variable_attributes
|
||||
let written_in_rust = true;
|
||||
let button_type = "button";
|
||||
cx.render(rsx!(button {
|
||||
disabled: "{written_in_rust}",
|
||||
class: "{button_type}",
|
||||
"Rewrite it in rust"
|
||||
}))
|
||||
// ANCHOR_END: variable_attributes
|
||||
}
|
||||
|
||||
pub fn CustomAttributes(cx: Scope) -> Element {
|
||||
// ANCHOR: custom_attributes
|
||||
cx.render(rsx!(b {
|
||||
"customAttribute": "value",
|
||||
"Rust is Cool"
|
||||
}))
|
||||
// ANCHOR_END: custom_attributes
|
||||
}
|
||||
|
||||
pub fn Formatting(cx: Scope) -> Element {
|
||||
// ANCHOR: formatting
|
||||
let coordinates = (42, 0);
|
||||
let country = "es";
|
||||
cx.render(rsx!(div {
|
||||
class: "country-{country}",
|
||||
"Coordinates: {coordinates:?}",
|
||||
// arbitrary expressions are allowed,
|
||||
// as long as they don't contain `{}`
|
||||
div {
|
||||
"{country.to_uppercase()}"
|
||||
},
|
||||
div {
|
||||
"{7*6}"
|
||||
},
|
||||
}))
|
||||
// ANCHOR_END: formatting
|
||||
}
|
||||
|
||||
pub fn Expression(cx: Scope) -> Element {
|
||||
// ANCHOR: expression
|
||||
let text = "Dioxus";
|
||||
cx.render(rsx!(span {
|
||||
[text.to_uppercase()]
|
||||
}))
|
||||
// ANCHOR_END: expression
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#![allow(non_snake_case, unused)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
// ANCHOR: spawn
|
||||
let logged_in = use_state(&cx, || false);
|
||||
|
||||
let log_in = move |_| {
|
||||
cx.spawn({
|
||||
let logged_in = logged_in.to_owned();
|
||||
|
||||
async move {
|
||||
let resp = reqwest::Client::new()
|
||||
.post("http://example.com/login")
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match resp {
|
||||
Ok(_data) => {
|
||||
println!("Login successful!");
|
||||
logged_in.set(true);
|
||||
}
|
||||
Err(_err) => {
|
||||
println!(
|
||||
"Login failed - you need a login server running on localhost:8080."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
onclick: log_in,
|
||||
"Login",
|
||||
}
|
||||
})
|
||||
// ANCHOR_END: spawn
|
||||
}
|
||||
|
||||
pub fn Tokio(cx: Scope) -> Element {
|
||||
let _ = || {
|
||||
// ANCHOR: tokio
|
||||
cx.spawn(async {
|
||||
let _ = tokio::spawn(async {}).await;
|
||||
|
||||
let _ = tokio::task::spawn_local(async {
|
||||
// some !Send work
|
||||
})
|
||||
.await;
|
||||
});
|
||||
// ANCHOR_END: tokio
|
||||
};
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn ToOwnedMacro(cx: Scope) -> Element {
|
||||
let count = use_state(&cx, || 0);
|
||||
let age = use_state(&cx, || 0);
|
||||
let name = use_state(&cx, || 0);
|
||||
let description = use_state(&cx, || 0);
|
||||
|
||||
let _ = || {
|
||||
// ANCHOR: to_owned_macro
|
||||
use dioxus::core::to_owned;
|
||||
|
||||
cx.spawn({
|
||||
to_owned![count, age, name, description];
|
||||
async move {
|
||||
// ...
|
||||
}
|
||||
});
|
||||
// ANCHOR_END: to_owned_macro
|
||||
};
|
||||
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#![allow(non_snake_case, unused)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct ApiResponse {
|
||||
#[serde(rename = "message")]
|
||||
image_url: String,
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
// ANCHOR: use_future
|
||||
let future = use_future(&cx, (), |_| async move {
|
||||
reqwest::get("https://dog.ceo/api/breeds/image/random")
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<ApiResponse>()
|
||||
.await
|
||||
});
|
||||
// ANCHOR_END: use_future
|
||||
|
||||
// ANCHOR: render
|
||||
cx.render(match future.value() {
|
||||
Some(Ok(response)) => rsx! {
|
||||
button {
|
||||
onclick: move |_| future.restart(),
|
||||
"Click to fetch another doggo"
|
||||
}
|
||||
div {
|
||||
img {
|
||||
max_width: "500px",
|
||||
max_height: "500px",
|
||||
src: "{response.image_url}",
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(Err(_)) => rsx! { div { "Loading dogs failed" } },
|
||||
None => rsx! { div { "Loading dogs..." } },
|
||||
})
|
||||
// ANCHOR_END: render
|
||||
}
|
||||
|
||||
#[inline_props]
|
||||
fn RandomDog(cx: Scope, breed: String) -> Element {
|
||||
// ANCHOR: dependency
|
||||
let future = use_future(&cx, (breed,), |(breed,)| async move {
|
||||
reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<ApiResponse>()
|
||||
.await
|
||||
});
|
||||
// ANCHOR_END: dependency
|
||||
|
||||
None
|
||||
}
|
||||
Reference in new issue
Block a user