diff --git a/Cargo.toml b/Cargo.toml index 483e418a5..3c3f78b7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ members = [ "packages/native-core", "packages/native-core-macro", "packages/rsx-prelude", + "docs/guide", ] [dev-dependencies] diff --git a/docs/guide/Cargo.toml b/docs/guide/Cargo.toml new file mode 100644 index 000000000..50edb6d7e --- /dev/null +++ b/docs/guide/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "dioxus-guide" +version = "0.0.1" +edition = "2021" +description = "Dioxus guide, including testable examples" +license = "MIT/Apache-2.0" + +[dev-dependencies] +dioxus = { path = "../..", features = ["desktop", "web", "ssr", "router", "fermi", "tui"] } +serde = { version = "1.0.138", features=["derive"] } +reqwest = { version = "0.11.11", features = ["json"] } +tokio = { version = "1.19.2" , features=[]} \ No newline at end of file diff --git a/docs/guide/book.toml b/docs/guide/book.toml index eabbe6f2b..91def1354 100644 --- a/docs/guide/book.toml +++ b/docs/guide/book.toml @@ -1,6 +1,6 @@ [book] title = "Dioxus Documentation" -description = "Create book from markdown files. Like Gitbook but implemented in Rust" +description = "Get started with Dioxus, a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust" authors = ["Jonathan Kelley"] language = "en" diff --git a/docs/guide/examples/Readme.md b/docs/guide/examples/Readme.md new file mode 100644 index 000000000..891fc8fe4 --- /dev/null +++ b/docs/guide/examples/Readme.md @@ -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`. \ No newline at end of file diff --git a/docs/guide/examples/anti_patterns.rs b/docs/guide/examples/anti_patterns.rs new file mode 100644 index 000000000..3911bfa0a --- /dev/null +++ b/docs/guide/examples/anti_patterns.rs @@ -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, +} + +fn AntipatternNoKeys(cx: Scope) -> 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 +} diff --git a/docs/guide/examples/boolean_attribute.rs b/docs/guide/examples/boolean_attribute.rs new file mode 100644 index 000000000..f624de331 --- /dev/null +++ b/docs/guide/examples/boolean_attribute.rs @@ -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 +} diff --git a/docs/guide/examples/component_borrowed_props.rs b/docs/guide/examples/component_borrowed_props.rs new file mode 100644 index 000000000..ed6beb1af --- /dev/null +++ b/docs/guide/examples/component_borrowed_props.rs @@ -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 diff --git a/docs/guide/examples/component_children.rs b/docs/guide/examples/component_children.rs new file mode 100644 index 000000000..37ba59ba8 --- /dev/null +++ b/docs/guide/examples/component_children.rs @@ -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 diff --git a/docs/guide/examples/component_children_inspect.rs b/docs/guide/examples/component_children_inspect.rs new file mode 100644 index 000000000..1a239890f --- /dev/null +++ b/docs/guide/examples/component_children_inspect.rs @@ -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 diff --git a/docs/guide/examples/component_element_props.rs b/docs/guide/examples/component_element_props.rs new file mode 100644 index 000000000..86ff67bc7 --- /dev/null +++ b/docs/guide/examples/component_element_props.rs @@ -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 diff --git a/docs/guide/examples/component_owned_props.rs b/docs/guide/examples/component_owned_props.rs new file mode 100644 index 000000000..04fb6e2eb --- /dev/null +++ b/docs/guide/examples/component_owned_props.rs @@ -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) -> Element { + cx.render(rsx! { + div { + "This post has ", + b { "{cx.props.score}" }, + " likes" + } + }) +} +// ANCHOR_END: Likes diff --git a/docs/guide/examples/component_props_options.rs b/docs/guide/examples/component_props_options.rs new file mode 100644 index 000000000..28482e421 --- /dev/null +++ b/docs/guide/examples/component_props_options.rs @@ -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) -> 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) -> Element { + return cx.render(rsx!(h1{ + "{cx.props.string}", + })); +} +// ANCHOR_END: IntoComponent diff --git a/docs/guide/examples/components.rs b/docs/guide/examples/components.rs new file mode 100644 index 000000000..476eadc8d --- /dev/null +++ b/docs/guide/examples/components.rs @@ -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 diff --git a/docs/guide/examples/conditional_rendering.rs b/docs/guide/examples/conditional_rendering.rs new file mode 100644 index 000000000..5dbadd29b --- /dev/null +++ b/docs/guide/examples/conditional_rendering.rs @@ -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 +} diff --git a/docs/guide/examples/dangerous_inner_html.rs b/docs/guide/examples/dangerous_inner_html.rs new file mode 100644 index 000000000..dafece034 --- /dev/null +++ b/docs/guide/examples/dangerous_inner_html.rs @@ -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 dangerously"; + + cx.render(rsx! { + div { + dangerous_inner_html: "{contents}", + } + }) + // ANCHOR_END: dangerous_inner_html +} diff --git a/docs/guide/examples/event_click.rs b/docs/guide/examples/event_click.rs new file mode 100644 index 000000000..457873d49 --- /dev/null +++ b/docs/guide/examples/event_click.rs @@ -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 +} diff --git a/docs/guide/examples/event_handler_prop.rs b/docs/guide/examples/event_handler_prop.rs new file mode 100644 index 000000000..4a9bbbb00 --- /dev/null +++ b/docs/guide/examples/event_handler_prop.rs @@ -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 diff --git a/docs/guide/examples/event_nested.rs b/docs/guide/examples/event_nested.rs new file mode 100644 index 000000000..63096448e --- /dev/null +++ b/docs/guide/examples/event_nested.rs @@ -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 +} diff --git a/docs/guide/examples/event_prevent_default.rs b/docs/guide/examples/event_prevent_default.rs new file mode 100644 index 000000000..f983400ae --- /dev/null +++ b/docs/guide/examples/event_prevent_default.rs @@ -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 +} diff --git a/docs/guide/examples/hello_world_desktop.rs b/docs/guide/examples/hello_world_desktop.rs new file mode 100644 index 000000000..373c1f9d6 --- /dev/null +++ b/docs/guide/examples/hello_world_desktop.rs @@ -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 diff --git a/docs/guide/examples/hello_world_tui.rs b/docs/guide/examples/hello_world_tui.rs new file mode 100644 index 000000000..f16751d71 --- /dev/null +++ b/docs/guide/examples/hello_world_tui.rs @@ -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!" + } + }) +} diff --git a/docs/guide/examples/hello_world_tui_no_ctrl_c.rs b/docs/guide/examples/hello_world_tui_no_ctrl_c.rs new file mode 100644 index 000000000..addf43671 --- /dev/null +++ b/docs/guide/examples/hello_world_tui_no_ctrl_c.rs @@ -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!" + } + }) +} diff --git a/docs/guide/examples/hello_world_web.rs b/docs/guide/examples/hello_world_web.rs new file mode 100644 index 000000000..a0547b8e5 --- /dev/null +++ b/docs/guide/examples/hello_world_web.rs @@ -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!" + } + }) +} diff --git a/docs/guide/examples/hooks_bad.rs b/docs/guide/examples/hooks_bad.rs new file mode 100644 index 000000000..bb4a43c26 --- /dev/null +++ b/docs/guide/examples/hooks_bad.rs @@ -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 +} diff --git a/docs/guide/examples/hooks_composed.rs b/docs/guide/examples/hooks_composed.rs new file mode 100644 index 000000000..3e90bb2bb --- /dev/null +++ b/docs/guide/examples/hooks_composed.rs @@ -0,0 +1,13 @@ +#![allow(unused)] + +use dioxus::prelude::*; + +fn main() {} + +struct AppSettings {} + +// ANCHOR: wrap_context +fn use_settings(cx: &ScopeState) -> UseSharedState { + use_context::(&cx).expect("App settings not provided") +} +// ANCHOR_END: wrap_context diff --git a/docs/guide/examples/hooks_counter.rs b/docs/guide/examples/hooks_counter.rs new file mode 100644 index 000000000..3a858c3e8 --- /dev/null +++ b/docs/guide/examples/hooks_counter.rs @@ -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 diff --git a/docs/guide/examples/hooks_counter_two_state.rs b/docs/guide/examples/hooks_counter_two_state.rs new file mode 100644 index 000000000..5ad534b33 --- /dev/null +++ b/docs/guide/examples/hooks_counter_two_state.rs @@ -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 diff --git a/docs/guide/examples/hooks_use_ref.rs b/docs/guide/examples/hooks_use_ref.rs new file mode 100644 index 000000000..c03993561 --- /dev/null +++ b/docs/guide/examples/hooks_use_ref.rs @@ -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 diff --git a/docs/guide/examples/input_controlled.rs b/docs/guide/examples/input_controlled.rs new file mode 100644 index 000000000..955fe7ef5 --- /dev/null +++ b/docs/guide/examples/input_controlled.rs @@ -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 diff --git a/docs/guide/examples/input_uncontrolled.rs b/docs/guide/examples/input_uncontrolled.rs new file mode 100644 index 000000000..66b77769a --- /dev/null +++ b/docs/guide/examples/input_uncontrolled.rs @@ -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 diff --git a/docs/guide/examples/meme_editor.rs b/docs/guide/examples/meme_editor.rs new file mode 100644 index 000000000..b8b6858d3 --- /dev/null +++ b/docs/guide/examples/meme_editor.rs @@ -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 diff --git a/docs/guide/examples/meme_editor_dark_mode.rs b/docs/guide/examples/meme_editor_dark_mode.rs new file mode 100644 index 000000000..ed7de16b8 --- /dev/null +++ b/docs/guide/examples/meme_editor_dark_mode.rs @@ -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::(&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::(&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 diff --git a/docs/guide/examples/rendering_lists.rs b/docs/guide/examples/rendering_lists.rs new file mode 100644 index 000000000..49fd2b7e2 --- /dev/null +++ b/docs/guide/examples/rendering_lists.rs @@ -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::::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" }, + })) +} diff --git a/docs/guide/examples/rsx_overview.rs b/docs/guide/examples/rsx_overview.rs new file mode 100644 index 000000000..124cf6ebc --- /dev/null +++ b/docs/guide/examples/rsx_overview.rs @@ -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 +} diff --git a/docs/guide/examples/spawn.rs b/docs/guide/examples/spawn.rs new file mode 100644 index 000000000..17dd259f7 --- /dev/null +++ b/docs/guide/examples/spawn.rs @@ -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 +} diff --git a/docs/guide/examples/use_future.rs b/docs/guide/examples/use_future.rs new file mode 100644 index 000000000..da52f8c63 --- /dev/null +++ b/docs/guide/examples/use_future.rs @@ -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::() + .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::() + .await + }); + // ANCHOR_END: dependency + + None +} diff --git a/docs/guide/src/README.md b/docs/guide/src/README.md deleted file mode 100644 index 778cabe4a..000000000 --- a/docs/guide/src/README.md +++ /dev/null @@ -1,105 +0,0 @@ -# Introduction - -![dioxuslogo](./images/dioxuslogo_full.png) - -**Dioxus** is a library for building fast, scalable, and robust user interfaces with the Rust programming language. This guide will help you get started with Dioxus running on the Web, Desktop, Mobile, and more. - -```rust -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!" } - )) -}; -``` - -In general, Dioxus and React share many functional similarities. If this guide is lacking in any general concept or an error message is confusing, React's documentation might be more helpful. We are dedicated to providing a *familiar* toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc). If you know React, then you already know Dioxus. If you don't know either, this guide will still help you! - -> This is an introduction book! For advanced topics, check out the [Reference](/reference) instead. - -## Multiplatform - -Dioxus is a *portable* toolkit, meaning the Core implementation can run anywhere with no platform-dependent linking. Unlike many other Rust frontend toolkits, Dioxus is not intrinsically linked to WebSys. In fact, every element and event listener can be swapped out at compile time. By default, Dioxus ships with the `html` feature enabled, but this can be disabled depending on your target renderer. - -Right now, we have several 1st-party renderers: -- WebSys (for WASM) -- Tao/Tokio (for Desktop apps) -- Tao/Tokio (for Mobile apps) -- SSR (for generating static markup) -- TUI/Rink (for terminal-based apps) - -### Web Support ---- -The Web is the best-supported target platform for Dioxus. To run on the Web, your app must be compiled to WebAssembly and depend on the `dioxus` crate with the `web` feature enabled. Because of the limitations of Wasm not every crate will work with your web-apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc). - -Because the web is a fairly mature platform, we expect there to be very little API churn for web-based features. - -[Jump to the getting started guide for the web.](/reference/platforms/web) - -Examples: -- [TodoMVC](https://github.com/DioxusLabs/example-projects/tree/master/todomvc) -- [ECommerce](https://github.com/DioxusLabs/example-projects/tree/master/ecommerce-site) - -[![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/example-projects/blob/master/todomvc) - -### SSR Support ---- -Dioxus supports server-side rendering! - -For rendering statically to an `.html` file or from a WebServer, then you'll want to make sure the `ssr` feature is enabled in the `dioxus` crate and use the `dioxus::ssr` API. We don't expect the SSR API to change drastically in the future. - -```rust -let contents = dioxus::ssr::render_vdom(&dom); -``` - -[Jump to the getting started guide for SSR.](/reference/platforms/ssr) - -Examples: -- [Example DocSite](https://github.com/dioxusLabs/docsite) -- [Tide WebServer]() -- [Markdown to fancy HTML generator]() - -### Desktop Support ---- -The desktop is a powerful target for Dioxus, but is currently limited in capability when compared to the Web platform. Currently, desktop apps are rendered with the platform's WebView library, but your Rust code is running natively on a native thread. This means that browser APIs are *not* available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs *are* accessible, so streaming, WebSockets, filesystem, etc are all viable APIs. In the future, we plan to move to a custom webrenderer-based DOM renderer with WGPU integrations. - -Desktop APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart. - -[Jump to the getting started guide for Desktop.](/reference/platforms/desktop) - -Examples: -- [File explorer](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer) -- [WiFi scanner](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner) - -[![File ExplorerExample](https://raw.githubusercontent.com/DioxusLabs/example-projects/master/file-explorer/image.png)](https://github.com/DioxusLabs/example-projects/tree/master/file-explorer) - -### Mobile Support ---- -Mobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with the platform's WebView, meaning that animations, transparency, and native widgets are not currently achievable. In addition, iOS is the only supported Mobile Platform. It is possible to get Dioxus running on Android and rendered with WebView, but the Rust windowing library that Dioxus uses - tao - does not currently supported Android. - -Mobile support is currently best suited for CRUD-style apps, ideally for internal teams who need to develop quickly but don't care much about animations or native widgets. - -[Jump to the getting started guide for Mobile.](/reference/platforms/mobile) - -Examples: -- [Todo App](https://github.com/DioxusLabs/example-projects/blob/master/ios_demo) - -### LiveView / Server Component Support ---- - -The internal architecture of Dioxus was designed from day one to support the `LiveView` use-case, where a web server hosts a running app for each connected user. As of today, there is no first-class LiveView support - you'll need to wire this up yourself. - -While not currently fully implemented, the expectation is that LiveView apps can be a hybrid between Wasm and server-rendered where only portions of a page are "live" and the rest of the page is either server-rendered, statically generated, or handled by the host SPA. - -### Multithreaded Support ---- -The Dioxus VirtualDom, sadly, is not currently `Send`. Internally, we use quite a bit of interior mutability which is not thread-safe. This means you can't easily use Dioxus with most web frameworks like Tide, Rocket, Axum, etc. - -To solve this, you'll want to spawn a VirtualDom on its own thread and communicate with it via channels. - -When working with web frameworks that require `Send`, it is possible to render a VirtualDom immediately to a String - but you cannot hold the VirtualDom across an await point. For retained-state SSR (essentially LiveView), you'll need to create a pool of VirtualDoms. - -Ultimately, you can always wrap the VirtualDom with a `Send` type and manually uphold the `Send` guarantees yourself. diff --git a/docs/guide/src/SUMMARY.md b/docs/guide/src/SUMMARY.md index 12a0b60cf..92836297b 100644 --- a/docs/guide/src/SUMMARY.md +++ b/docs/guide/src/SUMMARY.md @@ -1,54 +1,43 @@ # Summary -- [Introduction](README.md) -- [Roadmap](ROADMAP.md) -- [Getting Set Up](setup.md) -- [Hello, World!](hello_world.md) -- [Describing the UI](elements/index.md) - - [Intro to Elements](elements/vnodes.md) - - [Conditional Rendering](elements/conditional_rendering.md) - - [Lists](elements/lists.md) - - [Special Attributes](elements/special_attributes.md) -- [Components](components/index.md) - - [Properties](components/propsmacro.md) - - [Reusing, Importing, and Exporting](components/exporting_components.md) - - [Children and Attributes](components/component_children.md) - - [How Data Flows](components/composing.md) -- [Adding Interactivity](interactivity/index.md) +[Introduction](index.md) + +- [Getting Started](getting_started/index.md) + - [Desktop](getting_started/desktop.md) + - [Web](getting_started/web.md) + - [Hot Reload](getting_started/hot_reload.md) + - [Server-Side Rendering](getting_started/ssr.md) + - [Terminal UI](getting_started/tui.md) + - [Mobile](getting_started/mobile.md) +- [Describing the UI](describing_ui/index.md) + - [Special Attributes](describing_ui/special_attributes.md) + - [Components](describing_ui/components.md) + - [Props](describing_ui/component_props.md) + - [Component Children](describing_ui/component_children.md) +- [Interactivity](interactivity/index.md) - [Event Listeners](interactivity/event_handlers.md) - - [Hooks](interactivity/hooks.md) - - [UseState](interactivity/usestate.md) - - [UseRef](interactivity/useref.md) + - [Hooks & Component State](interactivity/hooks.md) - [User Input](interactivity/user_input.md) - -- [Managing State](state/index.md) - - [Local State](state/localstate.md) - - [Global State](state/sharedstate.md) - - [Lifting State](state/liftingstate.md) - - [Error handling](state/errorhandling.md) -- [Helper Crates](helpers/index.md) - - [Fermi](state/fermi.md) - - [Router](state/router.md) -- [Working with Async](async/index.md) + - [Sharing State](interactivity/sharing_state.md) + - [Custom Hooks](interactivity/custom_hooks.md) + - [Dynamic Rendering](interactivity/dynamic_rendering.md) + - [Routing](interactivity/router.md) +- [Async](async/index.md) - [UseFuture](async/use_future.md) - - [UseCoroutine](async/coroutines.md) - - [Updating State](async/loading_state.md) - - [Tasks](async/asynctasks.md) - - - - -- [Next Steps and Advanced Topics](final.md) - + - [UseCoroutine](async/use_coroutine.md) + - [Spawning Futures](async/spawn.md) +- [Best Practices](best_practices/index.md) + - [Error Handling](best_practices/error_handling.md) + - [Antipatterns](best_practices/antipatterns.md) +- [Publishing](publishing/index.md) + - [Desktop](publishing/desktop.md) + - [Web](publishing/web.md) ----------- -[Contributors](misc/contributors.md) +- [Custom Renderer](custom_renderer/index.md) +----------- - - - +[Roadmap](roadmap.md) +[Contributing](contributing.md) diff --git a/docs/guide/src/__unused/README.md b/docs/guide/src/__unused/README.md new file mode 100644 index 000000000..0a3583ef1 --- /dev/null +++ b/docs/guide/src/__unused/README.md @@ -0,0 +1,6 @@ +This directory includes: + +- Very outdated docs +- Docs for features which haven't been implemented (yet?) +- Information which has already been covered in the guide in a more organized fashion +- A few things yet to be integrated into the guide nicely \ No newline at end of file diff --git a/docs/guide/src/advanced-guides/06-subscription-api.md b/docs/guide/src/__unused/advanced-guides/06-subscription-api.md similarity index 100% rename from docs/guide/src/advanced-guides/06-subscription-api.md rename to docs/guide/src/__unused/advanced-guides/06-subscription-api.md diff --git a/docs/guide/src/advanced-guides/10-concurrent-mode.md b/docs/guide/src/__unused/advanced-guides/10-concurrent-mode.md similarity index 100% rename from docs/guide/src/advanced-guides/10-concurrent-mode.md rename to docs/guide/src/__unused/advanced-guides/10-concurrent-mode.md diff --git a/docs/guide/src/advanced-guides/11-arena-memo.md b/docs/guide/src/__unused/advanced-guides/11-arena-memo.md similarity index 87% rename from docs/guide/src/advanced-guides/11-arena-memo.md rename to docs/guide/src/__unused/advanced-guides/11-arena-memo.md index d5eaaa1d0..0c98fc255 100644 --- a/docs/guide/src/advanced-guides/11-arena-memo.md +++ b/docs/guide/src/__unused/advanced-guides/11-arena-memo.md @@ -2,9 +2,9 @@ Dioxus differs slightly from other UI virtual doms in some subtle ways due to its memory allocator. -One important aspect to understand is how props are passed down from parent components to children. All "components" (custom user-made UI elements) are tightly allocated together in an arena. However, because props and hooks are generically typed, they are casted to `Any` and allocated on the heap - not in the arena with the components. +One important aspect to understand is how props are passed down from parent components to children. All "components" (custom user-made UI elements) are tightly allocated together in an arena. However, because props and hooks are generically typed, they are casted to `Any` and allocated on the heap – not in the arena with the components. -With this system, we try to be more efficient when leaving the component arena and entering the heap. By default, props are memoized between renders using COW and context. This makes props comparisons fast - done via ptr comparisons on the cow pointer. Because memoization is done by default, parent re-renders will _not_ cascade to children if the child's props did not change. +With this system, we try to be more efficient when leaving the component arena and entering the heap. By default, props are memoized between renders using COW and context. This makes props comparisons fast – done via ptr comparisons on the cow pointer. Because memoization is done by default, parent re-renders will _not_ cascade to children if the child's props did not change. https://dmitripavlutin.com/use-react-memo-wisely/ diff --git a/docs/guide/src/advanced-guides/12-signals.md b/docs/guide/src/__unused/advanced-guides/12-signals.md similarity index 87% rename from docs/guide/src/advanced-guides/12-signals.md rename to docs/guide/src/__unused/advanced-guides/12-signals.md index cae2e1726..aa20d43ee 100644 --- a/docs/guide/src/advanced-guides/12-signals.md +++ b/docs/guide/src/__unused/advanced-guides/12-signals.md @@ -22,7 +22,7 @@ fn Comp(cx: Scope) -> DomTree { } ``` -This component is fairly straightforward - the input updates its own value on every change. However, every call to set_title will re-render the component. If we add a large list, then every time we update the title input, Dioxus will need to diff the entire list, over, and over, and over. This is **a lot** of wasted clock-cycles! +This component is fairly straightforward – the input updates its own value on every change. However, every call to set_title will re-render the component. If we add a large list, then every time we update the title input, Dioxus will need to diff the entire list, over, and over, and over. This is **a lot** of wasted clock-cycles! ```rust fn Comp(cx: Scope) -> DomTree { @@ -43,7 +43,7 @@ fn Comp(cx: Scope) -> DomTree { } ``` -Many experienced React developers will just say "this is bad design" - but we consider it to be a pit of failure, rather than a pit of success! That's why signals exist - to push you in a more performant (and ergonomic) direction. Signals let us directly bind values to their final place in the VirtualDOM. Whenever the signal value is updated, Dioxus will only the DOM nodes where that signal is used. Signals are built into Dioxus, so we can directly bind attributes of elements to their updates. +Many experienced React developers will just say "this is bad design" – but we consider it to be a pit of failure, rather than a pit of success! That's why signals exist – to push you in a more performant (and ergonomic) direction. Signals let us directly bind values to their final place in the VirtualDOM. Whenever the signal value is updated, Dioxus will only the DOM nodes where that signal is used. Signals are built into Dioxus, so we can directly bind attributes of elements to their updates. We can use signals to generate a two-way binding between data and the input box. Our text input is now just a two-line component! @@ -124,7 +124,7 @@ If you build a complex app on top of Dirac, you'll likely notice that many of yo ## Signals and Iterators -Sometimes you want to use a collection of items. With Signals, you can bypass diffing for collections - a very powerful technique to avoid re-rendering on large collections. +Sometimes you want to use a collection of items. With Signals, you can bypass diffing for collections – a very powerful technique to avoid re-rendering on large collections. By default, Dioxus is limited when you use iter/map. With the `For` component, you can provide an iterator and a function for the iterator to map to. diff --git a/docs/guide/src/advanced-guides/13-subtrees.md b/docs/guide/src/__unused/advanced-guides/13-subtrees.md similarity index 76% rename from docs/guide/src/advanced-guides/13-subtrees.md rename to docs/guide/src/__unused/advanced-guides/13-subtrees.md index 7c99ecf4f..0108186eb 100644 --- a/docs/guide/src/advanced-guides/13-subtrees.md +++ b/docs/guide/src/__unused/advanced-guides/13-subtrees.md @@ -16,7 +16,7 @@ Root The goal of this functionality is to enable things like Portals, Windows, and inline alternative renderers without needing to spin up a new VirtualDom. -With the right renderer plugins, a subtree could be rendered as anything - a 3D scene, SVG, or even as the contents of a new window or modal. This functionality is similar to "Portals" in React, but much more "renderer agnostic." Portals, by nature, are not necessarily cross-platform and rely on renderer functionality, so it makes sense to abstract their purpose into the subtree concept. +With the right renderer plugins, a subtree could be rendered as anything – a 3D scene, SVG, or even as the contents of a new window or modal. This functionality is similar to "Portals" in React, but much more "renderer agnostic." Portals, by nature, are not necessarily cross-platform and rely on renderer functionality, so it makes sense to abstract their purpose into the subtree concept. The desktop renderer comes pre-loaded with the window and notification subtree plugins, making it possible to render subtrees into entirely different windows. @@ -25,7 +25,7 @@ Subtrees also solve the "bridging" issues in React where two different renderers ## API -Due to their importance in the hierarchy, Components - not nodes - are treated as subtree roots. +Due to their importance in the hierarchy, Components – not nodes – are treated as subtree roots. ```rust diff --git a/docs/guide/src/advanced-guides/custom-renderer.md b/docs/guide/src/__unused/advanced-guides/custom-renderer.md similarity index 92% rename from docs/guide/src/advanced-guides/custom-renderer.md rename to docs/guide/src/__unused/advanced-guides/custom-renderer.md index a8bec614c..194fff54a 100644 --- a/docs/guide/src/advanced-guides/custom-renderer.md +++ b/docs/guide/src/__unused/advanced-guides/custom-renderer.md @@ -54,7 +54,7 @@ The Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia. ### An example -For the sake of understanding, lets consider this example - a very simple UI declaration: +For the sake of understanding, lets consider this example – a very simple UI declaration: ```rust rsx!( h1 {"hello world"} ) @@ -218,7 +218,7 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent { ## Custom raw elements -If you need to go as far as relying on custom elements for your renderer - you totally can. This still enables you to use Dioxus' reactive nature, component system, shared state, and other features, but will ultimately generate different nodes. All attributes and listeners for the HTML and SVG namespace are shuttled through helper structs that essentially compile away (pose no runtime overhead). You can drop in your own elements any time you want, with little hassle. However, you must be absolutely sure your renderer can handle the new type, or it will crash and burn. +If you need to go as far as relying on custom elements for your renderer – you totally can. This still enables you to use Dioxus' reactive nature, component system, shared state, and other features, but will ultimately generate different nodes. All attributes and listeners for the HTML and SVG namespace are shuttled through helper structs that essentially compile away (pose no runtime overhead). You can drop in your own elements any time you want, with little hassle. However, you must be absolutely sure your renderer can handle the new type, or it will crash and burn. These custom elements are defined as unit structs with trait implementations. @@ -248,7 +248,7 @@ There are three opportunities for platform incompatibilities to break your progr 2. When downcasting events via `Event.downcast_ref()` 3. Calling platform-specific APIs that don't exist -The best hooks will properly detect the target platform and still provide functionality, failing gracefully when a platform is not supported. We encourage - and provide - an indication to the user on what platforms a hook supports. For issues 1 and 2, these return a result as to not cause panics on unsupported platforms. When designing your hooks, we recommend propagating this error upwards into user facing code, making it obvious that this particular service is not supported. +The best hooks will properly detect the target platform and still provide functionality, failing gracefully when a platform is not supported. We encourage – and provide – an indication to the user on what platforms a hook supports. For issues 1 and 2, these return a result as to not cause panics on unsupported platforms. When designing your hooks, we recommend propagating this error upwards into user facing code, making it obvious that this particular service is not supported. This particular code _will panic_ due to the unwrap on downcast_ref. Try to avoid these types of patterns. diff --git a/docs/guide/src/advanced-guides/liveview.md b/docs/guide/src/__unused/advanced-guides/liveview.md similarity index 88% rename from docs/guide/src/advanced-guides/liveview.md rename to docs/guide/src/__unused/advanced-guides/liveview.md index ccb7182aa..e223c593e 100644 --- a/docs/guide/src/advanced-guides/liveview.md +++ b/docs/guide/src/__unused/advanced-guides/liveview.md @@ -11,7 +11,7 @@ Because Liveview combines the server and the client, you'll find dedicated APIs You can find more information to data modeling and fetching for LiveApps in the "Book of Dioxus Patterns". ### Ease of deployment -There is no hassle for deploying Dioxus liveview apps - simply upload the binary to your hosting provider of choice. There simply is no need to deal with intermediate APIs. An app's frontend and backend are tightly coupled to each other, meaning that the backed and frontend are always up to date. +There is no hassle for deploying Dioxus liveview apps – simply upload the binary to your hosting provider of choice. There simply is no need to deal with intermediate APIs. An app's frontend and backend are tightly coupled to each other, meaning that the backed and frontend are always up to date. This is especially powerful for small teams, where fast iteration, versioned unique prototypes, and simple deployments are important. As your app grows, Dioxus will happily grow with you, serving up to 100K RPS (thanks to async Rust performance). diff --git a/docs/guide/src/advanced-guides/rsx.md b/docs/guide/src/__unused/advanced-guides/rsx.md similarity index 74% rename from docs/guide/src/advanced-guides/rsx.md rename to docs/guide/src/__unused/advanced-guides/rsx.md index 8f7728f35..062246490 100644 --- a/docs/guide/src/advanced-guides/rsx.md +++ b/docs/guide/src/__unused/advanced-guides/rsx.md @@ -2,7 +2,7 @@ Many modern frameworks provide a domain-specific-language for declaring user-interfaces. In the case of React, this language extension is called JSX and must be handled through additional dependencies and pre/post processors to transform your source code. With Rust, we can simply provide a procedural macro in the Dioxus dependency itself that mimics the JSX language. -With Dioxus, we actually ship two different macros - a macro that mimics JSX (the `html!` macro) and a macro that mimics Rust's native nested-struct syntax (the `rsx!` macro). These macros simply transform their inputs into NodeFactory calls. +With Dioxus, we actually ship two different macros – a macro that mimics JSX (the `html!` macro) and a macro that mimics Rust's native nested-struct syntax (the `rsx!` macro). These macros simply transform their inputs into NodeFactory calls. For instance, this html! call: ```rust @@ -18,7 +18,7 @@ becomes this NodeFactory call: None // key ) ``` -The NodeFactory API is fairly ergonomic, making it a viable option to use directly. The NodeFactory API is also compile-time correct and has incredible syntax highlighting support. We use what Rust calls a "unit type" - the `dioxus_elements::div` and associated methods to ensure that a `div` can only have attributes associated with `div`s. This lets us tack on relevant documentation, autocomplete support, and jump-to-definition for methods and attributes. +The NodeFactory API is fairly ergonomic, making it a viable option to use directly. The NodeFactory API is also compile-time correct and has incredible syntax highlighting support. We use what Rust calls a "unit type" – the `dioxus_elements::div` and associated methods to ensure that a `div` can only have attributes associated with `div`s. This lets us tack on relevant documentation, autocomplete support, and jump-to-definition for methods and attributes. ![Compile time correct syntax](../images/compiletimecorrect.png) @@ -26,7 +26,7 @@ The NodeFactory API is fairly ergonomic, making it a viable option to use direct The html! macro supports a limited subset of the html standard. Rust's macro parsing tools are somewhat limited, so all text between tags _must be quoted_. -However, writing HTML by hand is a bit tedious - IDE tools for Rust don't support linting/autocomplete/syntax highlighting. We suggest using RSX - it's more natural for Rust programs and _does_ integrate well with Rust IDE tools. +However, writing HTML by hand is a bit tedious – IDE tools for Rust don't support linting/autocomplete/syntax highlighting. We suggest using RSX – it's more natural for Rust programs and _does_ integrate well with Rust IDE tools. ```rust let name = "jane"; diff --git a/docs/guide/src/advanced-guides/testing.md b/docs/guide/src/__unused/advanced-guides/testing.md similarity index 97% rename from docs/guide/src/advanced-guides/testing.md rename to docs/guide/src/__unused/advanced-guides/testing.md index dc53feafd..2f0f97b67 100644 --- a/docs/guide/src/advanced-guides/testing.md +++ b/docs/guide/src/__unused/advanced-guides/testing.md @@ -21,7 +21,7 @@ fn runs_in_browser() { Then, when you run ```console -$ dioxus test --chrome +dioxus test --chrome ``` Dioxus will build and test your code using the Chrome browser as a harness. diff --git a/docs/guide/src/__unused/attribute_spreading.md b/docs/guide/src/__unused/attribute_spreading.md new file mode 100644 index 000000000..fcf799f3d --- /dev/null +++ b/docs/guide/src/__unused/attribute_spreading.md @@ -0,0 +1,29 @@ +In the cases where you need to pass arbitrary element properties into a component – say to add more functionality to the `` tag, Dioxus will accept any quoted fields. This is similar to adding arbitrary fields to regular elements using quotes. + +```rust + +rsx!( + Clickable { + "class": "blue-button", + "style": "background: red;" + } +) + +``` + +For a component to accept these attributes, you must add an `attributes` field to your component's properties. We can use the spread syntax to add these attributes to whatever nodes are in our component. + +```rust +#[derive(Props)] +struct ClickableProps<'a> { + attributes: Attributes<'a> +} + +fn clickable(cx: Scope>) -> Element { + cx.render(rsx!( + a { + ..cx.props.attributes, + "Any link, anywhere" + } + )) +} \ No newline at end of file diff --git a/docs/guide/src/components/composing.md b/docs/guide/src/__unused/composing.md similarity index 98% rename from docs/guide/src/components/composing.md rename to docs/guide/src/__unused/composing.md index 85cf424a3..5746d6477 100644 --- a/docs/guide/src/components/composing.md +++ b/docs/guide/src/__unused/composing.md @@ -15,7 +15,7 @@ This section is a bit long, but worth the read. We recommend coffee, tea, and/or ## Reactive Programming -Dioxus is one of a handful of Rust libraries that provide a "Reactive Programming Model". The term "Reactive programming" is a classification of programming paradigm - much like functional or imperative programming. This is a very important distinction since it affects how we *think* about our code. +Dioxus is one of a handful of Rust libraries that provide a "Reactive Programming Model". The term "Reactive programming" is a classification of programming paradigm – much like functional or imperative programming. This is a very important distinction since it affects how we *think* about our code. Reactive programming is a programming model concerned with deriving computations from asynchronous data flow. Most reactive programs are comprised of datasources, intermediate computations, and a final result. diff --git a/docs/guide/src/__unused/event_javascript.rs b/docs/guide/src/__unused/event_javascript.rs new file mode 100644 index 000000000..2b0a60340 --- /dev/null +++ b/docs/guide/src/__unused/event_javascript.rs @@ -0,0 +1,26 @@ + +## JavaScript Handlers + +Instead of passing a closure, you can also pass a string to event handlers – this lets you use JavaScript (if your renderer can execute JavaScript): + +```rust +{{#include ../../examples/event_javascript.rs:rsx}} +``` + + +#![allow(non_snake_case)] +use dioxus::prelude::*; + +fn main() { + dioxus::desktop::launch(App); +} + +fn App(cx: Scope) -> Element { + cx.render(rsx! { + // ANCHOR: rsx + div { + onclick: "alert('hello world')", + } + // ANCHOR_END: rsx + }) +} diff --git a/docs/guide/src/state/fanout.md b/docs/guide/src/__unused/fanout.md similarity index 79% rename from docs/guide/src/state/fanout.md rename to docs/guide/src/__unused/fanout.md index 37807401b..087d4b543 100644 --- a/docs/guide/src/state/fanout.md +++ b/docs/guide/src/__unused/fanout.md @@ -35,7 +35,7 @@ fn Titlebar(cx: Scope) -> Element { } ``` -For many apps - this is a fine pattern, especially if the component is a one-off. However, if we want to reuse the component outside of this app, then we'll start to run into issues where our contexts are unavailable. +For many apps – this is a fine pattern, especially if the component is a one-off. However, if we want to reuse the component outside of this app, then we'll start to run into issues where our contexts are unavailable. ## Fanning Out @@ -58,4 +58,4 @@ fn DocsiteTitlesection(cx: Scope) { This particular wrapper component unfortunately cannot be reused across apps. However, because it simply wraps other real elements, it doesn't have to. We are free to reuse our TitleBar and UserBar components across apps with ease. We also know that this particular component is plenty performant because the wrapper doesn't have any props and is always memoized. The only times this component re-renders is when any of the atoms change. -This is the beauty of Dioxus - we always know where our components are likely to be re-rendered. Our wrapper components can easily prevent any large re-renders by simply memoizing their components. This system might not be as elegant or precise as signal systems found in libraries like Sycamore or SolidJS, but it is quite ergonomic. +This is the beauty of Dioxus – we always know where our components are likely to be re-rendered. Our wrapper components can easily prevent any large re-renders by simply memoizing their components. This system might not be as elegant or precise as signal systems found in libraries like Sycamore or SolidJS, but it is quite ergonomic. diff --git a/docs/guide/src/__unused/hello_world.md b/docs/guide/src/__unused/hello_world.md new file mode 100644 index 000000000..d49172e47 --- /dev/null +++ b/docs/guide/src/__unused/hello_world.md @@ -0,0 +1,14 @@ +# "Hello, World" desktop app + + + +If you plan to develop extensions for the `Dioxus` ecosystem, please use the `dioxus` crate with the `core` feature to limit the amount of dependencies your project brings in. + + +### What is this `Scope` object? + +Coming from React, the `Scope` object might be confusing. In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. + +In Dioxus, you are given an explicit `Scope` object to control how the component renders and stores data. The `Scope` object provides a handful of useful APIs for features like suspense, rendering, and more. + +For now, just know that `Scope` lets you store state with hooks and render elements with `cx.render`. diff --git a/docs/guide/src/helpers/index.md b/docs/guide/src/__unused/helpers.md similarity index 93% rename from docs/guide/src/helpers/index.md rename to docs/guide/src/__unused/helpers.md index 0a6858446..86c76bea8 100644 --- a/docs/guide/src/helpers/index.md +++ b/docs/guide/src/__unused/helpers.md @@ -18,27 +18,27 @@ Global state as easy as `use_state`. This crate has yet to be developed! However, we do plan on providing a crate for good fetching and query support. -## 3rd-party - Toast +## 3rd-party – Toast Toast notifications, curtesy of [@mrxiaozhuox](https://github.com/mrxiaozhuox). [Link](https://github.com/mrxiaozhuox/dioxus-toast) -## 3rd-party - HeroIcon +## 3rd-party – HeroIcon Collection of helpful hero icons, curtesy of [@autarch](https://github.com/autarch). [Link](https://github.com/houseabsolute/dioxus-heroicons) -## 3rd-party - Katex +## 3rd-party – Katex Draw beautiful equations, curtesy of [@oovm](https://github.com/oovm) [Link](https://github.com/oovm/katex-wasm/tree/dev/projects/dioxus-katex) -## 3rd-party - PrimsJS +## 3rd-party – PrimsJS Highlight your code blocks with ease, curtesy of [@oovm](https://github.com/oovm) diff --git a/docs/guide/src/state/index.md b/docs/guide/src/__unused/index.md similarity index 77% rename from docs/guide/src/state/index.md rename to docs/guide/src/__unused/index.md index ac025aff1..1732afdae 100644 --- a/docs/guide/src/state/index.md +++ b/docs/guide/src/__unused/index.md @@ -9,9 +9,9 @@ In this chapter, we'll cover the various ways to manage state, the appropriate t Why do people say state management is so difficult? What does it mean? -Generally, state management is the code you need to write to ensure that your app renders the *correct* content. If the user inputs a name, then you need to display the appropriate response - like alerts, validation, and disable/enable various elements on the page. Things can quickly become tricky if you need loading screens and cancellable tasks. +Generally, state management is the code you need to write to ensure that your app renders the *correct* content. If the user inputs a name, then you need to display the appropriate response – like alerts, validation, and disable/enable various elements on the page. Things can quickly become tricky if you need loading screens and cancellable tasks. -For the simplest of apps, all of your state can enter the app from the root props. This is common in server-side rendering - we can collect all of the required state *before* rendering the content. +For the simplest of apps, all of your state can enter the app from the root props. This is common in server-side rendering – we can collect all of the required state *before* rendering the content. ```rust let all_content = get_all_content().await; @@ -25,7 +25,7 @@ let output = dioxus::ssr::render_lazy(rsx!{ With this incredibly simple setup, it is highly unlikely that you'll have rendering bugs. There simply is barely any state to manage. -However, most of your apps will store state inside of the Dioxus VirtualDom - either through local state or global state. +However, most of your apps will store state inside of the Dioxus VirtualDom – either through local state or global state. ## Your options diff --git a/docs/guide/src/state/localstate.md b/docs/guide/src/__unused/localstate.md similarity index 84% rename from docs/guide/src/state/localstate.md rename to docs/guide/src/__unused/localstate.md index 788399cb5..bf5ae7014 100644 --- a/docs/guide/src/state/localstate.md +++ b/docs/guide/src/__unused/localstate.md @@ -59,14 +59,14 @@ struct Todo { } ``` -This is not only better for encapsulation and abstraction, but it's only more performant! Whenever a Todo is hovered, we won't need to re-render *every* Todo again - only the Todo that's currently being hovered. +This is not only better for encapsulation and abstraction, but it's only more performant! Whenever a Todo is hovered, we won't need to re-render *every* Todo again – only the Todo that's currently being hovered. Wherever possible, you should try to refactor the "view" layer *out* of your data model. ## Immutability -Storing all of your state inside a `use_ref` might be tempting. However, you'll quickly run into an issue where the `Ref` provided by `read()` "does not live long enough." An indeed - you can't return locally-borrowed value into the Dioxus tree. +Storing all of your state inside a `use_ref` might be tempting. However, you'll quickly run into an issue where the `Ref` provided by `read()` "does not live long enough." An indeed – you can't return locally-borrowed value into the Dioxus tree. In these scenarios consider breaking your `use_ref` into individual `use_state`s. @@ -91,7 +91,7 @@ let color = use_state(&cx, || "red"); let names = use_state(&cx, HashMap::new); ``` -You might recognize that our "names" value is a HashMap - which is not terribly cheap to clone every time we update its value. To solve this issue, we *highly* suggest using a library like [`im`](https://crates.io/crates/im) which will take advantage of structural sharing to make clones and mutations much cheaper. +You might recognize that our "names" value is a HashMap – which is not terribly cheap to clone every time we update its value. To solve this issue, we *highly* suggest using a library like [`im`](https://crates.io/crates/im) which will take advantage of structural sharing to make clones and mutations much cheaper. When combined with the `make_mut` method on `use_state`, you can get really succinct updates to collections: diff --git a/docs/guide/src/__unused/memoization.md b/docs/guide/src/__unused/memoization.md new file mode 100644 index 000000000..ef476a457 --- /dev/null +++ b/docs/guide/src/__unused/memoization.md @@ -0,0 +1,28 @@ + +## Memoization + +Dioxus uses Memoization for a more efficient user interface. Memoization is the process in which we check if a component actually needs to be re-rendered when its props change. If a component's properties change but they wouldn't affect the output, then we don't need to re-render the component, saving time! + +For example, let's say we have a component that has two children: + +```rust +fn Demo(cx: Scope) -> Element { + // don't worry about these 2, we'll cover them later + let name = use_state(&cx, || String::from("bob")); + let age = use_state(&cx, || 21); + + cx.render(rsx!{ + Name { name: name } + Age { age: age } + }) +} +``` + +If `name` changes but `age` does not, then there is no reason to re-render our `Age` component since the contents of its props did not meaningfully change. + +Dioxus memoizes owned components. It uses `PartialEq` to determine if a component needs rerendering, or if it has stayed the same. This is why you must derive PartialEq! + +> This means you can always rely on props with `PartialEq` or no props at all to act as barriers in your app. This can be extremely useful when building larger apps where properties frequently change. By moving our state into a global state management solution, we can achieve precise, surgical re-renders, improving the performance of our app. + +Borrowed Props cannot be safely memoized. However, this is not a problem – Dioxus relies on the memoization of their parents to determine if they need to be rerendered. + diff --git a/docs/guide/src/__unused/model_pattern.md b/docs/guide/src/__unused/model_pattern.md new file mode 100644 index 000000000..67f21321a --- /dev/null +++ b/docs/guide/src/__unused/model_pattern.md @@ -0,0 +1,67 @@ + + +## Using UseRef with "models" + +One option for state management that UseRef enables is the development of a "model" for your components. This particular pattern enables you to model your state with regular structs. + +For instance, our calculator example uses a struct to model the state. + +```rust + +struct Calculator { + display_value: String, + operator: Option, + waiting_for_operand: bool, + cur_val: f64, +} +``` + +Our component is really simple – we just call `use_ref` to get an initial calculator state. + +```rust +fn app(cx: Scope) -> Element { + let state = use_ref(&cx, Calculator::new); + + cx.render(rsx!{ + // the rendering code + }) +} +``` + +In our UI, we can then use `read` and a helper method to get data out of the model. + +```rust +// Our accessor method +impl Calculator { + fn formatted_display(&self) -> String { + self.display_value + .parse::() + .unwrap() + .separated_string() + } +} + +// And then in the UI +cx.render(rsx!{ + div { [state.read().formatted_display()] } +}) +``` + +To modify the state, we can setup a helper method and then attach it to a callback. + +```rust +// our helper +impl Calculator { + fn clear_display(&mut self) { + self.display_value = "0".to_string() + } +} + +// our callback +cx.render(rsx!{ + button { + onclick: move |_| state.write().clear_display(), + } +}) +``` + diff --git a/docs/guide/src/advanced-guides/00-index.md b/docs/guide/src/advanced-guides/00-index.md deleted file mode 100644 index aedc39e34..000000000 --- a/docs/guide/src/advanced-guides/00-index.md +++ /dev/null @@ -1,73 +0,0 @@ -# Core Topics - -In this chapter, we'll cover some core topics about how Dioxus works and how to best leverage the features to build a beautiful, reactive app. - -At a very high level, Dioxus is simply a Rust framework for _declaring_ user interfaces and _reacting_ to changes. - -1) We declare what we want our user interface to look like given a state using Rust-based logic and control flow. -2) We declare how we want our state to change when the user triggers an event. - -## Declarative UI - -Dioxus is a *declarative* framework. This means that instead of manually writing calls to "create element" and "set element background to red," we simply *declare* what we want the element to look like and let Dioxus handle the differences. - -Let's pretend that we have a stoplight we need to control - it has a color state with red, yellow, and green as options. - - -Using an imperative approach, we would have to manually declare each element and then handlers for advancing the stoplight. - -```rust -let container = Container::new(); - -let green_light = Light::new().color("green").enabled(true); -let yellow_light = Light::new().color("yellow").enabled(false); -let red_light = Light::new().color("red").enabled(false); -container.push(green_light); -container.push(yellow_light); -container.push(red_light); - -container.set_onclick(move |_| { - if red_light.enabled() { - red_light.set_enabled(false); - green_light.set_enabled(true); - } else if yellow_light.enabled() { - yellow_light.set_enabled(false); - red_light.set_enabled(true); - } else if green_light.enabled() { - green_light.set_enabled(false); - yellow_light.set_enabled(true); - } -}); -``` - -As the UI grows in scale, our logic to keep each element in the proper state would grow exponentially. This can become very unwieldy and lead to out-of-sync UIs that harm user experience. - -Instead, with Dioxus, we *declare* what we want our UI to look like: - -```rust -let mut state = use_state(&cx, || "red"); - -cx.render(rsx!( - Container { - Light { color: "red", enabled: state == "red", } - Light { color: "yellow", enabled: state == "yellow", } - Light { color: "green", enabled: state == "green", } - - onclick: move |_| { - state.set(match *state { - "green" => "yellow", - "yellow" => "red", - "red" => "green", - }) - } - } -)) -``` - -Remember: this concept is not new! Many frameworks are declarative - with React being the most popular. Declarative frameworks tend to be much more enjoyable to work with than imperative frameworks. - -Here's some reading about declaring UI in React: - -- [https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js](https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js) - -- [https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44](https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44) diff --git a/docs/guide/src/advanced-guides/rsx_in_depth.md b/docs/guide/src/advanced-guides/rsx_in_depth.md deleted file mode 100644 index 13f1d47c0..000000000 --- a/docs/guide/src/advanced-guides/rsx_in_depth.md +++ /dev/null @@ -1,229 +0,0 @@ -# RSX in Depth - -The RSX macro makes it very easy to assemble complex UIs with a very natural Rust syntax: - - -```rust -rsx!(div { - button { - "Add todo", - onclick: move |e| todos.write().new_todo() - } - ul { - class: "todo-list" - (todos.iter().map(|(key, todo)| rsx!( - li { - class: "beautiful-todo" - key: "f" - h3 { "{todo.title}" } - p { "{todo.contents}"} - } - ))) - } -}) -``` - -In this section, we'll cover the `rsx!` macro in depth. If you prefer to learn through examples, the `reference` guide has plenty of examples on how to use `rsx!` effectively. - - - -### Element structure - -```rust -div { - hidden: false, - "some text" - child {} - Component {} - {/* literal tokens that resolve to vnodes */} -} - -``` - -Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work: - -- `name: value` sets a property on this element. -- `"text"` adds a new text element -- `tag {}` adds a new child element -- `CustomTag {}` adds a new child component -- `{expr}` pastes the `expr` tokens literally. They must be `IntoIterator where T: IntoVnode` to work properly - -Commas are entirely optional, but might be useful to delineate between elements and attributes. - -The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. To push you in the right direction, all text-based attributes take `std::fmt::Arguments` directly, so you'll want to reach for `format_args!` when the built-in `f-string` interpolation just doesn't cut it. - - -### Ignoring `cx.render` with `rsx!(cx, ...)` - -Sometimes, writing `cx.render` is a hassle. The `rsx! macro will accept any token followed by a comma as the target to call "render" on: - -```rust -cx.render(rsx!( div {} )) -// becomes -rsx!(cx, div {}) -``` - - - -### Conditional Rendering - -Sometimes, you might not want to render an element given a condition. The rsx! macro will accept any tokens directly contained with curly braces, provided they resolve to a type that implements `IntoIterator`. This lets us write any Rust expression that resolves to a VNode: - - -```rust -rsx!({ - if enabled { - rsx!(cx, div {"enabled"}) - } else { - rsx!(cx, li {"disabled"}) - } -}) -``` -A convenient way of hiding/showing an element is returning an `Option`. When combined with `and_then`, we can succinctly control the display state given some boolean: - -```rust -rsx!({ - a.and_then(rsx!(div {"enabled"})) -}) -``` - -It's important to note that the expression `rsx!()` is typically lazy - this expression must be _rendered_ to produce a VNode. When using match statements, we must render every arm as to avoid the `no two closures are identical` rule that Rust imposes: - -```rust -// this will not compile! -match case { - true => rsx!(div {}), - false => rsx!(div {}) -} - -// the nodes must be rendered first -match case { - true => rsx!(cx, div {}), - false => rsx!(cx, div {}) -} -``` - -### Lists - -Again, because anything that implements `IntoIterator` is valid, we can use lists directly in our `rsx!`: - -```rust -let items = vec!["a", "b", "c"]; - -cx.render(rsx!{ - ul { - {items.iter().map(|f| rsx!(li { "a" }))} - } -}) -``` - -Sometimes, it makes sense to render VNodes into a list: - -```rust -let mut items = vec![]; - -for _ in 0..5 { - items.push(rsx!(cx, li {} )) -} - -rsx!(cx, {items} ) -``` - -#### Lists and Keys - -When rendering the VirtualDom to the screen, Dioxus needs to know which elements have been added and which have been removed. These changes are determined through a process called "diffing" - an old set of elements is compared to a new set of elements. If an element is removed, then it won't show up in the new elements, and Dioxus knows to remove it. - -However, with lists, Dioxus does not exactly know how to determine which elements have been added or removed if the order changes or if an element is added or removed from the middle of the list. - -In these cases, it is vitally important to specify a "key" alongside the element. Keys should be persistent between renders. - -```rust -fn render_list(cx: Scope, items: HashMap) -> DomTree { - rsx!(cx, ul { - {items.iter().map(|key, item| { - li { - key: key, - h2 { "{todo.title}" } - p { "{todo.contents}" } - } - })} - }) -} -``` - -There have been many guides made for keys in React, so we recommend reading up to understand their importance: - -- [React guide on keys](https://reactjs.org/docs/lists-and-keys.html) -- [Importance of keys (Medium)](https://kentcdodds.com/blog/understanding-reacts-key-prop) - -### Complete Reference -```rust -let text = "example"; - -cx.render(rsx!{ - div { - h1 { "Example" }, - - {title} - - // fstring interpolation - "{text}" - - p { - // Attributes - tag: "type", - - // Anything that implements display can be an attribute - abc: 123, - - enabled: true, - - // attributes also supports interpolation - // `class` is not a restricted keyword unlike JS and ClassName - class: "big small wide short {text}", - - class: format_args!("attributes take fmt::Arguments. {}", 99), - - tag: {"these tokens are placed directly"} - - // Children - a { "abcder" }, - - // Children with attributes - h2 { "hello", class: "abc-123" }, - - // Child components - CustomComponent { a: 123, b: 456, key: "1" }, - - // Child components with paths - crate::components::CustomComponent { a: 123, b: 456, key: "1" }, - - // Iterators - { (0..3).map(|i| rsx!( h1 {"{:i}"} )) }, - - // More rsx!, or even html! - { rsx! { div { } } }, - { html! {
} }, - - // Matching - // Requires rendering the nodes first. - // rsx! is lazy, and the underlying closures cannot have the same type - // Rendering produces the VNode type - {match rand::gen_range::(1..3) { - 1 => rsx!(cx, h1 { "big" }) - 2 => rsx!(cx, h2 { "medium" }) - _ => rsx!(cx, h3 { "small" }) - }} - - // Optionals - {true.and_then(|f| rsx!( h1 {"Conditional Rendering"} ))} - - // Child nodes - {cx.props.children} - - // Any expression that is `IntoVNode` - {expr} - } - } -}) -``` diff --git a/docs/guide/src/async/asynctasks.md b/docs/guide/src/async/asynctasks.md deleted file mode 100644 index bcb93633d..000000000 --- a/docs/guide/src/async/asynctasks.md +++ /dev/null @@ -1,90 +0,0 @@ -# Tasks - -All async code in Dioxus must be explicit and handled through Dioxus' task system. - -In this chapter, we'll learn how to spawn new tasks through our `Scope`. - -## Spawning a task - -You can push any `'static` future into the Dioxus future queue by simply calling `cx.spawn` to spawn a task. Pushing a future returns a `TaskId` which can then be used to cancel the - -```rust -fn App(cx: Scope) -> Element { - cx.spawn(async { - let mut count = 0; - loop { - tokio::time::delay(std::instant::Duration::from_millis(500)).await; - count += 1; - println!("Current count is {}", count); - } - }); - - None -} -``` - -The future must be `'static` - so any values captured by the task must not carry any references to `cx`. All the Dioxus hooks have a method called `for_async` which will create a slightly more limited handle to the hook for you to use in your async code. - -```rust -fn App(cx: Scope) -> Element { - let mut count = use_state(&cx, || 0); - - let taskid = cx.spawn({ - let mut count = count.for_async(); - async { - loop { - tokio::time::delay(std::instant::Duration::from_millis(500)).await; - count += 1; - println!("Current count is {}", count); - } - } - }); -} -``` - -The task will run in the background until it is completed. - -> Note: `spawn` will always spawn a *new* future. You probably want to call it from a hook initializer instead of the main body of your component. - -When bringing lots of values into your task, we provide the `for_async!` macro which will can `for_async` on all values passed in. For types that implement `ToOwned`, `for_async!` will simply call `ToOwned` for that value. - -```rust -fn App(cx: Scope) -> Element { - let mut age = use_state(&cx, || 0); - let mut name = use_state(&cx, || "Bob"); - let mut description = use_state(&cx, || "asd".to_string()); - - let taskid = cx.spawn({ - for_async![count, name, description] - async { /* code that uses count/name/description */ } - }); -} -``` - -## Details of Tasks - -Calling `spawn` is *not* a hook and will *always* generate a new task. Make sure to only spawn tasks when you want to. You should *probably* not call `spawn` in the main body of your component, since a new task will be spawned on every render. - -## Spawning Tokio Tasks (for multithreaded use cases) - -Sometimes, you might want to spawn a background task that needs multiple threads or talk to hardware that might block your app code. In these cases, we can can directly spawn a `tokio task` from our future. For Dioxus-Desktop, your task will be spawned onto Tokio's Multithreaded runtime: - -```rust -cx.spawn({ - tokio::spawn(async { - // some multithreaded work - }).await; - - tokio::spawn_blocking(|| { - // some extremely blocking work - }).await; - - tokio::spawn_local(|| { - // some !Send work - }).await; -}) -``` - -> Note: Tokio tasks must be `Send`. Most hooks are `Send` compatible, but if they aren't, then you can use `spawn_local` to spawn onto Dioxus-Desktop's `localset`. - - diff --git a/docs/guide/src/async/index.md b/docs/guide/src/async/index.md index b0abb494f..a95671399 100644 --- a/docs/guide/src/async/index.md +++ b/docs/guide/src/async/index.md @@ -1,104 +1,9 @@ # Working with Async -Not all apps you'll build can be self-contained with synchronous code. You'll often need to interact with file systems, network interfaces, hardware, or timers. - -So far, we've only talked about building apps with synchronous code, so this chapter will focus integrating asynchronous code into your app. +Often, apps need to interact with file systems, network interfaces, hardware, or timers. This chapter provides an overview of using async code in Dioxus. ## The Runtime By default, Dioxus-Desktop ships with the `Tokio` runtime and automatically sets everything up for you. This is currently not configurable, though it would be easy to write an integration for Dioxus desktop that uses a different asynchronous runtime. -## Send/Sync -Writing apps that deal with Send/Sync can be frustrating at times. Under the hood, Dioxus is not currently thread-safe, so any async code you write does *not* need to be `Send/Sync`. That means that you can use non-thread-safe structures like `Cell`, `Rc`, and `RefCell`. - -All async code in your app is polled on a `LocalSet`, so you can also use `tokio::spawn_local`. - -## Spawning a future - -Currently, all futures in Dioxus must be `'static`. To spawn a future, simply call `cx.spawn()`. - -```rust -rsx!{ - button { - onclick: move |_| cx.spawn(async move { - let result = fetch_thing().await; - }) - } -} -``` - -Calling `spawn` will give you a `JoinHandle` which lets you cancel or pause the future. - - -## Setting state from within a future - -If you start to write some async code, you'll quickly be greeted with the infamous error about borrowed items in static closures. - -``` -error[E0759]: `cx` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement - --> examples/tasks.rs:13:27 - | -12 | fn app(cx: Scope) -> Element { - | ----- this data with an anonymous lifetime `'_`... -13 | let count = use_state(&cx, || 0); - | ^^^ ...is used here... -14 | -15 | use_future(&cx, (), move |_| { - | ---------- ...and is required to live as long as `'static` here - | -note: `'static` lifetime requirement introduced by this bound - --> /Users/jonkelley/Development/dioxus/packages/hooks/src/usefuture.rs:25:29 - | -25 | F: Future + 'static, - | ^^^^^^^ - -For more information about this error, try `rustc --explain E0759`. -error: could not compile `dioxus` due to previous error -``` - -Rustc tends to provide great feedback in its errors, but this particular error is actually quite unhelpful. For reference, here's our code: - -```rust -fn app(cx: Scope) -> Element { - let count = use_state(&cx, || 0); - - cx.spawn(async move { - tokio::time::sleep(Duration::from_millis(1000)).await; - count += 1; - }); - - cx.render(rsx! { - button { - onclick: move |_| count.set(0), - "Reset the count" - } - }) -} -``` - -Simple, right? We spawn a future that updates the value after one second has passed. Well, yes, and no. Our `count` value is only valid for the lifetime of this component, but our future could still be running even after the component re-renders. By default, Dioxus places a requirement on all futures to be `'static`, meaning they can't just borrow state from our hooks outright. - -To get around this, we need to get a `'static` handle to our state. All Dioxus hooks implement `Clone`, so you simply need to call clone before spawning your future. Any time you get the particular error above, make sure you call `Clone` or `ToOwned`. - -```rust -cx.spawn({ - let mut count = count.clone(); - async move { - tokio::time::sleep(Duration::from_millis(1000)).await; - count += 1; - } -}); -``` - -To make this a little bit easier, Dioxus exports the `to_owned!` macro which will create a binding as shown above, which can be quite helpful when dealing with many values. - -```rust -cx.spawn({ - to_owned![count, age, name, description, etc]; - async move { - tokio::time::sleep(Duration::from_millis(1000)).await; - count += 1; - } -}); -``` - +Dioxus is not currently thread-safe, so any async code you write does *not* need to be `Send/Sync`. That means that you can use non-thread-safe structures like `Cell`, `Rc`, and `RefCell`. diff --git a/docs/guide/src/async/loading_state.md b/docs/guide/src/async/loading_state.md deleted file mode 100644 index 66ea79ba9..000000000 --- a/docs/guide/src/async/loading_state.md +++ /dev/null @@ -1,8 +0,0 @@ -# Updating State - -After your future has been spawned, you might want to update the state of our UI. Possible implementations might include: - -- A loading state -- A transition state -- Showing failures -- Listening to sockets and updating the app diff --git a/docs/guide/src/async/spawn.md b/docs/guide/src/async/spawn.md new file mode 100644 index 000000000..aa2d8046c --- /dev/null +++ b/docs/guide/src/async/spawn.md @@ -0,0 +1,29 @@ +# Spawning Futures + +The `use_future` and `use_coroutine` hooks are useful if you want to unconditionally spawn the future. Sometimes, though, you'll want to only spawn a future in response to an event, such as a mouse click. For example, suppose you need to send a request when the user clicks a "log in" button. For this, you can use `cx.spawn`: + +```rust +{{#include ../../examples/spawn.rs:spawn}} +``` + +> Note: `spawn` will always spawn a *new* future. You most likely don't want to call it on every render. + +The future must be `'static` – so any values captured by the task cannot carry any references to `cx`, such as a `UseState`. + +However, since you'll typically need a way to update the value of a hook, you can use `to_owned` to create a clone of the hook handle. You can then use that clone in the async closure. + +To make this a bit less verbose, Dioxus exports the `to_owned!` macro which will create a binding as shown above, which can be quite helpful when dealing with many values. + +```rust +{{#include ../../examples/spawn.rs:to_owned_macro}} +``` + +Calling `spawn` will give you a `JoinHandle` which lets you cancel or pause the future. + +## Spawning Tokio Tasks + +Sometimes, you might want to spawn a background task that needs multiple threads or talk to hardware that might block your app code. In these cases, we can directly spawn a Tokio task from our future. For Dioxus-Desktop, your task will be spawned onto Tokio's Multithreaded runtime: + +```rust +{{#include ../../examples/spawn.rs:tokio}} +``` diff --git a/docs/guide/src/async/coroutines.md b/docs/guide/src/async/use_coroutine.md similarity index 94% rename from docs/guide/src/async/coroutines.md rename to docs/guide/src/async/use_coroutine.md index f59e9102f..b3228b3e8 100644 --- a/docs/guide/src/async/coroutines.md +++ b/docs/guide/src/async/use_coroutine.md @@ -48,7 +48,7 @@ if sync.is_running() { } ``` -This pattern is where coroutines are extremely useful - instead of writing all the complicated logic for pausing our async tasks like we would with JavaScript promises, the Rust model allows us to just not poll our future. +This pattern is where coroutines are extremely useful – instead of writing all the complicated logic for pausing our async tasks like we would with JavaScript promises, the Rust model allows us to just not poll our future. ## Sending Values @@ -103,7 +103,7 @@ async fn editor_service(rx: UnboundedReceiver) { } ``` -We can combine coroutines with Fermi to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state *within* a task and then simply update the "view" values stored in Atoms. It cannot be understated how powerful this technique is: we get all the perks of native Rust tasks with the optimizations and ergonomics of global state. This means your *actual* state does not need to be tied up in a system like Fermi or Redux - the only Atoms that need to exist are those that are used to drive the display/UI. +We can combine coroutines with Fermi to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state *within* a task and then simply update the "view" values stored in Atoms. It cannot be understated how powerful this technique is: we get all the perks of native Rust tasks with the optimizations and ergonomics of global state. This means your *actual* state does not need to be tied up in a system like Fermi or Redux – the only Atoms that need to exist are those that are used to drive the display/UI. ```rust static USERNAME: Atom = |_| "default".to_string(); diff --git a/docs/guide/src/async/use_future.md b/docs/guide/src/async/use_future.md index 0c45ddcb3..7c3a611fe 100644 --- a/docs/guide/src/async/use_future.md +++ b/docs/guide/src/async/use_future.md @@ -1,85 +1,33 @@ # UseFuture -When dealing with asynchronous code, you might need to wait for some action to complete before rendering your component. If you had to build this abstraction yourself, you'd probably end up with some `use_state` spaghetti code. +[`use_future`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_future.html) lets you run an async closure, and provides you with its result. -One of the core hooks that Dioxus provides is `use_future` - a simple hook that lets you tap into a running task. - -## Use case - -The simplest use case of `use_future` is to prevent rendering until some asynchronous code has been completed. Dioxus doesn't currently have a library as sophisticated as React Query for prefetching tasks, but we can get some of the way there with `use_future`. In one of the Dioxus examples, we use `use_future` to download some search data before rendering the rest of the app: +For example, we can make an API request inside `use_future`: ```rust -fn app(cx: Scope) -> Element { - // set "breeds" to the current value of the future - let breeds = use_future(&cx, (), |_| async move { - reqwest::get("https://dog.ceo/api/breeds/list/all") - .await - .unwrap() - .json::() - .await - }); - - let status = match breeds.value() { - Some(Ok(val)) => "ready!", - Some(Err(err)) => "errored!", - None => "loading!", - } -} +{{#include ../../examples/use_future.rs:use_future}} ``` -On first run, the code inside `use_future` will be submitted to the Dioxus scheduler once the component has rendered. Since there's no data ready when the component loads the first time, its "value" will be `None`. +The code inside `use_future` will be submitted to the Dioxus scheduler once the component has rendered. -However, once the future is finished, the component will be re-rendered and a new screen will be displayed - Ok or Err, depending on the outcome of our fetch. +We can use `.value()` to get the result of the future. On the first run, since there's no data ready when the component loads, its value will be `None`. However, once the future is finished, the component will be re-rendered and the value will now be `Some(...)`, containing the return value of the closure. +We can then render that result: + +```rust +{{#include ../../examples/use_future.rs:render}} +``` ## Restarting the Future -The example we showed above will only ever run once. What happens if some value changed on the server and we need to update our future's value? +The `UseFuture` handle provides a `restart` method. It can be used to execute the future again, producing a new value. -Well, the UseFuture handle provides a handy "restart" method. We can wire this up to a button or some other comparison code to get a regenerating future. +## Dependencies -```rust -fn app(cx: Scope) -> Element { - // set "breeds" to the current value of the future - let dog = use_future(&cx, (), |_| async move { - reqwest::get("https://dog.ceo/api/breeds/image/random") - .await - .unwrap() - .json::() - .await - }); - - cx.render(match breeds.value() { - Some(Ok(val)) => rsx!(div { - img { src: "{val.message}"} - button { - onclick: move |_| dog.restart(), - "Click to fetch a new dog" - } - }), - Some(Err(err)) => rsx!("Failed to load dog"), - None => rsx!("Loading dog image!"), - }) -} -``` - -## With Dependencies - -We showed that UseFuture can be regenerated manually, but how can we automatically get it to update whenever some input value changes? This is where the "dependencies" tuple comes into play. We just need to add a value into our tuple argument and it'll be automatically cloned into our future when it starts. +Often, you will need to run the future again every time some value (e.g. a prop) changes. Rather than `.restart`ing it manually, you can provide a tuple of "dependencies" to the hook. It will automatically re-run the future when any of those dependencies change. Example: ```rust -#[inline_props] -fn RandomDog(cx: Scope, breed: String) -> Element { - let dog = use_future(&cx, (breed,), |(breed)| async move { - reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random")) - .await - .unwrap() - .json::() - .await - }); - - // some code as before -} +{{#include ../../examples/use_future.rs:dependency}} ``` diff --git a/docs/guide/src/best_practices/antipatterns.md b/docs/guide/src/best_practices/antipatterns.md new file mode 100644 index 000000000..715c5c00c --- /dev/null +++ b/docs/guide/src/best_practices/antipatterns.md @@ -0,0 +1,33 @@ +# Antipatterns + +This example shows what not to do and provides a reason why a given pattern is considered an "AntiPattern". Most anti-patterns are considered wrong due performance reasons, or for harming code re-usability. + +## Unnecessarily Nested Fragments + +Fragments don't mount a physical element to the DOM immediately, so Dioxus must recurse into its children to find a physical DOM node. This process is called "normalization". This means that deeply nested fragments make Dioxus perform unnecessary work. Prefer one or two levels of fragments / nested components until presenting a true DOM element. + +Only Component and Fragment nodes are susceptible to this issue. Dioxus mitigates this with components by providing an API for registering shared state without the Context Provider pattern. + +```rust +{{#include ../../examples/anti_patterns.rs:nested_fragments}} +``` + +## Incorrect Iterator Keys + +As described in the conditional rendering chapter, list items must have unique keys that are associated with the same items across renders. This helps Dioxus associate state with the contained components, and ensures good diffing performance. Do not omit keys, unless you know that the list is static and will never change. + +```rust +{{#include ../../examples/anti_patterns.rs:iter_keys}} +``` + +## Avoid Interior Mutability in Props + +While it is technically acceptable to have a `Mutex` or a `RwLock` in the props, they will be difficult to use. + +Suppose you have a struct `User` containing the field `username: String`. If you pass a `Mutex` prop to a `UserComponent` component, that component may wish to pass the username as a `&str` prop to a child component. However, it cannot pass that borrowed field down, since it only would live as long as the `Mutex`'s lock, which belongs to the `UserComponent` function. Therefore, the component will be forced to clone the `username` field. + +## Avoid Updating State During Render + +Every time you update the state, Dioxus needs to re-render the component – this is inefficient! Consider refactoring your code to avoid this. + +Also, if you unconditionally update the state during render, it will be re-rendered in an infinite loop. \ No newline at end of file diff --git a/docs/guide/src/state/errorhandling.md b/docs/guide/src/best_practices/error_handling.md similarity index 99% rename from docs/guide/src/state/errorhandling.md rename to docs/guide/src/best_practices/error_handling.md index 3dab3644a..c9a465532 100644 --- a/docs/guide/src/state/errorhandling.md +++ b/docs/guide/src/best_practices/error_handling.md @@ -6,7 +6,7 @@ However, we haven't talked about error handling at all in this guide! In this ch -## The simplest - returning None +## The simplest – returning None Astute observers might have noticed that `Element` is actually a type alias for `Option`. You don't need to know what a `VNode` is, but it's important to recognize that we could actually return nothing at all: diff --git a/docs/guide/src/best_practices/index.md b/docs/guide/src/best_practices/index.md new file mode 100644 index 000000000..2270508f1 --- /dev/null +++ b/docs/guide/src/best_practices/index.md @@ -0,0 +1,29 @@ +# Best Practices + +## Reusable Components + +As much as possible, break your code down into small, reusable components and hooks, instead of implementing large chunks of the UI in a single component. This will help you keep the code maintainable – it is much easier to e.g. add, remove or re-order parts of the UI if it is organized in components. + +Organize your components in modules to keep the codebase easy to navigate! + +## Minimize State Dependencies + +While it is possible to share state between components, this should only be done when necessary. Any component that is associated with a particular state object needs to be re-rendered when that state changes. For this reason: + +- Keep state local to a component if possible +- When sharing state through props, only pass down the specific data necessary + +## Reusable Libraries + +When publishing a library designed to work with Dioxus, we highly advise using only the core feature on the `dioxus` crate. This makes your crate compile faster, makes it more stable, and avoids bringing in incompatible libraries that might make it not compile on unsupported platforms. + + +❌ Don't include unnecessary dependencies in libraries: +```toml +dioxus = { version = "...", features = ["web", "desktop", "full"]} +``` + +✅ Only add the features you need: +```toml +dioxus = { version = "...", features = "core"} +``` diff --git a/docs/guide/src/components/component_children.md b/docs/guide/src/components/component_children.md deleted file mode 100644 index 9fb0f8552..000000000 --- a/docs/guide/src/components/component_children.md +++ /dev/null @@ -1,218 +0,0 @@ -# Passing children and attributes - -Oftentimes, you'll want to wrap some important functionality *around* your state, not directly nested *inside* another component. In these cases, you'll want to pass elements and attributes into a component and let the component place them appropriately. - -In this chapter, you'll learn about: -- Passing elements into components -- Passing attributes into components - - -## The use case - -Let's say you're building a user interface and want to make some part of it a clickable link to another website. You would normally start with the HTML `
` tag, like so: - -```rust -rsx!( - a { - href: "https://google.com" - "Link to google" - } -) -``` - -But, what if we wanted to style our `` tag? Or wrap it with some helper icon? We could abstract our RSX into its own component: - - -```rust -#[derive(Props)] -struct ClickableProps<'a> { - href: &'a str, - title: &'a str -} - -fn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element { - cx.render(rsx!( - a { - href: "{cx.props.href}" - "{cx.props.title}" - } - )) -} -``` - -And then use it in our code like so: - -```rust -rsx!( - Clickable { - href: "https://google.com" - title: "Link to Google" - } -) -``` - -Let's say we don't just want the text to be clickable, but we want another element, like an image, to be clickable. How do we implement that? - -## Passing children - -If we want to pass an image into our component, we can just adjust our props and component to allow any `Element`. - -```rust -#[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}", - &cx.props.body - } - )) -} -``` - -Then, at the call site, we can render some nodes and pass them in: - -```rust -rsx!( - Clickable { - href: "https://google.com" - body: cx.render(rsx!( - img { src: "https://www.google.com/logos/doodles/..." } - )) - } -) -``` - -## Auto Conversion of the `Children` field - -This pattern can become tedious in some instances, so Dioxus actually performs an implicit conversion of any `rsx` calls inside components into `Elements` at the `children` field. This means you must explicitly declare if a component can take children. - -```rust -#[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}", - &cx.props.children - } - )) -} -``` - -Now, whenever we use `Clickable` in another component, we don't need to call `render` on child nodes - it will happen automatically! -```rust -rsx!( - Clickable { - href: "https://google.com" - img { src: "https://www.google.com/logos/doodles/...." } - } -) -``` - -> Note: Passing children into components will break any memoization due to the associated lifetime. - -While technically allowed, it's an antipattern to pass children more than once in a component and will probably cause your app to crash. - -However, because the `Element` is transparently a `VNode`, we can actually match on it to extract the nodes themselves, in case we are expecting a specific format: - -```rust -fn clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element { - match cx.props.children { - Some(VNode::Text(text)) => { - // ... - } - _ => { - // ... - } - } -} -``` - -Passing nodes through props means that they are immutable. If you find yourself needing to mutate nodes passed through props, consider creating a new node in its place that takes on its attributes, children, and listeners. - - - -## Passing handlers - -Dioxus also provides some implicit conversions from listener attributes into an `EventHandler` for any field on components that starts with `on`. IE `onclick`, `onhover`, etc. For properties, we want to define our `on` fields as an event handler: - - -```rust -#[derive(Props)] -struct ClickableProps<'a> { - onclick: EventHandler<'a, MouseEvent> -} - -fn clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element { - cx.render(rsx!( - a { - onclick: move |evt| cx.props.onclick.call(evt) - } - )) -} -``` - -Then, we can attach a listener at the call site: - -```rust -rsx!( - Clickable { - onclick: move |_| log::info!("Clicked"), - } -) -``` - -Currently, Dioxus does not support an arbitrary amount of listeners - they must be strongly typed in `Properties`. If you need this use case, you can pass in an element with these listeners, or dip down into the `NodeFactory` API. - - -## Wrapping up - -In this chapter, we learned: -- How to pass arbitrary nodes through the tree -- How the `children` field works on component properties -- How the `attributes` field works on component properties -- How to convert `listeners` into `EventHandlers` for components -- How to extend any node with custom attributes and children diff --git a/docs/guide/src/components/component_example_title.png b/docs/guide/src/components/component_example_title.png deleted file mode 100644 index 60a5a329e..000000000 Binary files a/docs/guide/src/components/component_example_title.png and /dev/null differ diff --git a/docs/guide/src/components/component_example_votes.png b/docs/guide/src/components/component_example_votes.png deleted file mode 100644 index b46739b24..000000000 Binary files a/docs/guide/src/components/component_example_votes.png and /dev/null differ diff --git a/docs/guide/src/components/exporting_components.md b/docs/guide/src/components/exporting_components.md deleted file mode 100644 index 727e5b6b6..000000000 --- a/docs/guide/src/components/exporting_components.md +++ /dev/null @@ -1,292 +0,0 @@ -# Reusing, Importing, and Exporting Components - -As your application grows in size, you'll want to start breaking your UI into components and, eventually, different files. This is a great idea to encapsulate functionality of your UI and scale your team. - -In this chapter we'll cover: -- Rust's modules -- Pub/Private components -- Structure for large components - -## Breaking it down -Let's say our app looks something like this: - -```shell -├── Cargo.toml -└── src - └── main.rs -``` - -```rust -// main.rs -use dioxus::prelude::*; - -fn main() { - dioxus::desktop::launch(App); -} - -fn App(Scope) -> Element {} - -#[derive(PartialEq, Props)] -struct PostProps{} -fn Post(Scope) -> Element {} - -#[derive(PartialEq, Props)] -struct VoteButtonsProps {} -fn VoteButtons(Scope) -> Element {} - -#[derive(PartialEq, Props)] -struct TitleCardProps {} -fn TitleCard(Scope) -> Element {} - -#[derive(PartialEq, Props)] -struct MetaCardProps {} -fn MetaCard(Scope) -> Element {} - -#[derive(PartialEq, Props)] -struct ActionCardProps {} -fn ActionCard(Scope) -> Element {} -``` - -That's a lot of components for one file! We've successfully refactored our app into components, but we should probably start breaking it up into a file for each component. - -## Breaking into different files - -Fortunately, Rust has a built-in module system that's much cleaner than what you might be used to in JavaScript. Because `VoteButtons`, `TitleCard`, `MetaCard`, and `ActionCard` all belong to the `Post` component, let's put them all in a folder together called "post". We'll make a file for each component and move the props and render function. - -```rust -// src/post/action.rs - -use dioxus::prelude::*; - -#[derive(PartialEq, Props)] -struct ActionCardProps {} -fn ActionCard(Scope) -> Element {} -``` - -We should also create a `mod.rs` file in the `post` folder so we can use it from our `main.rs`. Our `Post` component and its props will go into this file. - -```rust -// src/post/mod.rs - -use dioxus::prelude::*; - -#[derive(PartialEq, Props)] -struct PostProps {} -fn Post(Scope) -> Element {} -``` - -```shell -├── Cargo.toml -└── src - ├── main.rs - └── post - ├── vote.rs - ├── title.rs - ├── meta.rs - ├── action.rs - └── mod.rs -``` - -In our `main.rs`, we'll want to declare the `post` module so we can access our `Post` component. - -```rust -// main.rs -use dioxus::prelude::*; - -fn main() { - dioxus::desktop::launch(App); -} - -mod post; - -fn App(Scope) -> Element { - cx.render(rsx!{ - post::Post { - id: Uuid::new_v4(), - score: 10, - comment_count: 10, - post_time: std::Instant::now(), - url: "example".to_string(), - title: "Title".to_string(), - original_poster: "me".to_string() - } - }) -} -``` - -If you tried to build this app right now, you'll get an error message saying that `Post is private, try changing it to public`. This is because we haven't properly exported our component! To fix this, we need to make sure both the Props and Component are declared as "public": - -```rust -// src/post/mod.rs - -use dioxus::prelude::*; - -#[derive(PartialEq, Props)] -pub struct PostProps {} -pub fn Post(Scope) -> Element {} -``` - -While we're here, we also need to make sure each of our subcomponents are included as modules and exported. - -Our "post/mod.rs" file will eventually look like this: - -```rust -use dioxus::prelude::*; - -mod vote; -mod title; -mod meta; -mod action; - -#[derive(Props, PartialEq)] -pub struct PostProps { - id: uuid::Uuid, - score: i32, - comment_count: u32, - post_time: std::time::Instant, - url: String, - title: String, - original_poster: String -} - -pub fn Post(Scope) -> Element { - cx.render(rsx!{ - div { class: "post-container" - vote::VoteButtons { - score: props.score, - } - title::TitleCard { - title: props.title, - url: props.url, - } - meta::MetaCard { - original_poster: props.original_poster, - post_time: props.post_time, - } - action::ActionCard { - post_id: props.id - } - } - }) -} -``` - -Ultimately, including and exporting components is governed by Rust's module system. [The Rust book is a great resource to learn about these concepts in greater detail.](https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html) - -## Final structure: - -```shell -├── Cargo.toml -└── src - ├── main.rs - └── post - ├── vote.rs - ├── title.rs - ├── meta.rs - ├── action.rs - └── mod.rs -``` - -```rust -// main.rs: -use dioxus::prelude::*; - -fn main() { - dioxus::desktop::launch(App); -} - -mod post; - -fn App(Scope) -> Element { - cx.render(rsx!{ - post::Post { - id: Uuid::new_v4(), - score: 10, - comment_count: 10, - post_time: std::Instant::now(), - url: "example".to_string(), - title: "Title".to_string(), - original_poster: "me".to_string() - } - }) -} -``` - - -```rust -// src/post/mod.rs -use dioxus::prelude::*; - -mod vote; -mod title; -mod meta; -mod action; - -#[derive(Props, PartialEq)] -pub struct PostProps { - id: uuid::Uuid, - score: i32, - comment_count: u32, - post_time: std::time::Instant, - url: String, - title: String, - original_poster: String -} - -pub fn Post(Scope) -> Element { - cx.render(rsx!{ - div { class: "post-container" - vote::VoteButtons { - score: props.score, - } - title::TitleCard { - title: props.title, - url: props.url, - } - meta::MetaCard { - original_poster: props.original_poster, - post_time: props.post_time, - } - action::ActionCard { - post_id: props.id - } - } - }) -} -``` - -```rust -// src/post/vote.rs -use dioxus::prelude::*; - -#[derive(PartialEq, Props)] -pub struct VoteButtonsProps {} -pub fn VoteButtons(Scope) -> Element {} -``` - -```rust -// src/post/title.rs -use dioxus::prelude::*; - -#[derive(PartialEq, Props)] -pub struct TitleCardProps {} -pub fn TitleCard(Scope) -> Element {} -``` - -```rust -// src/post/meta.rs -use dioxus::prelude::*; - -#[derive(PartialEq, Props)] -pub struct MetaCardProps {} -pub fn MetaCard(Scope) -> Element {} -``` - -```rust -// src/post/action.rs -use dioxus::prelude::*; - -#[derive(PartialEq, Props)] -pub struct ActionCardProps {} -pub fn ActionCard(Scope) -> Element {} -``` diff --git a/docs/guide/src/components/index.md b/docs/guide/src/components/index.md deleted file mode 100644 index ff4811a33..000000000 --- a/docs/guide/src/components/index.md +++ /dev/null @@ -1,42 +0,0 @@ -# Introduction to Components - -In the previous chapter, we learned about Elements and how they can be composed to create a basic user interface. Now, we'll learn how to group Elements together to form Components. We'll cover: - -- What makes a Component -- How to model a component and its properties in Dioxus -- How to "think declaratively" - -## What is a component? - -In short, a component is a special function that takes input properties and outputs an Element. Much like a function encapsulates some specific computation task, a Component encapsulates some specific rendering task – typically, rendering an isolated part of the user interface. - -### Real-world example - -Let's use a Reddit post as an example: - -![Reddit Post](../images/reddit_post.png) - -If we look at the layout of the component, we notice quite a few buttons and pieces of functionality: - -- Upvote/Downvote -- View comments -- Share -- Save -- Hide -- Give award -- Report -- Crosspost -- Filter by site -- View article -- Visit user - -If we included all this functionality in one `rsx!` call it would be huge! Instead, let's break the post down into Components: - -![Post as Component](../images/reddit_post_components.png) - -- **VoteButton**: Upvote/Downvote -- **TitleCard**: Title, Filter-By-Url -- **MetaCard**: Original Poster, Time Submitted -- **ActionCard**: View comments, Share, Save, Hide, Give award, Report, Crosspost - -In this chapter, we'll learn how to define these components. diff --git a/docs/guide/src/components/propsmacro.md b/docs/guide/src/components/propsmacro.md deleted file mode 100644 index ec9ff255e..000000000 --- a/docs/guide/src/components/propsmacro.md +++ /dev/null @@ -1,232 +0,0 @@ -# Component Properties - -Dioxus components are functions that accept Props as input and output an Element. In fact, the `App` function you saw in the previous chapter was a component with no Props! Most components, however, will need to take some Props to render something useful – so, in this section, we'll learn about props: - -- Deriving the Props trait -- Memoization through PartialEq -- Optional fields on props -- The inline_props macro - -## Props - -The input of your Component must be passed in a single struct, which must implement the `Props` trait. We can derive this trait automatically with `#[derive(Props)]`. - -> Dioxus `Props` is very similar to [@idanarye](https://github.com/idanarye)'s [TypedBuilder crate](https://github.com/idanarye/rust-typed-builder) and supports many of the same parameters. - -There are 2 flavors of Props: owned and borrowed. - -- All Owned Props must implement `PartialEq` -- Borrowed props [borrow](https://doc.rust-lang.org/beta/rust-by-example/scope/borrow.html) values from the parent Component - -### Owned Props - -Owned Props are very simple – they don't borrow anything. Example: -```rust -// Remember: owned props must implement PartialEq! -#[derive(PartialEq, Props)] -struct VoteButtonProps { - score: i32 -} - -fn VoteButton(cx: Scope) -> Element { - cx.render(rsx!{ - div { - div { "+" } - div { "{cx.props.score}"} - div { "-" } - } - }) -} -``` - -Now, we can use the VoteButton Component like we would use a regular HTML element: - -```rust -fn main() { - dioxus::desktop::launch(App); -} - -fn App(cx: Scope) -> Element { - cx.render(rsx! ( - VoteButton { score: 42 } - )) -} -``` - -And we can see that the Component indeed gets rendered: - -![Screenshot of running app. Text: "+ \ 42 \ -"](component_example_votes.png) - -> The simplest Owned Props you can have is `()` - or no value at all. This is what the `App` Component takes as props. `Scope` accepts a generic for the Props which defaults to `()`. -> -> ```rust ->// this scope ->Scope<()> -> ->// is the same as this scope ->Scope ->``` - -### Borrowed Props - -Owning props works well if your props are easy to copy around - like a single number. But what if we need to pass a larger data type, like a String from an `App` Component to a `TitleCard` subcomponent? A naive solution might be to [`.clone()`](https://doc.rust-lang.org/std/clone/trait.Clone.html) the String, creating a copy of it for the subcomponent – but this would be inefficient, especially for larger Strings. - -Rust allows for something more efficient – borrowing the String as a `&str`. Instead of creating a copy, this will give us a reference to the original String – this is what Borrowed Props are for! - -However, if we create a reference a String, Rust will require us to show that the String will not go away while we're using the reference. Otherwise, if we referenced something that doesn't exist, Bad Things could happen. To prevent this, Rust asks us to define a lifetime for the reference: - -```rust -#[derive(Props)] -struct TitleCardProps<'a> { - title: &'a str, -} - -fn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element { - cx.render(rsx!{ - h1 { "{cx.props.title}" } - }) -} -``` - -This lifetime `'a` tells the compiler that as long as `title` exists, the String it was created from must also exist. Dioxus will happily accept such a component – we can now render it alongside our VoteButton! - -```rust -fn App(cx: Scope) -> Element { - // For the sake of an example, we create the &str here. - // But you might as well borrow it from an owned String type. - let hello = "Hello Dioxus!"; - - cx.render(rsx! ( - VoteButton { score: 42 }, - TitleCard { title: hello } - )) -} -``` -![New screenshot of running app, now including a "Hello Dioxus!" heading.](component_example_title.png) - -## Memoization - -Dioxus uses Memoization for a more efficient user interface. Memoization is the process in which we check if a component actually needs to be re-rendered when its props change. If a component's properties change but they wouldn't affect the output, then we don't need to re-render the component, saving time! - -For example, let's say we have a component that has two children: - -```rust -fn Demo(cx: Scope) -> Element { - // don't worry about these 2, we'll cover them later - let name = use_state(&cx, || String::from("bob")); - let age = use_state(&cx, || 21); - - cx.render(rsx!{ - Name { name: name } - Age { age: age } - }) -} -``` - -If `name` changes but `age` does not, then there is no reason to re-render our `Age` component since the contents of its props did not meaningfully change. - -Dioxus memoizes owned components. It uses `PartialEq` to determine if a component needs rerendering, or if it has stayed the same. This is why you must derive PartialEq! - -> This means you can always rely on props with `PartialEq` or no props at all to act as barriers in your app. This can be extremely useful when building larger apps where properties frequently change. By moving our state into a global state management solution, we can achieve precise, surgical re-renders, improving the performance of our app. - -Borrowed Props cannot be safely memoized. However, this is not a problem – Dioxus relies on the memoization of their parents to determine if they need to be rerendered. - -## Optional Props - -You can easily create optional fields by using the `Option<…>` type for a field: - -```rust -#[derive(Props, PartialEq)] -struct MyProps { - name: String, - - description: Option -} - -fn Demo(cx: MyProps) -> Element { - let text = match cx.props.description { - Some(d) => d, // if a value is provided - None => "No description" // if the prop is omitted - }; - - cx.render(rsx! { - "{name}": "{text}" - }) -} -``` -In this example `name` is a required prop and `description` is optional. -This means we can completely omit the description field when calling the component: - -```rust -rsx!{ - Demo { - name: "Thing".to_string(), - // description is omitted - } -} -``` -Additionally if we provide a value we don't have to wrap it with `Some(…)`. This is done automatically for us: - -```rust -rsx!{ - Demo { - name: "Thing".to_string(), - description: "This is explains it".to_string(), - } -} -``` - -If you want to make a prop required even though it is of type `Option` you can provide the `!optional` modifier: - -```rust -#[derive(Props, PartialEq)] -struct MyProps { - name: String, - - #[props(!optional)] - description: Option -} -``` - -This can be especially useful if you have a type alias named `Option` in the current scope. - -For more information on how tags work, check out the [TypedBuilder](https://github.com/idanarye/rust-typed-builder) crate. However, all attributes for props in Dioxus are flattened (no need for `setter` syntax) and the `optional` field is new. The `optional` modifier is a combination of two separate modifiers: `default` and `strip_option` and it is automatically detected on `Option<…>` types. - -The full list of Dioxus' modifiers includes: - -- `default` - automatically add the field using its `Default` implementation -- `optional` - alias for `default` and `strip_option` -- `into` - automatically call `into` on the value at the callsite - - -## The `inline_props` macro - -So far, every Component function we've seen had a corresponding ComponentProps struct to pass in props. This was quite verbose... Wouldn't it be nice to have props as simple function arguments? Then we wouldn't need to define a Props struct, and instead of typing `cx.props.whatever`, we could just use `whatever` directly! - -`inline_props` allows you to do just that. Instead of typing the "full" version: - -```rust -#[derive(Props, PartialEq)] -struct TitleCardProps { - title: String, -} - -fn TitleCard(cx: Scope) -> Element { - cx.render(rsx!{ - h1 { "{cx.props.title}" } - }) -} -``` - -...you can define a function that accepts props as arguments. Then, just annotate it with `#[inline_props]`, and the macro will turn it into a regular Component for you: - -```rust -#[inline_props] -fn TitleCard(cx: Scope, title: String) -> Element { - cx.render(rsx!{ - h1 { "{title}" } - }) -} -``` - -> While the new Component is shorter and easier to read, this macro should not be used by library authors since you have less control over Prop documentation. diff --git a/docs/guide/src/contributing.md b/docs/guide/src/contributing.md new file mode 100644 index 000000000..0bfde1a15 --- /dev/null +++ b/docs/guide/src/contributing.md @@ -0,0 +1,20 @@ +# Contributing + +Development happens in the [Dioxus GitHub repository](https://github.com/DioxusLabs/dioxus). If you've found a bug or have an idea for a feature, please submit an issue (check if someone hasn't [done it already](https://github.com/DioxusLabs/dioxus/issues) though). + +[GitHub discussions](https://github.com/DioxusLabs/dioxus/discussions) can be used as a place to ask for help or talk about features. You can also join [our Discord channel](https://discord.gg/XgGxMSkvUM) where some development discussion happens. + +## Improving Docs + +If you'd like to improve the docs, PRs are welcome! Both Rust docs ([source](https://github.com/DioxusLabs/dioxus/tree/master/packages)) and this guide ([source](https://github.com/DioxusLabs/dioxus/tree/master/docs/guide)) can be found in the GitHub repo. + +## Working on the Ecosystem + +Part of what makes React great is the rich ecosystem. We'd like the same for Dioxus! So if you have a library in mind that you'd like to write, and that you think many people would benefit from, it will be appreciated. You can [browse npm.js](https://www.npmjs.com/search?q=keywords:react-component) for inspiration. + +## Bugs & Features + +If you've fixed [an issue that's open](https://github.com/DioxusLabs/dioxus/issues), feel free to submit a PR! You can also take a look at [the roadmap](./roadmap.md) and work on something in there. Consider [reaching out](https://discord.gg/XgGxMSkvUM) to the team first to make sure everyone's on the same page, and you don't do useless work! + +All pull requests (including those made by a team member) must be approved by at least one other team member. +Larger, more nuanced decisions about design, architecture, breaking changes, trade-offs, etc. are made by team consensus. diff --git a/docs/reference/src/guide/custom_renderer.md b/docs/guide/src/custom_renderer/index.md similarity index 97% rename from docs/reference/src/guide/custom_renderer.md rename to docs/guide/src/custom_renderer/index.md index 61f35c9ba..b5ccc51b1 100644 --- a/docs/reference/src/guide/custom_renderer.md +++ b/docs/guide/src/custom_renderer/index.md @@ -47,7 +47,7 @@ The Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia. ### An example -For the sake of understanding, lets consider this example - a very simple UI declaration: +For the sake of understanding, lets consider this example – a very simple UI declaration: ```rust rsx!( h1 {"hello world"} ) @@ -219,7 +219,7 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent { ## Custom raw elements -If you need to go as far as relying on custom elements for your renderer - you totally can. This still enables you to use Dioxus' reactive nature, component system, shared state, and other features, but will ultimately generate different nodes. All attributes and listeners for the HTML and SVG namespace are shuttled through helper structs that essentially compile away (pose no runtime overhead). You can drop in your own elements any time you want, with little hassle. However, you must be absolutely sure your renderer can handle the new type, or it will crash and burn. +If you need to go as far as relying on custom elements for your renderer – you totally can. This still enables you to use Dioxus' reactive nature, component system, shared state, and other features, but will ultimately generate different nodes. All attributes and listeners for the HTML and SVG namespace are shuttled through helper structs that essentially compile away (pose no runtime overhead). You can drop in your own elements any time you want, with little hassle. However, you must be absolutely sure your renderer can handle the new type, or it will crash and burn. These custom elements are defined as unit structs with trait implementations. diff --git a/docs/guide/src/describing_ui/component_children.md b/docs/guide/src/describing_ui/component_children.md new file mode 100644 index 000000000..588c9eb75 --- /dev/null +++ b/docs/guide/src/describing_ui/component_children.md @@ -0,0 +1,31 @@ +# Component Children + +In some cases, you may wish to create a component that acts as a container for some other content, without the component needing to know what that content is. To achieve this, create a prop of type `Element`: + +```rust +{{#include ../../examples/component_element_props.rs:Clickable}} +``` + +Then, when rendering the component, you can pass in the output of `cx.render(rsx!(...))`: + +```rust +{{#include ../../examples/component_element_props.rs:Clickable_usage}} +``` + +> Note: Since `Element<'a>` is a borrowed prop, there will be no memoization. + +> Warning: While it may compile, do not include the same `Element` more than once in the RSX. The resulting behavior is unspecified. + +## The `children` field + +Rather than passing the RSX through a regular prop, you may wish to accept children similarly to how elements can have children. The "magic" `children` prop lets you achieve this: + +```rust +{{#include ../../examples/component_children.rs:Clickable}} +``` + +This makes using the component much simpler: simply put the RSX inside the `{}` brackets – and there is no need for a `render` call or another macro! + +```rust +{{#include ../../examples/component_children.rs:Clickable_usage}} +``` diff --git a/docs/guide/src/describing_ui/component_props.md b/docs/guide/src/describing_ui/component_props.md new file mode 100644 index 000000000..bcf82a6b8 --- /dev/null +++ b/docs/guide/src/describing_ui/component_props.md @@ -0,0 +1,141 @@ +# Component Props + +Just like you can pass arguments to a function, you can pass props to a component that customize its behavior! The components we've seen so far didn't accept any props – so let's write some components that do. + +## `#[derive(Props)]` + +Component props are a single struct annotated with `#[derive(Props)]`. For a component to accept props, the type of its argument must be `Scope`. Then, you can access the value of the props using `cx.props`. + +There are 2 flavors of Props structs: +- Owned props: + - Don't have an associated lifetime + - Implement `PartialEq`, allowing for memoization (if the props don't change, Dioxus won't re-render the component) +- Borrowed props: + - [Borrow](https://doc.rust-lang.org/beta/rust-by-example/scope/borrow.html) from a parent component + - Cannot be memoized due to lifetime constraints + + +### Owned Props + +Owned Props are very simple – they don't borrow anything. Example: + +```rust +{{#include ../../examples/component_owned_props.rs:Likes}} +``` + +You can then pass prop values to the component the same way you would pass attributes to an element: +```rust +{{#include ../../examples/component_owned_props.rs:App}} +``` + +![Screenshot: Likes component](./images/component_owned_props_screenshot.png) + +### Borrowed Props + +Owning props works well if your props are easy to copy around – like a single number. But what if we need to pass a larger data type, like a String from an `App` Component to a `TitleCard` subcomponent? A naive solution might be to [`.clone()`](https://doc.rust-lang.org/std/clone/trait.Clone.html) the String, creating a copy of it for the subcomponent – but this would be inefficient, especially for larger Strings. + +Rust allows for something more efficient – borrowing the String as a `&str` – this is what Borrowed Props are for! + +```rust +{{#include ../../examples/component_borrowed_props.rs:TitleCard}} +``` + +We can then use the component like this: + +```rust +{{#include ../../examples/component_borrowed_props.rs:App}} +``` +![Screenshot: TitleCard component](./images/component_borrowed_props_screenshot.png) + + +## Prop Options + +The `#[derive(Props)]` macro has some features that let you customize the behavior of props. + +### Optional Props + +You can create optional fields by using the `Option<…>` type for a field: + +```rust +{{#include ../../examples/component_props_options.rs:OptionalProps}} +``` + +Then, you can choose to either provide them or not: + +```rust +{{#include ../../examples/component_props_options.rs:OptionalProps_usage}} +``` + +### Explicitly Required `Option`s + +If you want to explicitly require an `Option`, and not an optional prop, you can annotate it with `#[props(!optional)]`: + +```rust +{{#include ../../examples/component_props_options.rs:ExplicitOption}} +``` + +Then, you have to explicitly pass either `Some("str")` or `None`: + +```rust +{{#include ../../examples/component_props_options.rs:ExplicitOption_usage}} +``` + +### Default Props + +You can use `#[props(default = 42)]` to make a field optional and specify its default value: + +```rust +{{#include ../../examples/component_props_options.rs:DefaultComponent}} +``` + +Then, similarly to optional props, you don't have to provide it: + +```rust +{{#include ../../examples/component_props_options.rs:DefaultComponent_usage}} +``` + +### Automatic Conversion with `.into` + +It is common for Rust functions to accept `impl Into` rather than just `SomeType` to support a wider range of parameters. If you want similar functionality with props, you can use `#[props(into)]`. For example, you could add it on a `String` prop – and `&str` will also be automatically accepted, as it can be converted into `String`: + +```rust +{{#include ../../examples/component_props_options.rs:IntoComponent}} +``` + +Then, you can use it so: + +```rust +{{#include ../../examples/component_props_options.rs:IntoComponent_usage}} +``` + +## The `inline_props` macro + +So far, every Component function we've seen had a corresponding ComponentProps struct to pass in props. This was quite verbose... Wouldn't it be nice to have props as simple function arguments? Then we wouldn't need to define a Props struct, and instead of typing `cx.props.whatever`, we could just use `whatever` directly! + +`inline_props` allows you to do just that. Instead of typing the "full" version: + +```rust +#[derive(Props, PartialEq)] +struct TitleCardProps { + title: String, +} + +fn TitleCard(cx: Scope) -> Element { + cx.render(rsx!{ + h1 { "{cx.props.title}" } + }) +} +``` + +...you can define a function that accepts props as arguments. Then, just annotate it with `#[inline_props]`, and the macro will turn it into a regular Component for you: + +```rust +#[inline_props] +fn TitleCard(cx: Scope, title: String) -> Element { + cx.render(rsx!{ + h1 { "{title}" } + }) +} +``` + +> While the new Component is shorter and easier to read, this macro should not be used by library authors since you have less control over Prop documentation. diff --git a/docs/guide/src/describing_ui/components.md b/docs/guide/src/describing_ui/components.md new file mode 100644 index 000000000..c0b35c222 --- /dev/null +++ b/docs/guide/src/describing_ui/components.md @@ -0,0 +1,27 @@ +# Components + +Just like you wouldn't want to write a complex program in a single, long, `main` function, you shouldn't build a complex UI in a single `App` function. Instead, it would be better to break down the functionality of an app in logical parts called components. + +A component is a Rust function, named in UpperCammelCase, that takes a `Scope` parameter and returns an `Element` describing the UI it wants to render. In fact, our `App` function is a component! + +```rust +{{#include ../../examples/hello_world_desktop.rs:component}} +``` + +> You'll probably want to add `#![allow(non_snake_case)]` to the top of your crate to avoid warnings about the function name + +A Component is responsible for some rendering task – typically, rendering an isolated part of the user interface. For example, you could have an `About` component that renders a short description of Dioxus Labs: + +```rust +{{#include ../../examples/components.rs:About}} +``` + +Then, you can render your component in another component, similarly to how elements are rendered: + +```rust +{{#include ../../examples/components.rs:App}} +``` + +![Screenshot containing the About component twice](./images/screenshot_about_component.png) + +> At this point, it might seem like components are nothing more than functions. However, as you learn more about the features of Dioxus, you'll see that they are actually more powerful! diff --git a/docs/guide/src/describing_ui/images/component_borrowed_props_screenshot.png b/docs/guide/src/describing_ui/images/component_borrowed_props_screenshot.png new file mode 100644 index 000000000..6f1864662 Binary files /dev/null and b/docs/guide/src/describing_ui/images/component_borrowed_props_screenshot.png differ diff --git a/docs/guide/src/describing_ui/images/component_owned_props_screenshot.png b/docs/guide/src/describing_ui/images/component_owned_props_screenshot.png new file mode 100644 index 000000000..2a1146cd1 Binary files /dev/null and b/docs/guide/src/describing_ui/images/component_owned_props_screenshot.png differ diff --git a/docs/guide/src/describing_ui/images/screenshot_about_component.png b/docs/guide/src/describing_ui/images/screenshot_about_component.png new file mode 100644 index 000000000..36f7250a1 Binary files /dev/null and b/docs/guide/src/describing_ui/images/screenshot_about_component.png differ diff --git a/docs/guide/src/describing_ui/index.md b/docs/guide/src/describing_ui/index.md new file mode 100644 index 000000000..9e6f0d462 --- /dev/null +++ b/docs/guide/src/describing_ui/index.md @@ -0,0 +1,106 @@ +# Describing the UI + +Dioxus is a *declarative* framework. This means that instead of telling Dioxus what to do (e.g. to "create an element" or "set color to red") we simply *declare* what we want the UI to look like using RSX. + +You have already seen a simple example or RSX syntax in the "hello world" application: + +```rust +{{#include ../../examples/hello_world_desktop.rs:component}} +``` + +Here, we use the `rsx!` macro to *declare* that we want a `div` element, containing the text `"Hello, world!"`. Dioxus takes the RSX and constructs a UI from it. + +## RSX Features + +RSX is very similar to HTML in that it describes elements with attributes and children. Here's an empty `div` element in RSX, as well as the resulting HTML: + +```rust +{{#include ../../examples/rsx_overview.rs:empty}} +``` +```html +
+``` + +### Children + +To add children to an element, put them inside the `{}` brackets. They can be either other elements, or text. For example, you could have an `ol` (ordered list) element, containing 3 `li` (list item) elements, each of which contains some text: + +```rust +{{#include ../../examples/rsx_overview.rs:children}} +``` +```html +
    +
  1. First Item
  2. +
  3. Second Item
  4. +
  5. Third Item
  6. +
+``` + +### Fragments + +You can also "group" elements by wrapping them in `Fragment {}`. This will not create any additional elements. + +> Note: you can also render multiple elements at the top level of `rsx!` and they will be automatically grouped – no need for an explicit `Fragment {}` there. + +```rust +{{#include ../../examples/rsx_overview.rs:fragments}} +``` + +```html +

First Item

+

Second Item

+a group +of three +items +``` + +### Attributes + +Attributes are also specified inside the `{}` brackets, using the `name: value` syntax. You can provide the value as a literal in the RSX: +```rust +{{#include ../../examples/rsx_overview.rs:attributes}} +``` +```html +
Log In +``` + +> Note: All attributes defined in `dioxus-html` follow the snake_case naming convention. They transform their `snake_case` names to HTML's `camelCase` attributes. + +#### Custom Attributes + +Dioxus has a pre-configured set of attributes that you can use. RSX is validated at compile time to make sure you didn't specify an invalid attribute. If you want to override this behavior with a custom attribute name, specify the attribute in quotes: + +```rust +{{#include ../../examples/rsx_overview.rs:custom_attributes}} +``` +```html + + Rust is cool + +``` + +### Interpolation + +Similarly to how you can [format](https://doc.rust-lang.org/rust-by-example/hello/print/fmt.html) Rust strings, you can also interpolate in RSX text. Use `{variable}` to Display the value of a variable in a string, or `{variable:?}` to use the Debug representation: + +```rust +{{#include ../../examples/rsx_overview.rs:formatting}} +``` +```html + +
Coordinates: (42, 0) +
ES
+
42
+
+``` + +### Expressions + +You can include arbitrary Rust expressions within RSX, but you must escape them in `[]` brackets: + +```rust +{{#include ../../examples/rsx_overview.rs:expression}} +``` +```html +DIOXUS +``` \ No newline at end of file diff --git a/docs/guide/src/describing_ui/special_attributes.md b/docs/guide/src/describing_ui/special_attributes.md new file mode 100644 index 000000000..6285e28ef --- /dev/null +++ b/docs/guide/src/describing_ui/special_attributes.md @@ -0,0 +1,63 @@ +# Special Attributes + +While most attributes are simply passed on to the HTML, some have special behaviors. + +## The HTML Escape Hatch + +If you're working with pre-rendered assets, output from templates, or output from a JS library, then you might want to pass HTML directly instead of going through Dioxus. In these instances, reach for `dangerous_inner_html`. + +For example, shipping a markdown-to-Dioxus converter might significantly bloat your final application size. Instead, you'll want to pre-render your markdown to HTML and then include the HTML directly in your output. We use this approach for the [Dioxus homepage](https://dioxuslabs.com): + + +```rust +{{#include ../../examples/dangerous_inner_html.rs:dangerous_inner_html}} +``` + +> Note! This attribute is called "dangerous_inner_html" because it is **dangerous** to pass it data you don't trust. If you're not careful, you can easily expose [cross-site scripting (XSS)](https://en.wikipedia.org/wiki/Cross-site_scripting) attacks to your users. +> +> If you're handling untrusted input, make sure to sanitize your HTML before passing it into `dangerous_inner_html` – or just pass it to a Text Element to escape any HTML tags. + + +## Boolean Attributes + +Most attributes, when rendered, will be rendered exactly as the input you provided. However, some attributes are considered "boolean" attributes and just their presence determines whether they affect the output. For these attributes, a provided value of `"false"` will cause them to be removed from the target element. + +So this RSX wouldn't actually render the `hidden` attribute: + +```rust +{{#include ../../examples/boolean_attribute.rs:boolean_attribute}} +``` +```html +
hello
+``` + +Not all attributes work like this however. *Only the following attributes* have this behavior: + +- `allowfullscreen` +- `allowpaymentrequest` +- `async` +- `autofocus` +- `autoplay` +- `checked` +- `controls` +- `default` +- `defer` +- `disabled` +- `formnovalidate` +- `hidden` +- `ismap` +- `itemscope` +- `loop` +- `multiple` +- `muted` +- `nomodule` +- `novalidate` +- `open` +- `playsinline` +- `readonly` +- `required` +- `reversed` +- `selected` +- `truespeed` + +For any other attributes, a value of `"false"` will be sent directly to the DOM. diff --git a/docs/guide/src/elements/conditional_rendering.md b/docs/guide/src/elements/conditional_rendering.md deleted file mode 100644 index f5d2836f3..000000000 --- a/docs/guide/src/elements/conditional_rendering.md +++ /dev/null @@ -1,184 +0,0 @@ -# Conditional Rendering - -Your components will often need to display different things depending on different conditions. With Dioxus, we can use Rust's normal control flow to conditionally hide, show, and modify the structure of our markup. - -In this chapter, you'll learn: -- How to return different Elements depending on a condition -- How to conditionally include an Element in your structure -- Common patterns like matching and bool mapping - -## Conditionally returning Elements - -In some components, you might want to render different markup given some condition. The typical example for conditional rendering is showing a "Log In" screen for users who aren't logged into your app. To break down this condition, we can consider two states: - -- Logged in: show the app -- Logged out: show the login screen - -Using the knowledge from the previous section on components, we'll start by making the app's props: - -```rust -#[derive(Props, PartialEq)] -struct AppProps { - logged_in: bool -} -``` - -Now that we have a "logged_in" flag accessible in our props, we can render two different screens: - -```rust -fn App(cx: Scope) -> Element { - if cx.props.logged_in { - cx.render(rsx!{ - DashboardScreen {} - }) - } else { - cx.render(rsx!{ - LoginScreen {} - }) - } -} -``` - -When the user is logged in, then this component will return the DashboardScreen. If they're not logged in, the component will render the LoginScreen. - -## Using match statements - -Rust provides us algebraic datatypes: enums that can contain values. Using the `match` keyword, we can execute different branches of code given a condition. - -For instance, we could run a function that returns a Result: - -```rust -fn App(cx: Scope)-> Element { - match get_name() { - Ok(name) => cx.render(rsx!( "Hello, {name}!" )), - Err(err) => cx.render(rsx!( "Sorry, I don't know your name, because an error occurred: {err}" )), - } -} -``` - -We can even match against values: -```rust -fn App(cx: Scope)-> Element { - match get_name() { - "jack" => cx.render(rsx!( "Hey Jack, how's Diane?" )), - "diane" => cx.render(rsx!( "Hey Diane, how's Jack?" )), - name => cx.render(rsx!( "Hello, {name}!" )), - } -} -``` - -Do note: the `rsx!` macro does not return an Element, but rather a wrapper struct for a `Closure` (an anonymous function). To turn our `rsx!` into an Element, we need to call `cx.render`. - -To make patterns like these less verbose, the `rsx!` macro accepts an optional first argument on which it will call `render`. Our previous component can be shortened with this alternative syntax: - -```rust -fn App(cx: Scope)-> Element { - match get_name() { - "jack" => rsx!(cx, "Hey Jack, how's Diane?" ), - "diane" => rsx!(cx, "Hey Diana, how's Jack?" ), - name => rsx!(cx, "Hello, {name}!" ), - } -} -``` - -Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to `cx.render`: - -```rust -fn App(cx: Scope)-> Element { - let greeting = match get_name() { - "jack" => rsx!("Hey Jack, how's Diane?" ), - "diane" => rsx!("Hey Diana, how's Jack?" ), - name => rsx!("Hello, {name}!" ), - }; - cx.render(greeting) -} -``` - -## Nesting RSX - -By looking at other examples, you might have noticed that it's possible to include `rsx!` calls inside other `rsx!` calls. We can include anything in our `rsx!` that implements `IntoVnodeList`: a marker trait for iterators that produce Elements. `rsx!` itself implements this trait, so we can include it directly: - -```rust -rsx!( - div { - rsx!( - "more rsx!" - ) - } -) -``` - -As you might expect, we can refactor this structure into two separate calls using variables: - -```rust -let title = rsx!( "more rsx!" ); - -rsx!( - div { - title - } -) -``` - -In the case of a log-in screen, we might want to display the same NavBar and Footer for both logged in and logged out users. We can model this entirely by assigning a `screen` variable to a different Element depending on a condition: - - -```rust -let screen = match logged_in { - true => rsx!(DashboardScreen {}), - false => rsx!(LoginScreen {}) -}; - -cx.render(rsx!{ - Navbar {} - screen, - Footer {} -}) -``` - -## Rendering Nothing - -Sometimes, you don't want your component to return anything at all. Under the hood, the `Element` type is just an alias for `Option`, so you can simply return `None`. - -This can be helpful in certain patterns where you need to perform some logical side-effects but don't want to render anything. - -```rust -fn demo(cx: Scope) -> Element { - None -} -``` - -## Boolean Mapping - -In the spirit of highly-functional apps, we suggest using the "boolean mapping" pattern when trying to conditionally hide/show an Element. - -By default, Rust lets you convert any `boolean` into an Option of any other type with [`then()`](https://doc.rust-lang.org/std/primitive.bool.html#method.then). We can use this in Components by mapping to some Element. - -```rust -let show_title = true; -rsx!( - div { - // Renders nothing by returning None when show_title is false - show_title.then(|| rsx!{ - "This is the title" - }) - } -) -``` - -We can use this pattern for many things, including options: -```rust -let user_name = Some("bob"); -rsx!( - div { - // Renders nothing if user_name is None - user_name.map(|name| rsx!("Hello {name}")) - } -) -``` - -## Moving Forward: - -In this chapter, we learned how to render different Elements from a Component depending on a condition. This is a very powerful building block to assemble complex user interfaces! - -In the next chapter, we'll cover how to renderer lists inside your `rsx!`. diff --git a/docs/guide/src/elements/index.md b/docs/guide/src/elements/index.md deleted file mode 100644 index c814945d1..000000000 --- a/docs/guide/src/elements/index.md +++ /dev/null @@ -1,73 +0,0 @@ -# Core Topics - -In this chapter, we'll cover some core topics about how Dioxus works and how to best leverage the features to build a beautiful, reactive app. - -At a very high level, Dioxus is simply a Rust framework for _declaring_ user interfaces and _reacting_ to changes. - -1) We declare what we want our user interface to look like given a state using Rust-based logic and control flow. -2) We declare how we want our state to change when the user triggers an event. - -## Declarative UI - -Dioxus is a *declarative* framework. This means that instead of manually writing calls to "create element" and "set element background to red," we simply *declare* what we want the element to look like and let Dioxus handle the differences. - -Let's pretend that we have a stoplight we need to control - it has a color state with red, yellow, and green as options. - - -Using an imperative approach, we would have to manually declare each element and then handlers for advancing the stoplight. - -```rust -let container = Container::new(); - -let green_light = Light::new().color("green").enabled(true); -let yellow_light = Light::new().color("yellow").enabled(false); -let red_light = Light::new().color("red").enabled(false); -container.push(green_light); -container.push(yellow_light); -container.push(red_light); - -container.set_onclick(move |_| { - if red_light.enabled() { - red_light.set_enabled(false); - green_light.set_enabled(true); - } else if yellow_light.enabled() { - yellow_light.set_enabled(false); - red_light.set_enabled(true); - } else if green_light.enabled() { - green_light.set_enabled(false); - yellow_light.set_enabled(true); - } -}); -``` - -As the UI grows in scale, our logic to keep each element in the proper state would grow exponentially. This can become very unwieldy and lead to out-of-sync UIs that harm user experience. - -Instead, with Dioxus, we *declare* what we want our UI to look like: - -```rust -let mut state = use_state(&cx, || "red"); - -cx.render(rsx!( - Container { - Light { color: "red", enabled: state == "red", } - Light { color: "yellow", enabled: state == "yellow", } - Light { color: "green", enabled: state == "green", } - - onclick: move |_| { - state.set(match *state { - "green" => "yellow", - "yellow" => "red", - "red" => "green", - }) - } - } -)) -``` - -Remember: this concept is not new! Many frameworks are declarative - with React being the most popular. Declarative frameworks tend to be much more enjoyable to work with than imperative frameworks. - -Here's some reading about declaring UI in React: - -- [Difference between declarative and imperative in React.js](https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js), a StackOverflow thread - -- [Declarative vs Imperative](https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44), a blog post by Myung Kim diff --git a/docs/guide/src/elements/lists.md b/docs/guide/src/elements/lists.md deleted file mode 100644 index 9f8a8072c..000000000 --- a/docs/guide/src/elements/lists.md +++ /dev/null @@ -1,145 +0,0 @@ -# Conditional Lists and Keys - -You will often want to display multiple similar components from a collection of data. - -In this chapter, you will learn: - -- How to use iterators in `rsx!` -- How to filter and transform data into a list of Elements -- How to create efficient lists with keys - -## Rendering data from lists - -If we wanted to build the Reddit app, then we need to implement a list of data that needs to be rendered: the list of posts. This list of posts is always changing, so we cannot just hardcode the lists into our app directly, like so: - -```rust -// we shouldn't ship our app with posts that don't update! -rsx!( - div { - Post { - title: "Post A", - votes: 120, - } - Post { - title: "Post B", - votes: 14, - } - Post { - title: "Post C", - votes: 999, - } - } -) -``` - -Instead, we need to transform the list of data into a list of Elements. - -For convenience, `rsx!` supports any type in curly braces that implements the `IntoVnodeList` trait. Conveniently, every iterator that returns something that can be rendered as an Element also implements `IntoVnodeList`. - -As a simple example, let's render a list of names. First, start with our input data: - -```rust -let names = ["jim", "bob", "jane", "doe"]; -``` - -Then, we create a new iterator by calling `iter` and then [`map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map). In our `map` function, we'll render our template. - -```rust -let name_list = names.iter().map(|name| rsx!( - li { "{name}" } -)); -``` - -We can include this list in the final Element: - -```rust -rsx!( - ul { - name_list - } -) -``` - -Rather than storing `name_list` in a temporary variable, we could also include the iterator inline: -```rust -rsx!( - ul { - names.iter().map(|name| rsx!( - li { "{name}" } - )) - } -) -``` - -The rendered HTML list is what you would expect: -```html -
    -
  • jim
  • -
  • bob
  • -
  • jane
  • -
  • doe
  • -
-``` - -## Filtering Iterators - -Rust's iterators are extremely powerful, especially when used for filtering tasks. When building user interfaces, you might want to display a list of items filtered by some arbitrary check. - -As a very simple example, let's set up a filter where we only list names that begin with the letter "j". - -Using the list from above, let's create a new iterator. Before we render the list with `map` as in the previous example, we'll [`filter`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter) the names to only allow those that start with "j". - -```rust -let name_list = names - .iter() - .filter(|name| name.starts_with('j')) - .map(|name| rsx!( li { "{name}" })); -``` - -Rust's Iterators are very versatile – check out [their documentation](https://doc.rust-lang.org/std/iter/trait.Iterator.html) for more things you can do with them! - -For keen Rustaceans: notice how we don't actually call `collect` on the name list. If we `collect`ed our filtered list into new Vec, we would need to make an allocation to store these new elements, which slows down rendering. Instead, we create an entirely new _lazy_ iterator which Dioxus will consume in the `render` call. The `render` method is extraordinarily efficient, so it's best practice to let it do most of the allocations for us. - -## Keeping list items in order with `key` - -The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: if your array items move (e.g. due to sorting), get inserted, or get deleted, Dioxus has no way of knowing what happened. This can cause Elements to be unnecessarily removed, changed and rebuilt when all that was needed was to change their position – this is inefficient. - -To solve this problem, each item in the list must be **uniquely identifiable**. You can achieve this by giving it a unique, fixed "key". In Dioxus, a key is a string that identifies an item among others in the list. - -```rust -rsx!( li { key: "a" } ) -``` - -Now, if an item has already been rendered once, Dioxus can use the key to match it up later to make the correct updates – and avoid unnecessary work. - -NB: the language from this section is strongly borrowed from [React's guide on keys](https://reactjs.org/docs/lists-and-keys.html). - -### Where to get your key - -Different sources of data provide different sources of keys: - -- _Data from a database_: If your data is coming from a database, you can use the database keys/IDs, which are unique by nature. -- _Locally generated data_: If your data is generated and persisted locally (e.g. notes in a note-taking app), keep track of keys along with your data. You can use an incrementing counter or a package like `uuid` to generate keys for new items – but make sure they stay the same for the item's lifetime. - -Remember: keys let Dioxus uniquely identify an item among its siblings. A well-chosen key provides more information than the position within the array. Even if the position changes due to reordering, the key lets Dioxus identify the item throughout its lifetime. - -### Rules of keys - -- Keys must be unique among siblings. However, it’s okay to use the same keys for Elements in different arrays. -- An item's key must not change – **don’t generate them on the fly** while rendering. Otherwise, Dioxus will be unable to keep track of which item is which, and we're back to square one. - -You might be tempted to use an item's index in the array as its key. In fact, that’s what Dioxus will use if you don’t specify a key at all. This is only acceptable if you can guarantee that the list is constant – i.e., no re-ordering, additions or deletions. In all other cases, do not use the index for the key – it will lead to the performance problems described above. - -Note that if you pass the key to a [custom component](../components/index.md) you've made, it won't receive the key as a prop. It’s only used as a hint by Dioxus itself. If your component needs an ID, you have to pass it as a separate prop: -```rust -Post { key: "{key}", id: "{key}" } -``` - -## Moving on - -In this section, we learned: -- How to render lists of data -- How to use iterator tools to filter and transform data -- How to use keys to render lists efficiently - -Moving forward, we'll learn more about attributes. diff --git a/docs/guide/src/elements/special_attributes.md b/docs/guide/src/elements/special_attributes.md deleted file mode 100644 index 7b4e1ba40..000000000 --- a/docs/guide/src/elements/special_attributes.md +++ /dev/null @@ -1,191 +0,0 @@ -# Special Attributes - -Dioxus tries its hardest to stay close to React, but there are some divergences and "special behavior" that you should review before moving on. - -In this section, we'll cover special attributes built into Dioxus: - -- `dangerous_inner_html` -- Boolean attributes -- `prevent_default` -- event handlers as string attributes -- `value`, `checked`, and `selected` - -## The HTML escape hatch: `dangerous_inner_html` - -One thing you might've missed from React is the ability to render raw HTML directly to the DOM. If you're working with pre-rendered assets, output from templates, or output from a JS library, then you might want to pass HTML directly instead of going through Dioxus. In these instances, reach for `dangerous_inner_html`. - -For example, shipping a markdown-to-Dioxus converter might significantly bloat your final application size. Instead, you'll want to pre-render your markdown to HTML and then include the HTML directly in your output. We use this approach for the [Dioxus homepage](https://dioxuslabs.com): - - -```rust -fn BlogPost(cx: Scope) -> Element { - let contents = include_str!("../post.html"); - cx.render(rsx!{ - div { - class: "markdown", - dangerous_inner_html: "{contents}", - } - }) -} -``` - -> Note! This attribute is called "dangerous_inner_html" because it is **dangerous** to pass it data you don't trust. If you're not careful, you can easily expose cross-site-scripting (XSS) attacks to your users. -> -> If you're handling untrusted input, make sure to sanitize your HTML before passing it into `dangerous_inner_html` – or just pass it to a Text Element to escape any HTML tags. - - -## Boolean Attributes - -Most attributes, when rendered, will be rendered exactly as the input you provided. However, some attributes are considered "boolean" attributes and just their presence determines whether or not they affect the output. For these attributes, a provided value of `"false"` will cause them to be removed from the target element. - -So this RSX: - -```rust -rsx!{ - div { - hidden: "false", - "hello" - } -} -``` -wouldn't actually render the `hidden` attribute: -```html -
hello
-``` - -Not all attributes work like this however. *Only the following attributes* have this behavior: - -- `allowfullscreen` -- `allowpaymentrequest` -- `async` -- `autofocus` -- `autoplay` -- `checked` -- `controls` -- `default` -- `defer` -- `disabled` -- `formnovalidate` -- `hidden` -- `ismap` -- `itemscope` -- `loop` -- `multiple` -- `muted` -- `nomodule` -- `novalidate` -- `open` -- `playsinline` -- `readonly` -- `required` -- `reversed` -- `selected` -- `truespeed` - -For any other attributes, a value of `"false"` will be sent directly to the DOM. - -## Stopping form input and navigation with `prevent_default` - -Currently, calling `prevent_default` on events in EventHandlers is not possible from Desktop/Mobile. Until this is supported, it's possible to prevent default using the `prevent_default` attribute. - -> Note: you cannot conditionally prevent default with this approach. This is a limitation until synchronous event handling is available across the Webview boundary - -To use `prevent_default`, simply attach the `prevent_default` attribute to a given element and set it to the name of the event handler you want to prevent default on. We can attach this attribute multiple times for multiple attributes. - -```rust -rsx!{ - input { - oninput: move |_| {}, - prevent_default: "oninput", - - onclick: move |_| {}, - prevent_default: "onclick", - } -} -``` - - -## Controlled inputs and `value`, `checked`, and `selected` - -In Dioxus, there is a distinction between controlled and uncontrolled inputs. Most inputs you'll use are controlled, meaning we both drive the `value` of the input and react to the `oninput`. - -Controlled components: -```rust -let value = use_state(&cx, || String::from("hello world")); - -rsx! { - input { - oninput: move |evt| value.set(evt.value.clone()), - value: "{value}", - } -} -``` - -With uncontrolled inputs, we won't actually drive the value from the component. This has its advantages when we don't want to re-render the component when the user inputs a value. We could either select the element directly - something Dioxus doesn't support across platforms - or we could handle `oninput` and modify a value without causing an update: - -```rust -let value = use_ref(&cx, || String::from("hello world")); - -rsx! { - input { - oninput: move |evt| *value.write_silent() = evt.value.clone(), - // no "value" is driven here – the input keeps track of its own value, and you can't change it - } -} -``` - -## Strings for handlers like `onclick` - -For element fields that take a handler like `onclick` or `oninput`, Dioxus will let you attach a closure. Alternatively, you can also pass a string using normal attribute syntax and assign this attribute on the DOM. - -This lets you use JavaScript (only if your renderer can execute JavaScript). - -```rust -rsx!{ - div { - // handle oninput with rust - oninput: move |_| {}, - - // or handle oninput with javascript - oninput: "alert('hello world')", - } -} - -``` - -## Wrapping up - -In this chapter, we learned: -- How to declare elements -- How to conditionally render parts of your UI -- How to render lists -- Which attributes are "special" - - diff --git a/docs/guide/src/elements/vnodes.md b/docs/guide/src/elements/vnodes.md deleted file mode 100644 index 5312306f0..000000000 --- a/docs/guide/src/elements/vnodes.md +++ /dev/null @@ -1,175 +0,0 @@ -# Declaring your first UI with Elements - -Every user interface you've ever used is just a symphony of tiny widgets working together to abstract over larger complex functions. In Dioxus, we call these tiny widgets "Elements." Using Components, you can easily compose Elements into larger groups to form even larger structures: Apps. - -In this chapter, we'll cover: -- Declaring our first Element -- Composing Elements together -- Element properties - -## Declaring our first Element - -Because Dioxus is mostly used with HTML/CSS renderers, the default Element "collection" is HTML. Provided the `html` feature is not disabled, we can declare Elements using the `rsx!` macro: - -```rust -rsx!( - div {} -) -``` -As you might expect, we can render this call using Dioxus-SSR to produce valid HTML: - -```rust -dioxus::ssr::render_lazy(rsx!( - div {} -)) -``` - -Produces: -```html -
-``` - -We can construct any valid HTML tag with the `tag {}` pattern and expect the resulting HTML structure to resemble our declaration. - -## Composing Elements - -Of course, we need more complex structures to make our apps actually useful! Just like HTML, the `rsx!` macro lets us nest Elements inside of each other. - -```rust -#use dioxus::prelude::*; -rsx!( - div { - h1 {} - h2 {} - p {} - } -) -``` -As you might expect, the generated HTML for this structure would look like: -```html -
-

-

-

-
-``` - -With the default configuration, any Element defined within the `dioxus-html` crate can be declared in this way. To create your own new elements, see the `Custom Elements` Advanced Guide. - -## Text Elements - -Dioxus also supports a special type of Element: Text. Text Elements do not accept children, just a string literal denoted by double quotes. - -```rust -rsx! ( - "hello world" -) -``` - -Text Elements can be composed within other Elements: -```rust -rsx! ( - div { - h1 { "hello world" } - p { "Some body content" } - } -) -``` - -Text can also be formatted with any value that implements `Display`. We use the same syntax as Rust [format strings](https://www.rustnote.com/blog/format_strings.html) – which will already be familiar for Python and JavaScript users: - -```rust -let name = "Bob"; -rsx! ( "hello {name}" ) -``` - -Unfortunately, you cannot yet drop in arbitrary expressions directly into the string literal with Rust. In the cases where we need to compute a complex value, we'll want to use `format_args!` directly. Due to specifics of the `rsx!` macro (which we'll cover later), our call to `format_args` must be contained within square braces. - -```rust -rsx!( [format_args!("Hello {}", if enabled { "Jack" } else { "Bob" } )] ) -``` - -Alternatively, `&str` can be included directly, though it must also be inside square braces: - -```rust -rsx!( "Hello ", [if enabled { "Jack" } else { "Bob" }] ) -``` - -This is different from React's way of generating arbitrary markup but fits within idiomatic Rust. - -Typically, with Dioxus, you'll just want to compute your substrings outside of the `rsx!` call and leverage the f-string formatting: - -```rust -let name = if enabled { "Jack" } else { "Bob" }; -rsx! ( "hello {name}" ) -``` - -## Attributes - -Every Element in your user interface will have some sort of properties that the renderer will use when drawing to the screen. These might inform the renderer if the component should be hidden, what its background color should be, or to give it a specific name or ID. - -To do this, we use the familiar struct-style syntax that Rust provides: - -```rust -rsx!( - div { - hidden: "true", - background_color: "blue", - class: "card color-{mycolor}" - } -) -``` - -Each field is defined as a method on the element in the `dioxus-html` crate. This prevents you from misspelling a field name and lets us provide inline documentation. When you need to use a field not defined as a method, you have two options: - -1) file an issue if the attribute _should_ be enabled -2) add a custom attribute on-the-fly - -To use custom attributes, simply put the attribute name in quotes: - -```rust -rsx!( - div { - "customAttr": "important data here" - } -) -``` - -> Note: the name of the custom attribute must match exactly what you want the renderer to output. All attributes defined as methods in `dioxus-html` follow the snake_case naming convention. However, they internally translate their snake_case convention to HTML's camelCase convention. When using custom attributes, make sure the name of the attribute **exactly** matches what the renderer is expecting. - -All element attributes must occur *before* child elements. The `rsx!` macro will throw an error if your child elements come before any of your attributes. If you don't see the error, try editing your Rust-Analyzer IDE setting to ignore macro-errors. This is a temporary workaround because Rust-Analyzer currently throws *two* errors instead of just the one we care about. - -```rust -// settings.json -{ - "rust-analyzer.diagnostics.disabled": [ - "macro-error" - ], -} -``` - -## Listeners - -Listeners are a special type of Attribute that only accept functions. Listeners let us attach functionality to our Elements by running a provided closure whenever the specified Listener is triggered. - -We'll cover listeners in more depth in the Adding Interactivity chapter, but for now, just know that every listener starts with "on" and accepts closures. - -```rust -rsx!( - div { - onclick: move |_| log::debug!("div clicked!"), - } -) -``` - -## Moving On - -This chapter just scratches the surface on how Elements can be defined. - -We learned: -- Elements are the basic building blocks of User Interfaces -- Elements can contain other elements -- Elements can either be a named container or text -- Some Elements have properties that the renderer can use to draw the UI to the screen - -Next, we'll compose Elements together using Rust-based logic. diff --git a/docs/guide/src/final.md b/docs/guide/src/final.md deleted file mode 100644 index 41449edc7..000000000 --- a/docs/guide/src/final.md +++ /dev/null @@ -1,49 +0,0 @@ -# Congrats! - -Congrats! You've made it through the `learning Dioxus` book. Throughout this tutorial, you've learned a ton: - -- How to build User Interfaces with Elements -- How to compose Element groups together as Components -- How to handle user input with event listeners -- How to manage local and global state -- How to work with async using tasks, coroutines, and suspense -- How to build custom hooks and handlers - -With any luck, you followed through the "Putting it All Together" mini guide and have your very own dog search engine app! - -# Next Steps and Advanced Topics - -Continuing on your journey with Dioxus, you can try a number of things: - -- Build a simple TUI app -- Publish your search engine app -- Deploy a WASM app to GitHub -- Design a custom hook -- Contribute to the ecosystem! - -There are a number of advanced topics we glossed over: - -- The underlying NodeFactory API -- Static elements and templates -- Anti-patterns -- Bundling/distribution -- Working with wasm apps - -# Contributing to the ecosystem - -Dioxus is still quite young and could use your help! - -The core team is actively working on: - -- Declarative window management (via Tauri) for Desktop apps -- Portals for Dioxus Core -- Mobile support -- Integration with 3D renderers -- Better async story (suspense, error handling) -- Global state management -- Web development server -- LiveView -- Broader platform support (iOS/Android/TV/embedded) - -If there's something specifically interesting to you, don't be afraid to jump in! - diff --git a/docs/guide/src/getting_started/desktop.md b/docs/guide/src/getting_started/desktop.md new file mode 100644 index 000000000..8fec4aa00 --- /dev/null +++ b/docs/guide/src/getting_started/desktop.md @@ -0,0 +1,45 @@ +# Desktop Application + +Build a standalone native desktop app that looks and feels the same across operating systems. + +Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory. + +Examples: +- [File explorer](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer) +- [WiFi scanner](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner) + +[![File ExplorerExample](https://raw.githubusercontent.com/DioxusLabs/example-projects/master/file-explorer/image.png)](https://github.com/DioxusLabs/example-projects/tree/master/file-explorer) + +## Support + +The desktop is a powerful target for Dioxus, but is currently limited in capability when compared to the Web platform. Currently, desktop apps are rendered with the platform's WebView library, but your Rust code is running natively on a native thread. This means that browser APIs are *not* available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs *are* accessible, so streaming, WebSockets, filesystem, etc are all viable APIs. In the future, we plan to move to a custom webrenderer-based DOM renderer with WGPU integrations. + +Dioxus Desktop is built off [Tauri](https://tauri.app/). Right now there aren't any Dioxus abstractions over keyboard shortcuts, menubar, handling, etc, so you'll want to leverage Tauri – mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly. + +## Creating a Project + +Create a new crate: + +```shell +cargo new --bin demo +cd demo +``` + +Add Dioxus with the `desktop` feature (this will edit `Cargo.toml`): + +```shell +cargo add dioxus --features desktop +``` + +> If your system does not provide the `libappindicator3` library, like Debian/bullseye, you can enable the replacement `ayatana` with an additional flag: +> +>```shell +># On Debian/bullseye use: +>cargo add dioxus --features desktop --features ayatana +>``` + +Edit your `main.rs`: + +```rust +{{#include ../../examples/hello_world_desktop.rs:all}} +``` diff --git a/docs/reference/src/guide/hot_reloading.md b/docs/guide/src/getting_started/hot_reload.md similarity index 97% rename from docs/reference/src/guide/hot_reloading.md rename to docs/guide/src/getting_started/hot_reload.md index 8560c62f9..e5d7b0327 100644 --- a/docs/reference/src/guide/hot_reloading.md +++ b/docs/guide/src/getting_started/hot_reload.md @@ -1,4 +1,5 @@ -# Hot Reloading +# Setting Up Hot Reload + 1. Hot reloading allows much faster iteration times inside of rsx calls by interperting them and streaming the edits. 2. It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program. 3. Currently the cli only implements hot reloading for the web renderer. diff --git a/docs/guide/src/getting_started/index.md b/docs/guide/src/getting_started/index.md new file mode 100644 index 000000000..6b4937453 --- /dev/null +++ b/docs/guide/src/getting_started/index.md @@ -0,0 +1,74 @@ +# Getting Started + +This section will help you set up your Dioxus project! + +## Prerequisites + +### An Editor + +Dioxus integrates very well with the [Rust-Analyzer LSP plugin](https://rust-analyzer.github.io) which will provide appropriate syntax highlighting, code navigation, folding, and more. + +### Rust + +Head over to [https://rust-lang.org](http://rust-lang.org) and install the Rust compiler. + +We strongly recommend going through the [official Rust book](https://doc.rust-lang.org/book/ch01-00-getting-started.html) _completely_. However, our hope is that a Dioxus app can serve as a great first Rust project. With Dioxus, you'll learn about: + +- Error handling +- Structs, Functions, Enums +- Closures +- Macros + +We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need deep knowledge on async, lifetimes, or smart pointers until you really start building complex Dioxus apps. + +### Platform-Specific Dependencies + +#### Windows + +Windows Desktop apps depend on WebView2 – a library which should be installed in all modern Windows distributions. If you have Edge installed, then Dioxus will work fine. If you *don't* have Webview2, [then you can install it through Microsoft](https://developer.microsoft.com/en-us/microsoft-edge/webview2/). MS provides 3 options: + +1. A tiny "evergreen" *bootstrapper* which will fetch an installer from Microsoft's CDN +2. A tiny *installer* which will fetch Webview2 from Microsoft's CDN +3. A statically linked version of Webview2 in your final binary for offline users + +For development purposes, use Option 1. + +#### Linux + +Webview Linux apps require WebkitGtk. When distributing, this can be part of your dependency tree in your `.rpm` or `.deb`. However, it's very likely that your users will already have WebkitGtk. + +```bash +sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev +``` + +When using Debian/bullseye `libappindicator3-dev` is no longer available but replaced by `libayatana-appindicator3-dev`. + +```bash +# on Debian/bullseye use: +sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev +``` + +If you run into issues, make sure you have all the basics installed, as outlined in the [Tauri docs](https://tauri.studio/v1/guides/getting-started/prerequisites#setting-up-linux). + + +#### MacOS + +Currently – everything for macOS is built right in! However, you might run into an issue if you're using nightly Rust due to some permissions issues in our Tao dependency (which have been resolved but not published). + +### Suggested Cargo Extensions + +If you want to keep your traditional `npm install XXX` workflow for adding packages, you might want to install `cargo-edit` and a few other fun `cargo` extensions: + +- [cargo-expand](https://github.com/dtolnay/cargo-expand) for expanding macro calls +- [cargo tree](https://doc.rust-lang.org/cargo/commands/cargo-tree.html) – an integrated cargo command that lets you inspect your dependency tree + + +## Setup Guides + +Dioxus supports multiple platforms. Depending on what you want, the setup is a bit different. + +- [Web](web.md): running in the browser using WASM +- [Server Side Rendering](ssr.md): render Dioxus HTML as text +- [Desktop](desktop.md): a standalone app using webview +- [Mobile](mobile.md) +- [Terminal UI](tui.md): terminal text-based graphic interface \ No newline at end of file diff --git a/docs/reference/src/platforms/mobile.md b/docs/guide/src/getting_started/mobile.md similarity index 59% rename from docs/reference/src/platforms/mobile.md rename to docs/guide/src/getting_started/mobile.md index 193487885..0dffdac30 100644 --- a/docs/reference/src/platforms/mobile.md +++ b/docs/guide/src/getting_started/mobile.md @@ -1,11 +1,15 @@ -# Getting started: mobile +# Mobile App +Build a mobile app with Dioxus! -Dioxus is unique in that it actually supports mobile. However, support is very young and you might need to dip down into some of the primitives until better support is ready. +Example: [Todo App](https://github.com/DioxusLabs/example-projects/blob/master/ios_demo) -Currently, only iOS is supported through us, however you *can* add android support by following the same instructions below by using the `android` guide in `cargo-mobile`. +## Support +Mobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with the platform's WebView, meaning that animations, transparency, and native widgets are not currently achievable. -Also, Dioxus Desktop and Dioxus Mobile share the same codebase, and dioxus-mobile currently just re-exports dioxus-desktop. +In addition, iOS is the only supported Mobile Platform. It is possible to get Dioxus running on Android and rendered with WebView, but the Rust windowing library that Dioxus uses – tao – does not currently support Android. + +Mobile support is currently best suited for CRUD-style apps, ideally for internal teams who need to develop quickly but don't care much about animations or native widgets. ## Getting Set up @@ -14,14 +18,13 @@ Getting set up with mobile can be quite challenging. The tooling here isn't grea We're going to be using `cargo-mobile` to build for mobile. First, install it: ```shell -$ cargo install --git https://github.com/BrainiumLLC/cargo-mobile +cargo install --git https://github.com/BrainiumLLC/cargo-mobile ``` - And then initialize your app for the right platform. Use the `winit` template for now. Right now, there's no "Dioxus" template in cargo-mobile. ```shell -$ cargo mobile init +cargo mobile init ``` We're going to completely clear out the `dependencies` it generates for us, swapping out `winit` with `dioxus-mobile`. @@ -55,7 +58,6 @@ simple_logger = "*" Edit your `lib.rs`: ```rust -// main.rs use dioxus::prelude::*; fn main() { @@ -70,9 +72,3 @@ fn app(cx: Scope) -> Element { }) } ``` - -To configure the webview, menubar, and other important desktop-specific features, checkout out some of the launch configuration in the [API reference](https://docs.rs/dioxus-mobile/). - -## Future Steps - -Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't! diff --git a/docs/reference/src/platforms/ssr.md b/docs/guide/src/getting_started/ssr.md similarity index 68% rename from docs/reference/src/platforms/ssr.md rename to docs/guide/src/getting_started/ssr.md index 09e32ba43..0b6ca0701 100644 --- a/docs/reference/src/platforms/ssr.md +++ b/docs/guide/src/getting_started/ssr.md @@ -1,6 +1,17 @@ -# Getting Started: Server-Side-Rendering +# Server-Side Rendering + +The Dioxus VirtualDom can be rendered server-side. + +[Example: Dioxus DocSite](https://github.com/dioxusLabs/docsite) + +## Multithreaded Support + +The Dioxus VirtualDom, sadly, is not currently `Send`. Internally, we use quite a bit of interior mutability which is not thread-safe. This means you can't easily use Dioxus with most web frameworks like Tide, Rocket, Axum, etc. + +To solve this, you'll want to spawn a VirtualDom on its own thread and communicate with it via channels. + +When working with web frameworks that require `Send`, it is possible to render a VirtualDom immediately to a String – but you cannot hold the VirtualDom across an await point. For retained-state SSR (essentially LiveView), you'll need to create a pool of VirtualDoms. -The Dioxus VirtualDom can be rendered to a string by traversing the Element Tree. This is implemented in the `ssr` crate where your Dioxus app can be directly rendered to HTML to be served by a web server. ## Setup @@ -21,21 +32,21 @@ However, for this guide, we're going to show how to use Dioxus SSR with `Axum`. Make sure you have Rust and Cargo installed, and then create a new project: ```shell -$ cargo new --bin demo -$ cd app +cargo new --bin demo +cd app ``` Add Dioxus with the `ssr` feature: ```shell -$ cargo add dioxus --features ssr +cargo add dioxus --features ssr ``` Next, add all the Axum dependencies. This will be different if you're using a different Web Framework ``` -$ cargo add tokio --features full -$ cargo add axum +cargo add tokio --features full +cargo add axum ``` Your dependencies should look roughly like this: @@ -47,7 +58,7 @@ dioxus = { version = "*", features = ["ssr"] } tokio = { version = "1.15.0", features = ["full"] } ``` -Now, setup your Axum app to respond on an endpoint. +Now, set up your Axum app to respond on an endpoint. ```rust use axum::{response::Html, routing::get, Router}; @@ -96,7 +107,3 @@ async fn app_endpoint() -> Html { And that's it! > You might notice that you cannot hold the VirtualDom across an await point. Dioxus is currently not ThreadSafe, so it _must_ remain on the thread it started. We are working on loosening this requirement. - -## Future Steps - -Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't! diff --git a/docs/guide/src/getting_started/tui.md b/docs/guide/src/getting_started/tui.md new file mode 100644 index 000000000..106dd3587 --- /dev/null +++ b/docs/guide/src/getting_started/tui.md @@ -0,0 +1,46 @@ +# Terminal UI + +You can build a text-based interface that will run in the terminal using Dioxus. + +![Hello World screenshot](https://github.com/DioxusLabs/rink/raw/master/examples/example.png) + +> Note: this book was written with HTML-based platforms in mind. You might be able to follow along with TUI, but you'll have to adapt a bit. + +## Support + +TUI support is currently quite experimental. Even the project name will change. But, if you're willing to venture into the realm of the unknown, this guide will get you started. + +## Getting Set up + + +Start by making a new package and adding our TUI feature. + +```shell +cargo new --bin demo +cd demo +cargo add dioxus --features tui +``` + +Then, edit your `main.rs` with the basic template. + +```rust +{{#include ../../examples/hello_world_tui.rs}} +``` + +To run our app: + +```shell +cargo run +``` + +Press "ctrl-c" to close the app. To switch from "ctrl-c" to just "q" to quit you can launch the app with a configuration to disable the default quit and use the root TuiContext to quit on your own. + +```rust +{{#include ../../examples/hello_world_tui_no_ctrl_c.rs}} +``` + +## Notes + +- Our TUI package uses flexbox for layout +- 1px is one character lineheight. Your regular CSS px does not translate. +- If your app panics, your terminal is wrecked. This will be fixed eventually. diff --git a/docs/guide/src/getting_started/web.md b/docs/guide/src/getting_started/web.md new file mode 100644 index 000000000..886322a9c --- /dev/null +++ b/docs/guide/src/getting_started/web.md @@ -0,0 +1,72 @@ +# Web + +Build single-page applications that run in the browser with Dioxus. To run on the Web, your app must be compiled to WebAssembly and depend on the `dioxus` crate with the `web` feature enabled. + +A build of Dioxus for the web will be roughly equivalent to the size of a React build (70kb vs 65kb), but will load significantly faster due to [WebAssembly's StreamingCompile](https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/) option. + +Examples: +- [TodoMVC](https://github.com/DioxusLabs/example-projects/tree/master/todomvc) +- [ECommerce](https://github.com/DioxusLabs/example-projects/tree/master/ecommerce-site) + +[![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/example-projects/blob/master/todomvc) + +> Note: Because of the limitations of Wasm, not every crate will work with your web apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc). + +## Support + +The Web is the best-supported target platform for Dioxus. + +## Tooling + +To develop your Dioxus app for the web, you'll need a tool to build and serve your assets. We recommend using [trunk](https://trunkrs.dev) which includes a build system, Wasm optimization, a dev server, and support for SASS/CSS: + +```shell +cargo install trunk +``` + +Make sure the `wasm32-unknown-unknown` target is installed: +```shell +rustup target add wasm32-unknown-unknown +``` + +## Creating a Project + +Create a new crate: + +```shell +cargo new --bin demo +cd demo +``` + +Add Dioxus as a dependency with the `web` feature: + +```bash +cargo add dioxus --features web +``` + +Add an `index.html` for Trunk to use. Make sure your "mount point" element has an ID of "main": + +```html + + + + + + + +
+ + +``` + +Edit your `main.rs`: +```rust +{{#include ../../examples/hello_world_web.rs}} +``` + + +And to serve our app: + +```bash +trunk serve +``` diff --git a/docs/guide/src/hello_world.md b/docs/guide/src/hello_world.md deleted file mode 100644 index d01122959..000000000 --- a/docs/guide/src/hello_world.md +++ /dev/null @@ -1,152 +0,0 @@ -# "Hello, World" desktop app - -Let's put together a simple "hello world" desktop application to get acquainted with Dioxus. - -In this chapter, we'll cover: - -- Starting a new Dioxus project with Cargo -- Adding Dioxus as a dependency -- Launching our first component as the app root - -### A new project with Cargo - -First, let's start a new project. Rust has the concept of executables and libraries. Executables have a `main.rs` and libraries have `lib.rs`. A project may have both. Our `hello world` will be an executable - we expect our app to launch when we run it! Cargo provides this for us: - -```shell -$ cargo new --bin hello-dioxus -``` - -Now, we can `cd` into our project and poke around: - -```shell -$ cd hello-dioxus -$ tree -. -├── Cargo.toml -├── .git -├── .gitignore -└── src - └── main.rs -``` - -We are greeted with a pre-initialized git repository, our code folder (`src`) and our project file (`Cargo.toml`). - -Our `src` folder holds our code. Our `main.rs` file holds our `fn main` which will be executed when our app is run. - -```shell -$ more src/main.rs -``` - -```rust -fn main() { - println!("Hello, world!"); -} -``` - -Right now, whenever our app is launched, "Hello world" will be echoed to the terminal. - -```shell -$ cargo run - Compiling hello-dioxus v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.41s - Running `target/debug/hello-dioxus` -Hello, world! -``` - -Our `Cargo.toml` file holds our dependencies and project flags. - -```shell -$ cat Cargo.toml -``` - -```toml -[package] -name = "hello-dioxus" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] - -``` - -### Adding Dioxus as a dependency - -To use the Dioxus library, we'll want to add the most recent version of `Dioxus` to our crate. If you have `cargo edit` installed, simply call: - -```shell -$ cargo add dioxus --features desktop -``` - -It's very important to add `dioxus` with the `desktop` feature for this example. The `dioxus` crate is a batteries-include crate that combines a bunch of utility crates together, ensuring compatibility of the most important parts of the ecosystem. Under the hood, the `dioxus` crate configures various renderers, hooks, debug tooling, and more. The `desktop` feature ensures the we only depend on the smallest set of required crates to compile and render. - -If you system does not provide the `libappindicator3` library, like Debian/bullseye, you can enable the replacement `ayatana` with an additional flag: - -```shell -$ # On Debian/bullseye use: -$ cargo add dioxus --features desktop --features ayatana -``` - -If you plan to develop extensions for the `Dioxus` ecosystem, please use the `dioxus` crate with the `core` feature to limit the amount of dependencies your project brings in. - -### Our first app - -Now, let's edit our `main.rs` file: - -```rust -use dioxus::prelude::*; - - -fn main() { - dioxus::desktop::launch(app); -} - -fn app(cx: Scope) -> Element { - cx.render(rsx! ( - div { "Hello, world!" } - )) -} -``` - -At this point, you could call `cargo run` and be greeted with a simple `Hello, World!` screen: - -![hello world](images/helloworld.png) - -### Dissecting our example - -The `use` statement at the top of our app imports everything from the `prelude` module. `use`-ing the prelude imports the right traits, types, and macros needed for working with Dioxus. - -```rust -use dioxus::prelude::*; -``` - -This initialization code launches a Tokio runtime on a helper thread where your code will run. Then, the WebView renderer will be launched on the main thread. Due to platform requirements, the main thread is blocked by your app's event loop. - -```rust -fn main() { - dioxus::desktop::launch(app); -} -``` - -Finally, our app. Every component in Dioxus is a function that takes in `Context` and `Props` and returns an `Element`. - -```rust -fn app(cx: Scope) -> Element { - cx.render(rsx! { - div { "Hello, world!" } - }) -} -``` - -### What is this `Scope` object? - -Coming from React, the `Scope` object might be confusing. In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. - -In Dioxus, you are given an explicit `Scope` object to control how the component renders and stores data. The `Scope` object provides a handful of useful APIs for features like suspense, rendering, and more. - -For now, just know that `Scope` lets you store state with hooks and render elements with `cx.render`. - -## Moving on - -Congrats! You've built your first desktop application with Dioxus. Next, we're going to learn about the basics of building interactive user interfaces. diff --git a/docs/guide/src/images/01-setup-helloworld.png b/docs/guide/src/images/01-setup-helloworld.png deleted file mode 100644 index 9da58b932..000000000 Binary files a/docs/guide/src/images/01-setup-helloworld.png and /dev/null differ diff --git a/docs/guide/src/images/helloworld.png b/docs/guide/src/images/helloworld.png deleted file mode 100644 index c10ebdf46..000000000 Binary files a/docs/guide/src/images/helloworld.png and /dev/null differ diff --git a/docs/guide/src/images/reddit.png b/docs/guide/src/images/reddit.png deleted file mode 100644 index 2277db264..000000000 Binary files a/docs/guide/src/images/reddit.png and /dev/null differ diff --git a/docs/guide/src/images/reddit_post.png b/docs/guide/src/images/reddit_post.png deleted file mode 100644 index 6eccf39b2..000000000 Binary files a/docs/guide/src/images/reddit_post.png and /dev/null differ diff --git a/docs/guide/src/images/reddit_post_components.png b/docs/guide/src/images/reddit_post_components.png deleted file mode 100644 index f9548a0fa..000000000 Binary files a/docs/guide/src/images/reddit_post_components.png and /dev/null differ diff --git a/docs/guide/src/index.md b/docs/guide/src/index.md new file mode 100644 index 000000000..761f1e7d8 --- /dev/null +++ b/docs/guide/src/index.md @@ -0,0 +1,51 @@ +# Introduction + +![dioxuslogo](./images/dioxuslogo_full.png) + +Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust. This guide will help you get started with writing Dioxus apps for the Web, Desktop, Mobile, and more. + +```rust +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!" } + )) +} +``` + +Dioxus is heavily inspired by React. If you know React, getting started with Dioxus will be a breeze. + +> This guide assumes you already know some [Rust](https://www.rust-lang.org/)! If not, we recommend reading [*the book*](https://doc.rust-lang.org/book/ch01-00-getting-started.html) to learn Rust first. + +## Features + +- Desktop apps running natively (no Electron!) in less than 10 lines of code. +- Incredibly ergonomic and powerful state management. +- Comprehensive inline documentation – hover and guides for all HTML elements, listeners, and events. +- Extremely memory efficient – 0 global allocations for steady-state components. +- Multi-channel asynchronous scheduler for first-class async support. +- And more! Read the [full release post](https://dioxuslabs.com/blog/introducing-dioxus/. + +### Multiplatform + +Dioxus is a *portable* toolkit, meaning the Core implementation can run anywhere with no platform-dependent linking. Unlike many other Rust frontend toolkits, Dioxus is not intrinsically linked to WebSys. In fact, every element and event listener can be swapped out at compile time. By default, Dioxus ships with the `html` feature enabled, but this can be disabled depending on your target renderer. + +Right now, we have several 1st-party renderers: +- WebSys (for WASM): Great support +- Tao/Tokio (for Desktop apps): Good support +- Tao/Tokio (for Mobile apps): Poor support +- SSR (for generating static markup) +- TUI/Rink (for terminal-based apps): Experimental + +## Stability + +Dioxus has not reached a stable release yet. + +Web: Since the web is a fairly mature platform, we expect there to be very little API churn for web-based features. + +Desktop: APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart. + +SSR: We don't expect the SSR API to change drastically in the future. \ No newline at end of file diff --git a/docs/guide/src/interactivity/custom_hooks.md b/docs/guide/src/interactivity/custom_hooks.md new file mode 100644 index 000000000..4b1e0a29f --- /dev/null +++ b/docs/guide/src/interactivity/custom_hooks.md @@ -0,0 +1,26 @@ +# Custom Hooks + +Hooks are a great way to encapsulate business logic. If none of the existing hooks work for your problem, you can write your own. + +## Composing Hooks + +To avoid repetition, you can encapsulate business logic based on existing hooks to create a new hook. + +For example, if many components need to access an `AppSettings` struct, you can create a "shortcut" hook: + +```rust +{{#include ../../examples/hooks_composed.rs:wrap_context}} +``` + +## Custom Hook Logic + +You can use [`cx.use_hook`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Scope.html#method.use_hook) to build your own hooks. In fact, this is what all the standard hooks are built on! + +`use_hook` accepts a single closure for initializing the hook. It will be only run the first time the component is rendered. The return value of that closure will be used as the value of the hook – Dioxus will take it, and store it for as long as the component is alive. On every render (not just the first one!), you will get a reference to this value. + +> Note: You can implement [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html) for your hook value – it will be dropped then the component is unmounted (no longer in the UI) + +Inside the initialization closure, you will typically make calls to other `cx` methods. For example: + +- The `use_state` hook tracks state in the hook value, and uses [`cx.schedule_update`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Scope.html#method.schedule_update) to make Dioxus re-render the component whenever it changes. +- The `use_context` hook calls [`cx.consume_context`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Scope.html#method.consume_context) (which would be expensive to call on every render) to get some context from the scope \ No newline at end of file diff --git a/docs/guide/src/interactivity/dynamic_rendering.md b/docs/guide/src/interactivity/dynamic_rendering.md new file mode 100644 index 000000000..07e6a27ed --- /dev/null +++ b/docs/guide/src/interactivity/dynamic_rendering.md @@ -0,0 +1,73 @@ +# Dynamic Rendering + +Sometimes you want to render different things depending on the state/props. With Dioxus, just describe what you want to see using Rust control flow – the framework will take care of making the necessary changes on the fly if the state or props change! + +## Conditional Rendering + +To render different elements based on a condition, you could use an `if-else` statement: + +```rust +{{#include ../../examples/conditional_rendering.rs:if_else}} +``` + +> You could also use `match` statements, or any Rust function to conditionally render different things. + + +### Inspecting `Element` props + +Since `Element` is a `Option`, components accepting `Element` as a prop can actually inspect its contents, and render different things based on that. Example: + +```rust +{{#include ../../examples/component_children_inspect.rs:Clickable}} +``` + +You can't mutate the `Element`, but if you need a modified version of it, you can construct a new one based on its attributes/children/etc. + + +## Rendering Nothing + +To render nothing, you can return `None` from a component. This is useful if you want to conditionally hide something: + +```rust +{{#include ../../examples/conditional_rendering.rs:conditional_none}} +``` + +This works because the `Element` type is just an alias for `Option` + +> Again, you may use a different method to conditionally return `None`. For example the boolean's [`then()`](https://doc.rust-lang.org/std/primitive.bool.html#method.then) function could be used. + +## Rendering Lists + +Often, you'll want to render a collection of components. For example, you might want to render a list of all comments on a post. + +For this, Dioxus accepts iterators that produce `Element`s. So we need to: + +- Get an iterator over all of our items (e.g., if you have a `Vec` of comments, iterate over it with `iter()`) +- `.map` the iterator to convert each item into a rendered `Element` using `cx.render(rsx!(...))` + - Add a unique `key` attribute to each iterator item +- Include this iterator in the final RSX + +Example: suppose you have a list of comments you want to render. Then, you can render them like this: + +```rust +{{#include ../../examples/rendering_lists.rs:render_list}} +``` + +### The `key` Attribute + +Every time you re-render your list, Dioxus needs to keep track of which item went where, because the order of items in a list might change – items might be added, removed or swapped. Despite that, Dioxus needs to: + +- Keep track of component state +- Efficiently figure out what updates need to be made to the UI + +For example, suppose the `CommentComponent` had some state – e.g. a field where the user typed in a reply. If the order of comments suddenly changes, Dioxus needs to correctly associate that state with the same comment – otherwise, the user will end up replying to a different comment! + +To help Dioxus keep track of list items, we need to associate each item with a unique key. In the example above, we dynamically generated the unique key. In real applications, it's more likely that the key will come from e.g. a database ID. It doesn't really matter where you get the key from, as long as it meets the requirements + +- Keys must be unique in a list +- The same item should always get associated with the same key +- Keys should be relatively small (i.e. converting the entire Comment structure to a String would be a pretty bad key) so they can be compared efficiently + +You might be tempted to use an item's index in the list as its key. In fact, that’s what Dioxus will use if you don’t specify a key at all. This is only acceptable if you can guarantee that the list is constant – i.e., no re-ordering, additions or deletions. + +> Note that if you pass the key to a component you've made, it won't receive the key as a prop. It’s only used as a hint by Dioxus itself. If your component needs an ID, you have to pass it as a separate prop. diff --git a/docs/guide/src/interactivity/event_handlers.md b/docs/guide/src/interactivity/event_handlers.md index 89139adb4..8d4ce5515 100644 --- a/docs/guide/src/interactivity/event_handlers.md +++ b/docs/guide/src/interactivity/event_handlers.md @@ -1,118 +1,66 @@ -# Event handlers +# Event Handlers -To make our boring UIs less static and more interesting, we want to add the ability to interact with user input. To do this, we need to add some event handlers. +Events are interesting things that happen, usually related to something the user has done. For example, some events happen when the user clicks, scrolls, moves the mouse, or types a character. To respond to these actions and make the UI interactive, we need to handle those events. +Events are associated with elements. For example, we usually don't care about all the clicks that happen within an app, only those on a particular button. To handle events that happen on an element, we must attach the desired event handler to it. -## The most basic events: clicks +Event handlers are similar to regular attributes, but their name usually starts with `on`- and they accept closures as values. The closure will be called whenever the event is triggered, and will be passed that event. -If you've poked around in the Dioxus examples at all, you've definitely noticed the support for buttons and clicks. To add some basic action when a button is clicked, we need to define a button and then attach an "onclick" handler to it. +For example, to handle clicks on an element, we can specify an `onclick` handler: ```rust -fn app(cx: Scope) -> Element { - cx.render(rsx!{ - button { - onclick: move |evt| println!("I've been clicked!"), - "click me!" - } - }) -} +{{#include ../../examples/event_click.rs:rsx}} ``` -If you're using the builder pattern, it's pretty simple too. `onclick` is a method for any builder with HTML elements. - -```rust -fn app(cx: Scope) -> Element { - button(&cx) - .onclick(move |evt| println!("I've been clicked!")) - .text("click me!") - .build() -} -``` - -The event handler is different in Dioxus than other libraries. Event handlers in Dioxus may borrow any data that has a lifetime that matches the component's scope. This means we can save a value with `use_hook` and then use it in our handler. - -```rust -fn app(cx: Scope) -> Element { - let val = cx.use_hook(|_| 10); - - button(&cx) - .onclick(move |evt| println!("Current number {val}")) - .text("click me!") - .build() -} -``` - - ## The `Event` object -When the listener is fired, Dioxus will pass in any related data through the `event` parameter. This holds helpful state relevant to the event. For instance, on forms, Dioxus will fill in the "values" field. +Event handlers receive an [`UiEvent`](https://docs.rs/dioxus-core/latest/dioxus_core/struct.UiEvent.html) object containing information about the event. Different types of events contain different types of data. For example, mouse-related events contain [`MouseData`](https://docs.rs/dioxus/latest/dioxus/events/struct.MouseData.html), which tells you things like where the mouse was clicked and what mouse buttons were used. -```rust -// the FormEvent is roughly -struct FormEvent { - value: String, - values: HashMap -} +In the example above, this event data was logged to the terminal: -fn app(cx: Scope) -> Element { - cx.render(rsx!{ - form { - onsubmit: move |evt| { - println!("Values of form are {evt.values:#?}"); - } - input { id: "password", name: "password" } - input { id: "username", name: "username" } - } - }) -} +``` +Clicked! Event: UiEvent { data: MouseData { coordinates: Coordinates { screen: (468.0, 109.0), client: (73.0, 25.0), element: (63.0, 15.0), page: (73.0, 25.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } } +Clicked! Event: UiEvent { data: MouseData { coordinates: Coordinates { screen: (468.0, 109.0), client: (73.0, 25.0), element: (63.0, 15.0), page: (73.0, 25.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } } ``` -## Stopping propagation +To learn what the different event types provide, read the [events module docs](https://docs.rs/dioxus/latest/dioxus/events/index.html). -With a complex enough UI, you might realize that listeners can actually be nested. +### Stopping propagation + +When you have e.g. a `button` inside a `div`, any click on the `button` is also a click on the `div`. For this reason, Dioxus propagates the click event: first, it is triggered on the target element, then on parent elements. If you want to prevent this behavior, you can call `cancel_bubble()` on the event: ```rust -div { - onclick: move |evt| {}, - "outer", - div { - onclick: move |evt| {}, - "inner" - } -} -``` - -In this particular layout, a click on the inner div is transitively also a click on the outer div. If we didn't want the outer div to be triggered every time we trigger the inner div, then we'd want to call `cancel_bubble()`. - -This will prevent any listeners above the current listener from being triggered. - -```rust -div { - onclick: move |evt| {}, - "outer", - div { - onclick: move |evt| { - // now, outer won't be triggered - evt.cancel_bubble(); - }, - "inner" - } -} +{{#include ../../examples/event_click.rs:rsx}} ``` ## Prevent Default -With HTML-based renderers, the browser will automatically perform some action. For text inputs, this would be entering the provided key. For forms, this might involve navigating the page. +Some events have a default behavior. For keyboard events, this might be entering the typed character. For mouse events, this might be selecting some text. -In some instances, you don't want this default behavior. In these cases, instead of handling the event directly, you'd want to prevent any default handlers. - -Normally, in React or JavaScript, you'd call "preventDefault" on the event in the callback. Dioxus does *not* currently support this behavior. Instead, you need to add an attribute to the element generating the event. +In some instances, might want to avoid this default behavior. For this, you can add the `prevent_default` attribute with the name of the handler whose default behavior you want to stop. This attribute is special: you can attach it multiple times for multiple attributes: ```rust -form { - prevent_default: "onclick", - onclick: move |_|{ - // handle the event without navigating the page. - } -} +{{#include ../../examples/event_prevent_default.rs:prevent_default}} ``` + +Any event handlers will still be called. + +> Normally, in React or JavaScript, you'd call "preventDefault" on the event in the callback. Dioxus does *not* currently support this behavior. Note: this means you cannot conditionally prevent default behavior. + +## Handler Props + +Sometimes, you might want to make a component that accepts an event handler. A simple example would be a `FancyButton` component, which accepts an `on_click` handler: + +```rust +{{#include ../../examples/event_handler_prop.rs:component_with_handler}} +``` + +Then, you can use it like any other handler: + +```rust +{{#include ../../examples/event_handler_prop.rs:usage}} +``` + +> Note: just like any other attribute, you can name the handlers anything you want! Though they must start with `on`, for the prop to be automatically turned into an `EventHandler` at the call site. +> +> You can also put custom data in the event, rather than e.g. `MouseData` diff --git a/docs/guide/src/interactivity/hooks.md b/docs/guide/src/interactivity/hooks.md index 4f298a143..c437bdeea 100644 --- a/docs/guide/src/interactivity/hooks.md +++ b/docs/guide/src/interactivity/hooks.md @@ -1,253 +1,83 @@ -# Hooks and Internal State +# Hooks and Component State -In the [Adding Interactivity](./interactivity.md) section, we briefly covered the concept of hooks and state stored internal to components. +So far our components, being Rust functions, had no state – they were always rendering the same thing. However, in a UI component, it is often useful to have stateful functionality to build user interactions. For example, you might want to track whether the user has openend a drop-down, and render different things accordingly. -In this section, we'll dive a bit deeper into hooks, exploring both the theory and mechanics. +For stateful logic, you can use hooks. Hooks are Rust functions that take a reference to `ScopeState` (in a component, you can pass `&cx`), and provide you with functionality and state. +## `use_state` Hook +[`use_state`](https://docs.rs/dioxus/latest/dioxus/hooks/fn.use_state.html) is one of the simplest hooks. -## Theory of Hooks +- You provide a closure that determines the initial value +- `use_state` gives you the current value, and a way to update it by setting it to something else +- When the value updates, `use_state` makes the component re-render, and provides you with the new value -Over the past several decades, computer scientists and engineers have long sought the "right way" of designing user interfaces. With each new programming language, novel features are unlocked that change the paradigm in which user interfaces are coded. - -Generally, a number of patterns have emerged, each with their own strengths and tradeoffs. - -Broadly, there are two types of GUI structures: - -- Immediate GUIs: re-render the entire screen on every update -- Retained GUIs: only re-render the portion of the screen that changed - -Typically, immediate-mode GUIs are simpler to write but can slow down as more features, like styling, are added. - -Many GUIs today are written in *Retained mode* - your code changes the data of the user interface but the renderer is responsible for actually drawing to the screen. In these cases, our GUI's state sticks around as the UI is rendered. To help accommodate retained mode GUIs, like the web browser, Dioxus provides a mechanism to keep state around. - -> Note: Even though hooks are accessible, you should still prefer one-way data flow and encapsulation. Your UI code should be as predictable as possible. Dioxus is plenty fast, even for the largest apps. - -## Mechanics of Hooks -In order to have state stick around between renders, Dioxus provides the `hook` through the `use_hook` API. This gives us a mutable reference to data returned from the initialization function. +For example, you might have seen the counter example, in which state (a number) is tracked using the `use_state` hook: ```rust -fn example(cx: Scope) -> Element { - let name: &mut String = cx.use_hook(|| "John Doe".to_string()); - - // -} +{{#include ../../examples/hooks_counter.rs:component}} ``` +![Screenshot: counter app](./images/counter.png) -We can even modify this value directly from an event handler: +Every time the component's state changes, it re-renders, and the component function is called, so you can describe what you want the new UI to look like. You don't have to worry about "changing" anything – just describe what you want in terms of the state, and Dioxus will take care of the rest! + +> `use_state` returns your value wrapped in a smart pointer of type [`UseState`](https://docs.rs/dioxus/latest/dioxus/hooks/struct.UseState.html). This is why you can both read the value and update it, even within a handler. + +You can use multiple hooks in the same component if you want: ```rust -fn example(cx: Scope) -> Element { - let name: &mut String = cx.use_hook(|| "John Doe".to_string()); - - cx.render(rsx!( - button { - onclick: move |_| name.push_str(".."), - } - )) -} +{{#include ../../examples/hooks_counter_two_state.rs:component}} ``` +![Screenshot: app with two counters](./images/counter_two_state.png) -Mechanically, each call to `use_hook` provides us with `&mut T` for a new value. +## Rules of Hooks + +The above example might seem a bit magic, since Rust functions are typically not associated with state. Dioxus allows hooks to maintain state across renders through a reference to `ScopeState`, which is why you must pass `&cx` to them. + +But how can Dioxus differentiate between multiple hooks in the same component? As you saw in the second example, both `use_state` functions were called with the same parameters, so how come they can return different things when the counters are different? ```rust -fn example(cx: Scope) -> Element { - let name: &mut String = cx.use_hook(|| "John Doe".to_string()); - let age: &mut u32 = cx.use_hook(|| 10); - let friends: &mut Vec = cx.use_hook(|| vec!["Jane Doe".to_string()]); - - // -} +{{#include ../../examples/hooks_counter_two_state.rs:use_state_calls}} ``` -Internally, Dioxus is creating a list of hook values with each call to `use_hook` advancing the index of the list to return the next value. +This is only possible because the two hooks are always called in the same order, so Dioxus knows which is which. So the order you call hooks matters, which is why you must follow certain rules when using hooks: -Our internal HookList would look something like: +1. Hooks may be only used in components or other hooks (we'll get to that later) +2. On every call to the component function + 1. The same hooks must be called + 2. In the same order +3. Hooks name's must start with `use_` so you don't accidentally confuse them with regular functions + +These rules mean that there are certain things you can't do with hooks: + +### No Hooks in Conditionals +```rust +{{#include ../../examples/hooks_bad.rs:conditional}} +``` + +### No Hooks in Closures +```rust +{{#include ../../examples/hooks_bad.rs:closure}} +``` + +### No Hooks in Loops +```rust +{{#include ../../examples/hooks_bad.rs:loop}} +``` + +## `use_ref` Hook + +`use_state` is great for tracking simple values. However, you may notice in the [`UseState` API](https://docs.rs/dioxus/latest/dioxus/hooks/struct.UseState.html) that the only way to modify its value is to replace it with something else (e.g., by calling `set`, or through one of the `+=`, `-=` operators). This works well when it is cheap to construct a value (such as any primitive). But what if you want to maintain more complex data in the components state? + +For example, suppose we want to maintain a `Vec` of values. If we stored it with `use_state`, the only way to add a new value to the list would be to create a new `Vec` with the additional value, and put it in the state. This is expensive! We want to modify the existing `Vec` instead. + +Thankfully, there is another hook for that, `use_ref`! It is similar to `use_state`, but it lets you get a mutable reference to the contained data. + +Here's a simple example that keeps a list of events in a `use_ref`. We can acquire write access to the state with `.write()`, and then just `.push` a new value to the state: ```rust -[ - Hook, - Hook, - Hook, -] +{{#include ../../examples/hooks_use_ref.rs:component}} ``` -This is why hooks called out of order will fail - if we try to downcast a `Hook` to `Hook`, Dioxus has no choice but to panic. We do provide a `try_use_hook` but you should never need that in practice. +> The return values of `use_state` and `use_ref`, (`UseState` and `UseRef`, respectively) are in some ways similar to [`Cell`](https://doc.rust-lang.org/std/cell/) and [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) – they provide interior mutability. However, these Dioxus wrappers also ensure that the component gets re-rendered whenever you change the state. -This pattern might seem strange at first, but it can be a significant upgrade over structs as blobs of state, which tend to be difficult to use in [Rust given the ownership system](https://rust-lang.github.io/rfcs/2229-capture-disjoint-fields.html). - - -## Rules of hooks - -Hooks are sensitive to how they are used. To use hooks, you must abide by the -"rules of hooks" ([borrowed from react](https://reactjs.org/docs/hooks-rules.html)): - -- Functions with "use_" should not be called in callbacks -- Functions with "use_" should not be called out of order -- Functions with "use_" should not be called in loops or conditionals - -Examples of "no-nos" include: - -### ❌ Nested uses - -```rust -// ❌ don't call use_hook or any `use_` function *inside* use_hook! -cx.use_hook(|_| { - let name = cx.use_hook(|_| "ads"); -}) - -// ✅ instead, move the first hook above -let name = cx.use_hook(|_| "ads"); -cx.use_hook(|_| { - // do something with name here -}) -``` - -### ❌ Uses in conditionals -```rust -// ❌ don't call use_ in conditionals! -if do_thing { - let name = use_state(&cx, || 0); -} - -// ✅ instead, *always* call use_state but leave your logic -let name = use_state(&cx, || 0); -if do_thing { - // do thing with name here -} -``` - -### ❌ Uses in loops - - -```rust -// ❌ Do not use hooks in loops! -let mut nodes = vec![]; - -for name in names { - let age = use_state(&cx, |_| 0); - nodes.push(cx.render(rsx!{ - div { "{age}" } - })) -} - -// ✅ Instead, consider refactoring your usecase into components -#[inline_props] -fn Child(cx: Scope, name: String) -> Element { - let age = use_state(&cx, |_| 0); - cx.render(rsx!{ div { "{age}" } }) -} - -// ✅ Or, use a hashmap with use_ref -let ages = use_ref(&cx, || HashMap::new()); - -names.iter().map(|name| { - let age = ages.get(name).unwrap(); - cx.render(rsx!{ div { "{age}" } }) -}) -``` - -## Building new Hooks - -However, most hooks you'll interact with *don't* return an `&mut T` since this is not very useful in a real-world situation. - -Consider when we try to pass our `&mut String` into two different handlers: - -```rust -fn example(cx: Scope) -> Element { - let name: &mut String = cx.use_hook(|| "John Doe".to_string()); - - cx.render(rsx!( - button { onclick: move |_| name.push_str("yes"), } - button { onclick: move |_| name.push_str("no"), } - )) -} -``` - -Rust will not allow this to compile! We cannot `Copy` unique mutable references - they are, by definition, unique. However, we *can* reborrow our `&mut T` as an `&T` which are non-unique references and share those between handlers: - -```rust -fn example(cx: Scope) -> Element { - let name: &String = &*cx.use_hook(|| "John Doe".to_string()); - - cx.render(rsx!( - button { onclick: move |_| log::info!("{}", name), } - button { onclick: move |_| log::info!("{}", name), } - )) -} -``` - -So, for any custom hook we want to design, we need to enable mutation through [interior mutability](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html) - IE move to runtime [borrow checking](https://doc.rust-lang.org/1.8.0/book/references-and-borrowing.html). We might incur a tiny runtime cost for each time we grab a new value from the hook, but this cost is extremely minimal. - -This example uses the `Cell` type to let us replace the value through interior mutability. `Cell` has practically zero overhead, but is slightly more limited that its `RefCell` cousin. - -```rust -fn example(cx: Scope) -> Element { - let name: &Cell<&'static str> = cx.use_hook(|| Cell::new("John Doe")); - - cx.render(rsx!( - button { onclick: move |_| name.set("John"), } - button { onclick: move |_| name.set("Jane"), } - )) -} -``` - -## Driving state updates through hooks - -Hooks like `use_state` and `use_ref` wrap this runtime borrow checking in a type that *does* implement `Copy`. Additionally, they also mark the component as "dirty" whenever a new value has been set. This way, whenever `use_state` has a new value `set`, the component knows to update. - -```rust -fn example(cx: Scope) -> Element { - let name = use_state(&cx, || "Jack"); - - cx.render(rsx!( - "Hello, {name}" - button { onclick: move |_| name.set("John"), } - button { onclick: move |_| name.set("Jane"), } - )) -} -``` - -Internally, our `set` function looks something like this: - -```rust -impl<'a, T> UseState<'a, T> { - fn set(&self, new: T) { - // Replace the value in the cell - self.value.set(new); - - // Mark our component as dirty - self.cx.needs_update(); - } -} -``` - -Most hooks we provide implement `Deref` on their values since they are essentially smart pointers. To access the underlying value, you'll often need to use the deref operator: - -```rust -fn example(cx: Scope) -> Element { - let name = use_state(&cx, || "Jack"); - - match *name { - "Jack" => {} - "Jill" => {} - _ => {} - } - - // .. -} - -``` - - -## Hooks provided by the `Dioxus-Hooks` package - -By default, we bundle a handful of hooks in the Dioxus-Hooks package. Feel free to click on each hook to view its definition and associated documentation. - -- [use_state](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_state.html) - store state with ergonomic updates -- [use_ref](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_ref.html) - store non-clone state with a refcell -- [use_future](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_future.html) - store a future to be polled after initialization -- [use_coroutine](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_coroutine.html) - store a future that can be stopped/started/communicated with -- [use_context_provider](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html) - expose state to descendent components -- [use_context](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html) - consume state provided by `use_provide_context` -- [use_suspense](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_suspense.html) diff --git a/docs/guide/src/interactivity/images/counter.png b/docs/guide/src/interactivity/images/counter.png new file mode 100644 index 000000000..dd89cadef Binary files /dev/null and b/docs/guide/src/interactivity/images/counter.png differ diff --git a/docs/guide/src/interactivity/images/counter_two_state.png b/docs/guide/src/interactivity/images/counter_two_state.png new file mode 100644 index 000000000..4f77942a2 Binary files /dev/null and b/docs/guide/src/interactivity/images/counter_two_state.png differ diff --git a/docs/guide/src/interactivity/images/meme_editor_screenshot.png b/docs/guide/src/interactivity/images/meme_editor_screenshot.png new file mode 100644 index 000000000..3f7c807fb Binary files /dev/null and b/docs/guide/src/interactivity/images/meme_editor_screenshot.png differ diff --git a/docs/guide/src/interactivity/importanthooks.md b/docs/guide/src/interactivity/importanthooks.md deleted file mode 100644 index dcffb1c86..000000000 --- a/docs/guide/src/interactivity/importanthooks.md +++ /dev/null @@ -1,10 +0,0 @@ -# `use_state` and `use_ref` - -Most components you will write in Dioxus will need to store state somehow. For local state, we provide two very convenient hooks: - -- [use_state](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_state.html) -- [use_ref](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_ref.html) - -Both of these hooks are extremely powerful and flexible, so we've dedicated this section to understanding them properly. - -> These two hooks are not the only way to store state. You can always build your own hooks! diff --git a/docs/guide/src/interactivity/index.md b/docs/guide/src/interactivity/index.md index 670ca5a40..5429402b0 100644 --- a/docs/guide/src/interactivity/index.md +++ b/docs/guide/src/interactivity/index.md @@ -1,215 +1,3 @@ -# Adding Interactivity +# Interactivity -So far, we've learned how to describe the structure and properties of our user interfaces. Unfortunately, they're static and quite uninteresting. In this chapter, we're going to learn how to add interactivity through events, state, and tasks. - -## Primer on interactivity - -Before we get too deep into the mechanics of interactivity, we should first understand how Dioxus exactly chooses to handle user interaction and updates to your app. - -### What is state? - -Every app you'll ever build has some sort of information that needs to be rendered to the screen. Dioxus is responsible for translating your desired user interface to what is rendered to the screen. *You* are responsible for providing the content. - -The dynamic data in your user interface is called `State`. - -When you first launch your app with `dioxus::web::launch_with_props` you'll be providing the initial state. You need to declare the initial state *before* starting the app. - -```rust -fn main() { - // declare our initial state - let props = PostProps { - id: Uuid::new_v4(), - score: 10, - comment_count: 0, - post_time: std::time::Instant::now(), - url: String::from("dioxuslabs.com"), - title: String::from("Hello, world"), - original_poster: String::from("dioxus") - }; - - // start the render loop - dioxus::desktop::launch_with_props(Post, props); -} -``` - -When Dioxus renders your app, it will pass an immutable reference to `PostProps` into your `Post` component. Here, you can pass the state down into children. - -```rust -fn App(cx: Scope) -> Element { - cx.render(rsx!{ - Title { title: &cx.props.title } - Score { score: &cx.props.score } - // etc - }) -} -``` - -State in Dioxus follows a pattern called "one-way data-flow." As your components create new components as their children, your app's structure will eventually grow into a tree where state gets passed down from the root component into "leaves" of the tree. - -You've probably seen the tree of UI components represented using a directed acyclic graph: - -![image](../images/component_tree.png) - -With Dioxus, your state will always flow down from parent components into child components. - -### How do I change my app's state? - -We've talked about the data flow of state, but we haven't yet talked about how to change that state dynamically. Dioxus provides a variety of ways to change the state of your app while it's running. - -For starters, we _could_ use the `update_root_props` method on the VirtualDom to provide an entirely new root state of your App. However, for most applications, you probably don't want to regenerate your entire app just to update some text or a flag. - -Instead, you'll want to store state internally in your components and let *that* flow down the tree. To store state in your components, you'll use something called a `hook`. Hooks are special functions that reserve a slot of state in your component's memory and provide some functionality to update that state. - -The most common hook you'll use for storing state is `use_state`. `use_state` provides a slot for some data that allows you to read and update the value without accidentally mutating it. - -```rust -fn App(cx: Scope)-> Element { - let post = use_state(&cx, || { - PostData { - id: Uuid::new_v4(), - score: 10, - comment_count: 0, - post_time: std::time::Instant::now(), - url: String::from("dioxuslabs.com"), - title: String::from("Hello, world"), - original_poster: String::from("dioxus") - } - }); - - cx.render(rsx!{ - Title { title: &post.title } - Score { score: &post.score } - // etc - }) -} -``` - -Whenever we have a new post that we want to render, we can call `set_post` and provide a new value: - -```rust -set_post(PostData { - id: Uuid::new_v4(), - score: 20, - comment_count: 0, - post_time: std::time::Instant::now(), - url: String::from("google.com"), - title: String::from("goodbye, world"), - original_poster: String::from("google") -}) -``` - -We'll dive deeper into how exactly these hooks work later. - -### When do I update my state? - -There are a few different approaches to choosing when to update your state. You can update your state in response to user-triggered events or asynchronously in some background task. - -### Updating state in listeners - -When responding to user-triggered events, we'll want to "listen" for an event on some element in our component. - -For example, let's say we provide a button to generate a new post. Whenever the user clicks the button, they get a new post. To achieve this functionality, we'll want to attach a function to the `onclick` method of `button`. Whenever the button is clicked, our function will run, and we'll get new Post data to work with. - -```rust -fn App(cx: Scope)-> Element { - let post = use_state(&cx, || PostData::new()); - - cx.render(rsx!{ - button { - onclick: move |_| post.set(PostData::random()) - "Generate a random post" - } - Post { props: &post } - }) -} -``` - -We'll dive much deeper into event listeners later. - -### Updating state asynchronously - -We can also update our state outside of event listeners with `futures` and `coroutines`. - -- `Futures` are Rust's version of promises that can execute asynchronous work by an efficient polling system. We can submit new futures to Dioxus either through `push_future` which returns a `TaskId` or with `spawn`. -- `Coroutines` are asynchronous blocks of our component that have the ability to cleanly interact with values, hooks, and other data in the component. - -Since coroutines and Futures stick around between renders, the data in them must be valid for the `'static` lifetime. We must explicitly declare which values our task will rely on to avoid the `stale props` problem common in React. - -We can use tasks in our components to build a tiny stopwatch that ticks every second. - -> Note: The `use_future` hook will start our coroutine immediately. The `use_coroutine` hook provides more flexibility over starting and stopping futures on the fly. - -```rust -fn App(cx: Scope)-> Element { - let elapsed = use_state(&cx, || 0); - - use_future(&cx, (), |()| { - to_owned![elapsed]; // explicitly capture this hook for use in async - async move { - loop { - gloo_timers::future::TimeoutFuture::new(1_000).await; - elapsed.modify(|i| i + 1) - } - } - }); - - rsx!(cx, div { "Current stopwatch time: {elapsed}" }) -} -``` - -Using asynchronous code can be difficult! This is just scratching the surface of what's possible. We have an entire chapter on using async properly in your Dioxus Apps. - -### How do I tell Dioxus that my state changed? - -Whenever you inform Dioxus that the component needs to be updated, it will "render" your component again, storing the previous and current Elements in memory. Dioxus will automatically figure out the differences between the old and the new and generate a list of edits that the renderer needs to apply to change what's on the screen. This process is called "diffing": - -![Diffing](../images/diffing.png) - -In React, the specifics of when a component gets re-rendered is somewhat blurry. With Dioxus, any component can mark itself as "dirty" through a method on `Context`: `needs_update`. In addition, any component can mark any _other_ component as dirty provided it knows the other component's ID with `needs_update_any`. - -With these building blocks, we can craft new hooks similar to `use_state` that let us easily tell Dioxus that new information is ready to be sent to the screen. - -### How do I update my state efficiently? - -In general, Dioxus should be plenty fast for most use cases. However, there are some rules you should consider following to ensure your apps are quick. - -1) **Don't call set_state _while rendering_**. This will cause Dioxus to unnecessarily re-check the component for updates or enter an infinite loop. -2) **Break your state apart into smaller sections.** Hooks are explicitly designed to "unshackle" your state from the typical model-view-controller paradigm, making it easy to reuse useful bits of code with a single function. -3) **Move local state down**. Dioxus will need to re-check child components of your app if the root component is constantly being updated. You'll get best results if rapidly-changing state does not cause major re-renders. - - - -## The `Scope` object - -Though very similar to React, Dioxus is different in a few ways. Most notably, React components will not have a `Scope` parameter in the component declaration. - -Have you ever wondered how the `useState()` call works in React without a `this` object to actually store the state? - -```javascript -// in React: -function Component(props) { - // This state persists between component renders, but where does it live? - let [state, set_state] = useState(10); -} -``` - -React uses global variables to store this information. However, global mutable variables must be carefully managed and are broadly discouraged in Rust programs. Because Dioxus needs to work with the rules of Rust it uses the `Scope` rather than a global state object to maintain some internal bookkeeping. - -That's what the `Scope` object is: a place for the Component to store state, manage listeners, and allocate elements. Advanced users of Dioxus will want to learn how to properly leverage the `Scope` object to build robust and performant extensions for Dioxus. - -```rust -fn Post(cx: Scope) -> Element { - cx.render(rsx!("hello")) -} -``` - -## Moving On - -This overview was a lot of information - but it doesn't tell you everything! - -In the next sections we'll go over: -- `use_state` in depth -- `use_ref` and other hooks -- Handling user input +So far, we've learned how to describe the structure and properties of our user interfaces. However, most interfaces need to be interactive in order to be useful. In this chapter, we describe how to make a Dioxus app that responds to the user. \ No newline at end of file diff --git a/docs/guide/src/interactivity/lifecycles.md b/docs/guide/src/interactivity/lifecycles.md deleted file mode 100644 index 1d6ec4f15..000000000 --- a/docs/guide/src/interactivity/lifecycles.md +++ /dev/null @@ -1,11 +0,0 @@ -# Effects - -In theory, your UI code should be side-effect free. Whenever a component renders, all of its state should be prepared ahead of time. In reality, we often need to perform some sort of side effect. Possible effects include: - -- Logging some data -- Pre-fetching some data -- Attaching code to native elements -- Cleaning up - -This section is organized under interactivity because effects can be important to add things like transitions, videos, and other important media. - diff --git a/docs/guide/src/state/router.md b/docs/guide/src/interactivity/router.md similarity index 87% rename from docs/guide/src/state/router.md rename to docs/guide/src/interactivity/router.md index 7682ed24a..e1d170ec3 100644 --- a/docs/guide/src/state/router.md +++ b/docs/guide/src/interactivity/router.md @@ -2,7 +2,7 @@ In many of your apps, you'll want to have different "scenes". For a webpage, these scenes might be the different webpages with their own content. -You could write your own scene management solution - quite simply too. However, to save you the effort, Dioxus supports a first-party solution for scene management called Dioxus Router. +You could write your own scene management solution – quite simply too. However, to save you the effort, Dioxus supports a first-party solution for scene management called Dioxus Router. ## What is it? @@ -13,7 +13,7 @@ For an app like the Dioxus landing page (https://dioxuslabs.com), we want to hav - Blog - Example showcase -Each of these scenes is independent - we don't want to render both the homepage and blog at the same time. +Each of these scenes is independent – we don't want to render both the homepage and blog at the same time. This is where the router crates come in handy. To make sure we're using the router, simply add the `"router"` feature to your dioxus dependency. diff --git a/docs/guide/src/interactivity/sharing_state.md b/docs/guide/src/interactivity/sharing_state.md new file mode 100644 index 000000000..1a0be57ec --- /dev/null +++ b/docs/guide/src/interactivity/sharing_state.md @@ -0,0 +1,66 @@ +# Sharing State + +Often, multiple components need to access the same state. Depending on your needs, there are several ways to implement this. + +## Lifting State + +One approach to share state between components is to "lift" it up to the nearest common ancestor. This means putting the `use_state` hook in a parent component, and passing the needed values down as props. + +For example, suppose we want to build a meme editor. We want to have an input to edit the meme caption, but also a preview of the meme with the caption. Logically, the meme and the input are 2 separate components, but they need access to the same state (the current caption). + +> Of course, in this simple example, we could write everything in one component – but it is better to split everything out in smaller components to make the code more reusable and easier to maintain (this is even more important for larger, complex apps). + +We start with a `Meme` component, responsible for rendering a meme with a given caption: +```rust +{{#include ../../examples/meme_editor.rs:meme_component}} +``` + +> Note that the `Meme` component is unaware where the caption is coming from – it could be stored in `use_state`, `use_ref`, or a constant. This ensures that it is very reusable – the same component can be used for a meme gallery without any changes! + +We also create a caption editor, completely decoupled from the meme. The caption editor must not store the caption itself – otherwise, how will we provide it to the `Meme` component? Instead, it should accept the current caption as a prop, as well as an event handler to delegate input events to: + +```rust +{{#include ../../examples/meme_editor.rs:caption_editor}} +``` + +Finally, a third component will render the other two as children. It will be responsible for keeping the state and passing down the relevant props. +```rust +{{#include ../../examples/meme_editor.rs:meme_editor}} +``` +![Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: "me waiting for a language feature"](./images/meme_editor_screenshot.png) + +## Using Context + +Sometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient. + +Suppose now that we want to implement a dark mode toggle for our app. To achieve this, we will make every component select styling depending on whether dark mode is enabled or not. + +> Note: we're choosing this approach for the sake of an example. There are better ways to implement dark mode (e.g. using CSS variables). Let's pretend CSS variables don't exist – welcome to 2013! + +Now, we could write another `use_state` in the top component, and pass `is_dark_mode` down to every component through props. But think about what will happen as the app grows in complexity – almost every component that renders any CSS is going to need to know if dark mode is enabled or not – so they'll all need the same dark mode prop. And every parent component will need to pass it down to them. Imagine how messy and verbose that would get, especially if we had components several levels deep! + +Dioxus offers a better solution than this "prop drilling" – providing context. The [`use_context_provider`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html) hook is similar to `use_ref`, but it makes it available through [`use_context`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html) for all children components. + +First, we have to create a struct for our dark mode configuration: + +```rust +{{#include ../../examples/meme_editor_dark_mode.rs:DarkMode_struct}} +``` + +Now, in a top-level component (like `App`), we can provide the `DarkMode` context to all children components: +```rust +{{#include ../../examples/meme_editor_dark_mode.rs:context_provider}} +``` + +As a result, any child component of `App` (direct or not), can access the `DarkMode` context. +```rust +{{#include ../../examples/meme_editor_dark_mode.rs:use_context}} +``` + +> `use_context` returns `Option>` here. If the context has been provided, the value is `Some(UseSharedState)`, which you can call `.read` or `.write` on, similarly to `UseRef`. Otherwise, the value is `None`. + +For example, here's how we would implement the dark mode toggle, which both reads the context (to determine what color it should render) and writes to it (to toggle dark mode): +```rust +{{#include ../../examples/meme_editor_dark_mode.rs:toggle}} +``` + diff --git a/docs/guide/src/interactivity/user_input.md b/docs/guide/src/interactivity/user_input.md index e878afbbe..4ccf4a422 100644 --- a/docs/guide/src/interactivity/user_input.md +++ b/docs/guide/src/interactivity/user_input.md @@ -1,82 +1,31 @@ -# User Input and Controlled Components +# User Input -Handling user input is one of the most common things your app will do, but it *can* be tricky. - -The reactive paradigm and one-way-data-flow models were all derived to solve problems that can crop up around user input handling. This section will teach you about two effective patterns for handling user input: controlled and uncontrolled inputs. +Interfaces often need to provide a way to input data: e.g. text, numbers, checkboxes, etc. In Dioxus, there are two ways you can work with user input. ## Controlled Inputs -The most common approach to handling input from elements is through "controlled" inputs. With this pattern, we drive the value of the input from our state, while simultaneously updating our state from new values. - -This is the most common form of input handling and is widely used because it prevents the UI from being out of sync with your local state. +With controlled inputs, you are directly in charge of the state of the input. This gives you a lot of flexibility, and makes it easy to keep things in sync. For example, this is how you would create a controlled text input: ```rust -let name = use_state(&cx, || "bob".to_string()); - -cx.render(rsx!{ - input { - value: "{name}", - oninput: move |evt| name.set(evt.value.clone()), - } -}) +{{#include ../../examples/input_controlled.rs:component}} ``` -Why not just "bind" like in other frameworks? - -In a handful of cases, you don't want the inputted value to match what's rendered to the screen. Let's say we want to implement an input that converts the input to uppercase when the input matches a certain value. With binding, we're forced to share the same input and state value. By explicitly handling the oninput case, we're given the opportunity to set a *new* value. - - -```rust -let name = use_state(&cx, || "bob".to_string()); - -cx.render(rsx!{ - input { - value: "{name}", - oninput: move |evt| { - if evt.value == "tim" { - name.set("TIM"); - } - }, - } -}) -``` - - -## Binding - ->! Note - binding is currently not implemented in Dioxus. This section represents a future in-progress feature. - -Because the above pattern is so common, we have an additional attribute called "bind" which is essentially a shorthand for our code above. - -Bind just connects an oninput to a `UseState` and is implemented through the signal system. - -```rust -let name = use_state(&cx, || "bob".to_string()); - -cx.render(rsx!{ - input { bind: name } -}) -``` +Notice the flexibility – you can: +- Also display the same contents in another element, and they will be in sync +- Transform the input every time it is modified (e.g. to make sure it is upper case) +- Validate the input every time it changes +- Have custom logic happening when the input changes (e.g. network request for autocompletion) +- Programmatically change the value (e.g. a "randomize" button that fills the input with nonsense) ## Uncontrolled Inputs -When working with large sets of inputs, you might be quickly tired of creating `use_state` for each value. Additionally, the pattern of one `use_state` per interaction might deteriorate when you need to have a flexible number of inputs. In these cases, we use "uncontrolled" inputs. Here, we don't drive the value of the input from the `use_state`, choosing to leave it in an "uncontrolled" state. +As an alternative to controlled inputs, you can simply let the platform keep track of the input values. If we don't tell a HTML input what content it should have, it be editable anyway (this is built into the webview). This approach can be more performant, but less flexible. For example, it's harder to keep the input in sync with another element. -This approach can be more performant and more flexible, but more prone to UI inconsistencies than its controlled counterpart. - -To use the "uncontrolled" pattern, we simply omit setting the value of the input. Instead, we can react to the change directly on the input itself, or from a form element higher up in the tree. - -For this example, we don't attach any `use_state` handles into the labels. Instead, we simply attach an `oninput` handler to the form element. This will run each time any of the child inputs change, allowing us to perform tasks like form validation. +Since you don't necessarily have the current value of the uncontrolled input in state, you can access it either by listening to `oninput` events (similarly to controlled components), or, if the input is part of a form, you can access the form data in the form events (e.g. `oninput` or `onsubmit`): ```rust -form { - oninput: move |evt| { - if !validate_input(evt.values) { - // display what errors validation resulted in - } - }, - input { name: "name", } - input { name: "age", } - input { name: "date", } -} +{{#include ../../examples/input_uncontrolled.rs:component}} ``` +``` +Submitted! UiEvent { data: FormData { value: "", values: {"age": "very old", "date": "1966", "name": "Fred"} } } +``` \ No newline at end of file diff --git a/docs/guide/src/interactivity/useref.md b/docs/guide/src/interactivity/useref.md deleted file mode 100644 index e8eda8e83..000000000 --- a/docs/guide/src/interactivity/useref.md +++ /dev/null @@ -1,155 +0,0 @@ -# UseRef - -You might've noticed a fundamental limitation to `use_state`: to modify the value in-place, it must be cheaply cloneable. But what if your type is not cheap to clone? - -In these cases, you should reach for `use_ref` which is essentially just a glorified `Rc>` (Rust [smart pointers](https://doc.rust-lang.org/book/ch15-04-rc.html)). - -This provides us some runtime locks around our data, trading reliability for performance. For most cases though, you will find it hard to make `use_ref` crash. - -> Note: this is *not* exactly like its React counterpart since calling `write` will cause a re-render. For more React parity, use the `write_silent` method. - -To use the hook: - -```rust -let names = use_ref(&cx, || vec!["susie", "calvin"]); -``` - -To read the hook values, use the `read()` method: - -```rust -cx.render(rsx!{ - ul { - names.read().iter().map(|name| { - rsx!{ "{name}" } - }) - } -}) -``` - -And then to write into the hook value, use the `write` method: - -```rust -names.write().push("Tiger"); -``` - -If you don't want to re-render the component when names is updated, then we can use the `write_silent` method: - -```rust -names.write_silent().push("Transmogrifier"); -``` - -Again, like `UseState`, the `UseRef` handle is clonable into async contexts: - - -```rust -// infinitely push calvin into the list -cx.spawn({ - to_owned![names]; - async move { - loop { - wait_ms(100).await; - names.write().push("Calvin"); - } - } -}) -``` - -## Using UseRef for complex state - -The best use case of `use_ref` is to manage "complex" data local to your component. This should be things like large structs, collections, queues, state machines, and other data where cloning would be expensive. - - -```rust -let val = use_ref(&cx, || vec![1, 3, 3, 7]); -let val = use_ref(&cx, || (0..10000).collect::x>()); -let val = use_ref(&cx, || Configuration { - a: "asdasd", - // .. more complex fields -}); -``` - -UseRef can be sent down into child components too. - -UseRef memoizes with itself, performing a cheap pointer comparison. If the UseRef handle is the same, then the component can be memoized. - -```rust - -fn app(cx: Scope) -> Element { - let val = use_ref(&cx, || 0); - - cx.render(rsx!{ - Child { val: val.clone() } - }) -} - -#[inline_props] -fn Child(cx: Scope, val: UseRef) -> Element { - // ... -} -``` - -## Using UseRef with "models" - -One option for state management that UseRef enables is the development of a "model" for your components. This particular pattern enables you to model your state with regular structs. - -For instance, our calculator example uses a struct to model the state. - -```rust - -struct Calculator { - display_value: String, - operator: Option, - waiting_for_operand: bool, - cur_val: f64, -} -``` - -Our component is really simple - we just call `use_ref` to get an initial calculator state. - -```rust -fn app(cx: Scope) -> Element { - let state = use_ref(&cx, Calculator::new); - - cx.render(rsx!{ - // the rendering code - }) -} -``` - -In our UI, we can then use `read` and a helper method to get data out of the model. - -```rust -// Our accessor method -impl Calculator { - fn formatted_display(&self) -> String { - self.display_value - .parse::() - .unwrap() - .separated_string() - } -} - -// And then in the UI -cx.render(rsx!{ - div { [state.read().formatted_display()] } -}) -``` - -To modify the state, we can setup a helper method and then attach it to a callback. - -```rust -// our helper -impl Calculator { - fn clear_display(&mut self) { - self.display_value = "0".to_string() - } -} - -// our callback -cx.render(rsx!{ - button { - onclick: move |_| state.write().clear_display(), - } -}) -``` - diff --git a/docs/guide/src/interactivity/usestate.md b/docs/guide/src/interactivity/usestate.md deleted file mode 100644 index 64aa81d59..000000000 --- a/docs/guide/src/interactivity/usestate.md +++ /dev/null @@ -1,114 +0,0 @@ -# Fundamental hook: `use_state` - -The first fundamental hook for state management is `use_state`. This particular hook is designed to work well with the entire Dioxus ecosystem including futures, children, and memoization. - - -## Basic usage - -The simplest use case of `use_state` is a simple counter. The handle returned by `use_state` implements `Add` and `AddAssign`. Writing through `AddAssign` will automatically mark the component as dirty, forcing an update. - -```rust -fn app(cx: Scope) -> Element { - let count = use_state(&cx, || 0); - - rsx!(cx, button { onclick: move |_| count += 1, }) -} -``` - -## Reference - -### Common ops - -The `use_state` hook is very similar to its React counterpart. When we use it, we get a smart pointer - essentially an `Rc` that tracks when we update it. - -```rust -let mut count = use_state(&cx, || 0); - -// then to set the count -count += 1; -``` - - -### Current value - -Our `count` value is more than just an integer. Because it's a smart pointer, it also implements other useful methods helpful in various contexts. - -For instance, we can get a handle to the current value at any time: - -```rust -let current: Rc = count.current(); -``` - -Or, we can get the inner handle to set the value: - -```rust -let setter: Rc = count.setter(); -``` - -### Modify - -Or, we can set a new value using the current value as a reference: - -```rust -count.modify(|i| i + 1); -``` - -### `with_mut` and `make_mut` - -If the value is cheaply cloneable, then we can call `with_mut` to get a mutable reference to the value: - -```rust -count.with_mut(|i| *i += 1); -``` - -Alternatively, we can call `make_mut` which gives us a `RefMut` to the underlying value: - -```rust -*count.make_mut() += 1; -``` - -### Use in Async - -Plus, the `set_count` handle is cloneable into async contexts, so we can use it in futures. - -```rust -// count up infinitely -cx.spawn({ - let count = count.clone(); - async move { - loop { - wait_ms(100).await; - count += 1; - } - } -}) -``` - -## Using UseState for simple data - -The best use case of `use_state` is to manage "simple" data local to your component. This should be things like numbers, strings, small maps, and cheaply-clonable types. - -```rust -let val = use_state(&cx, || 0); -let val = use_state(&cx, || "Hello!"); -let val = use_state(&cx, || vec![1, 3, 3, 7]); -``` - -UseState can be sent down into child components too. - -You can either pass by reference to always force the child to update when the value changes, or you can clone the handle to take advantage of automatic memoization. In these cases, calling "get" will return stale data - always prefer "current" when "cloning" the UseState. This automatic memoization of UseState solves a performance problem common in React. - -```rust - -fn app(cx: Scope) -> Element { - let val = use_state(&cx, || 0); - - cx.render(rsx!{ - Child { val: val.clone() } - }) -} - -fn Child(cx: Scope, val: UseState) -> Element { - // ... -} -``` diff --git a/docs/guide/src/lib.rs b/docs/guide/src/lib.rs new file mode 100644 index 000000000..97cda56a8 --- /dev/null +++ b/docs/guide/src/lib.rs @@ -0,0 +1 @@ +// empty (we only need this crate for the examples) diff --git a/docs/guide/src/misc/contributors.md b/docs/guide/src/misc/contributors.md deleted file mode 100644 index 9a7181a37..000000000 --- a/docs/guide/src/misc/contributors.md +++ /dev/null @@ -1,8 +0,0 @@ -# Contributors - -All pull requests (including those made by a team member) must be approved by at least one other team member. -Larger, more nuanced decisions about design, architecture, breaking changes, trade offs, etc are made by team consensus. - -Contributors to this guide: -- [jkelleyrtp](https://github.com/jkelleyrtp) -- [alexkirsz](https://github.com/alexkirsz) diff --git a/docs/guide/src/tutorial/publishing.md b/docs/guide/src/publishing/desktop.md similarity index 87% rename from docs/guide/src/tutorial/publishing.md rename to docs/guide/src/publishing/desktop.md index 89bf39edf..f30b0b26d 100644 --- a/docs/guide/src/tutorial/publishing.md +++ b/docs/guide/src/publishing/desktop.md @@ -59,7 +59,7 @@ For example, a macOS app would look like this: ![Published App](../images/publish.png) -Nice! And it's only 4.8 Mb - extremely lean!! Because Dioxus leverages your platform's native WebView, Dioxus apps are extremely memory efficient and won't waste your battery. +Nice! And it's only 4.8 Mb – extremely lean!! Because Dioxus leverages your platform's native WebView, Dioxus apps are extremely memory efficient and won't waste your battery. -> Note: not all CSS works the same on all platforms. Make sure to view your app's CSS on each platform - or web browser (Firefox, Chrome, Safari) before publishing. +> Note: not all CSS works the same on all platforms. Make sure to view your app's CSS on each platform – or web browser (Firefox, Chrome, Safari) before publishing. diff --git a/docs/guide/src/publishing/index.md b/docs/guide/src/publishing/index.md new file mode 100644 index 000000000..96670f1c8 --- /dev/null +++ b/docs/guide/src/publishing/index.md @@ -0,0 +1 @@ +# Publishing diff --git a/docs/guide/src/publishing/web.md b/docs/guide/src/publishing/web.md new file mode 100644 index 000000000..9c285a104 --- /dev/null +++ b/docs/guide/src/publishing/web.md @@ -0,0 +1,8 @@ +## Publishing with Github Pages +To build our app and publish it to Github: + +- Make sure GitHub Pages is set up for your repo +- Build your app with `trunk build --release` (include `--public-url ` to update asset prefixes if using a project site) +- Move your generated HTML/CSS/JS/Wasm from `dist` into the folder configured for Github Pages +- Add and commit with git +- Push to GitHub diff --git a/docs/guide/src/ROADMAP.md b/docs/guide/src/roadmap.md similarity index 81% rename from docs/guide/src/ROADMAP.md rename to docs/guide/src/roadmap.md index 9d2ff542c..3f0dac800 100644 --- a/docs/guide/src/ROADMAP.md +++ b/docs/guide/src/roadmap.md @@ -1,14 +1,14 @@ # Roadmap & Feature-set -Before we dive into Dioxus, feel free to take a look at our feature set and roadmap to see if what Dioxus can do today works for you. +This feature set and roadmap can help you decide if what Dioxus can do today works for you. If a feature that you need doesn't exist or you want to contribute to projects on the roadmap, feel free to get involved by [joining the discord](https://discord.gg/XgGxMSkvUM). Generally, here's the status of each platform: -- **Web**: Dioxus is a great choice for pure web-apps - especially for CRUD/complex apps. However, it does lack the ecosystem of React, so you might be missing a component library or some useful hook. +- **Web**: Dioxus is a great choice for pure web-apps – especially for CRUD/complex apps. However, it does lack the ecosystem of React, so you might be missing a component library or some useful hook. -- **SSR**: Dioxus is a great choice for pre-rendering, hydration, and rendering HTML on a web endpoint. Be warned - the VirtualDom is not (currently) `Send + Sync`. +- **SSR**: Dioxus is a great choice for pre-rendering, hydration, and rendering HTML on a web endpoint. Be warned – the VirtualDom is not (currently) `Send + Sync`. - **Desktop**: You can build very competent single-window desktop apps right now. However, multi-window apps require support from Dioxus core and are not ready. @@ -58,15 +58,11 @@ Generally, here's the status of each platform: - ✅ = implemented and working - 🛠 = actively being worked on - 👀 = not yet implemented or being worked on -- ❓ = not sure if will or can implement ## Roadmap These Features are planned for the future of Dioxus: ---- - - ### Core - [x] Release of Dioxus Core - [x] Upgrade documentation to include more theory and be more comprehensive @@ -115,3 +111,22 @@ These Features are planned for the future of Dioxus: - [ ] Resize observer +## Work in Progress + +### Build Tool + +We are currently working on our own build tool called [Dioxus CLI](https://github.com/DioxusLabs/cli) which will support: + +- an interactive TUI +- on-the-fly reconfiguration +- hot CSS reloading +- two-way data binding between browser and source code +- an interpreter for `rsx!` +- ability to publish to github/netlify/vercel +- bundling for iOS/Desktop/etc + +### LiveView / Server Component Support + +The internal architecture of Dioxus was designed from day one to support the `LiveView` use-case, where a web server hosts a running app for each connected user. As of today, there is no first-class LiveView support – you'll need to wire this up yourself. + +While not currently fully implemented, the expectation is that LiveView apps can be a hybrid between Wasm and server-rendered where only portions of a page are "live" and the rest of the page is either server-rendered, statically generated, or handled by the host SPA. diff --git a/docs/guide/src/setup.md b/docs/guide/src/setup.md deleted file mode 100644 index 3244ce301..000000000 --- a/docs/guide/src/setup.md +++ /dev/null @@ -1,114 +0,0 @@ -# Overview - -In this chapter, we're going to get set up with a small desktop application. - -We'll learn about: -- Installing the Rust programming language -- Installing the Dioxus CLI for bundling and developing -- Suggested cargo extensions - - -For platform-specific guides, check out the [Platform Specific Guides](/reference/platforms/index.md). - -# Setting up Dioxus - -Dioxus requires a few main things to get up and running: - -- The [Rust compiler](https://www.rust-lang.org) and associated build tooling -- An editor of your choice, ideally configured with the [Rust-Analyzer LSP plugin](https://rust-analyzer.github.io) - -Dioxus integrates very well with the Rust-Analyzer IDE plugin which will provide appropriate syntax highlighting, code navigation, folding, and more. - -## Installing Rust - -Head over to [https://rust-lang.org](http://rust-lang.org) and install the Rust compiler. - -Once installed, make sure to install wasm32-unknown-unknown as a target if you're planning on deploying your app to the web. - -``` -rustup target add wasm32-unknown-unknown -``` - -## Platform-Specific Dependencies - -If you are running a modern, mainstream operating system, you should need no additional setup to build WebView-based Desktop apps. However, if you are running an older version of Windows or a flavor of Linux with no default web rendering engine, you might need to install some additional dependencies. - - -### Windows - -Windows Desktop apps depend on WebView2 - a library which should be installed in all modern Windows distributions. If you have Edge installed, then Dioxus will work fine. If you *don't* have Webview2, [then you can install it through Microsoft](https://developer.microsoft.com/en-us/microsoft-edge/webview2/). MS provides 3 options: - -1. A tiny "evergreen" *bootstrapper* which will fetch an installer from Microsoft's CDN -2. A tiny *installer* which will fetch Webview2 from Microsoft's CDN -3. A statically linked version of Webview2 in your final binary for offline users - -For development purposes, use Option 1. - -### Linux - -Webview Linux apps require WebkitGtk. When distributing, this can be part of your dependency tree in your `.rpm` or `.deb`. However, it's very likely that your users will already have WebkitGtk. - -``` -sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev -``` - -When using Debian/bullseye `libappindicator3-dev` is no longer available but replaced by `libayatana-appindicator3-dev`. - -``` -# on Debian/bullseye use: -sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev -``` - -If you run into issues, make sure you have all the basics installed, as outlined in the [Tauri docs](https://tauri.studio/v1/guides/getting-started/prerequisites#setting-up-linux). - - -### macOS - -Currently - everything for macOS is built right in! However, you might run into an issue if you're using nightly Rust due to some permissions issues in our Tao dependency (which have been resolved but not published). - - -## Dioxus-CLI for dev server, bundling, etc. - -We also recommend installing the Dioxus CLI. The Dioxus CLI automates building and packaging for various targets and integrates with simulators, development servers, and app deployment. To install the CLI, you'll need cargo (which should be automatically installed with Rust): - -``` -$ cargo install dioxus-cli -``` - -You can update dioxus-cli at any time with: - -``` -$ cargo install --force dioxus-cli -``` - -We provide this 1st-party tool to save you from having to run potentially untrusted code every time you add a crate to your project - as is standard in the NPM ecosystem. - -## Suggested extensions - -If you want to keep your traditional `npm install XXX` workflow for adding packages, you might want to install `cargo-edit` and a few other fun `cargo` extensions: - -- [cargo edit](https://github.com/killercup/cargo-edit) for adding dependencies from the CLI -- [cargo-expand](https://github.com/dtolnay/cargo-expand) for expanding macro calls -- [cargo tree](https://doc.rust-lang.org/cargo/commands/cargo-tree.html) - an integrated cargo command that lets you inspect your dependency tree - -That's it! We won't need to touch NPM/WebPack/Babel/Parcel, etc. However, you _can_ configure your app to use WebPack with [traditional WASM-pack tooling](https://rustwasm.github.io/wasm-pack/book/tutorials/hybrid-applications-with-webpack/using-your-library.html). - -## Rust Knowledge - -With Rust, things like benchmarking, testing, and documentation are included in the language. We strongly recommend going through the official Rust book _completely_. However, our hope is that a Dioxus app can serve as a great first Rust project. With Dioxus you'll learn about: - -- Error handling -- Structs, Functions, Enums -- Closures -- Macros - -We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need deep knowledge on async, lifetimes, or smart pointers until you really start building complex Dioxus apps. - -We strongly encourage exploring the guides for more information on how to work with the integrated tooling: - -- [Testing](Testing.md) -- [Documentation](Documentation.md) -- [Benchmarking](Benchmarking.md) -- [Building](Building.md) -- [Modules](Modules.md) -- [Crates](Crates.md) diff --git a/docs/guide/src/state/fermi.md b/docs/guide/src/state/fermi.md deleted file mode 100644 index 541621840..000000000 --- a/docs/guide/src/state/fermi.md +++ /dev/null @@ -1,110 +0,0 @@ -# Fermi - -After having covered local and global state, you're definitely ready to start building some complex Dioxus apps. Before you get too far, check out the Fermi crate. Fermi makes it dead-easy to manage global state and scales to the largest of apps. - -## What is Fermi for? - -If you're building an app that has scaled past a few small components, then it'll be worth sketching out and organizing your app's state. Fermi makes it easy to transition from a simple app that relies on `use_state` to an app with dozens of components. - -> To put it simply - Fermi is the ideal crate for managing global state in your Dioxus app. - - -## How do I use it? - -To add fermi to your project, simply add the "fermi" feature to your Dioxus dependency. - -```toml -[dependencies] -dioxus = { version = "0.2", features = ["desktop", "fermi"] } -``` - -Fermi is built on the concept of "atoms" of global state. Instead of bundling all our state together in a single mega struct, we actually chose to implement it piece-by-piece with functions. - -Each piece of global state in your app is represented by an "atom." - -```rust -static TITLE: Atom = |_| "Defualt Title".to_string(); -``` - -This atom can be then used the with the `use_atom` hook as a drop-in replacement for `use_state`. - -```rust -static TITLE: Atom = |_| "Defualt Title".to_string(); - -fn Title(cx: Scope) -> Element { - let title = use_atom(&cx, TITLE); - - cx.render(rsx!{ - button { onclick: move |_| title.set("new title".to_string()) } - }) -} -``` - -However, Fermi really becomes useful when we want to share the value between two different components. - -```rust -static TITLE: Atom = |_| "Defualt Title".to_string(); - -fn TitleBar(cx: Scope) -> Element { - let title = use_atom(&cx, TITLE); - - rsx!{cx, button { onclick: move |_| title.set("titlebar title".to_string()) } } -} - -fn TitleCard(cx: Scope) -> Element { - let title = use_atom(&cx, TITLE); - - rsx!{cx, button { onclick: move |_| title.set("title card".to_string()) } } -} -``` - -These two components can get and set the same value! - - -## Use with collections - -Fermi gets *really* powerful when used to manage collections of data. Under the hood, Fermi uses immutable collections and tracks reads and writes of individual keys. This makes it easy to implement things like lists and infinite posts with little performance penalty. It also makes it really easy to refactor our app and add new fields. - -```rust -static NAMES: AtomRef = |builder| {}; -static CHECKED: AtomRef = |builder| {}; -static CREATED: AtomRef = |builder| {}; -``` - -To use these collections: - -```rust -#[inline_props] -fn Todo(cx: Scope, id: Uuid) -> Element { - let name = use_atom(&cx, NAMES.select(id)); - let checked = use_atom(&cx, CHECKED.select(id)); - let created = use_atom(&cx, CREATED.select(id)); - - // or - - let (name, checked, created) = use_atom(&cx, (NAMES, CHECKED, CREATED).select(id)); -} -``` - -This particular pattern might seem strange at first - "why isn't all of our state under one struct?" - but eventually shows its worth when dealing with large amounts of data. By composing collections together, we can get get the perks of the Entity-Component-System architecture in our Dioxus apps. Performance is quite predictable here and easy to trace. - -## AtomRef - -Much like `use_ref` can be used to manage complex state locally, `AtomRef` can be used to manage complex global state. `AtomRef` is basically a global `Rc>` with mutation tracking. - -It too serves as a basic replacement for `use_ref`: - -```rust -fn Todo(cx: Scope) -> Element { - let cfg = use_atom_ref(&cx, CFG); - - cfg.write().enable_option(); -} - -``` - -## Future Reading - -This guide is meant just to be an overview of Fermi. This page is just a short advertisement for what we consider the best state management solution available today for Dioxus apps. - -For further reading, check out the [crate itself](https://github.com/DioxusLabs/dioxus/tree/master/packages/fermi)! diff --git a/docs/guide/src/state/liftingstate.md b/docs/guide/src/state/liftingstate.md deleted file mode 100644 index 9974c8326..000000000 --- a/docs/guide/src/state/liftingstate.md +++ /dev/null @@ -1,130 +0,0 @@ -# Lifting State and Fanning Out - -Maintaining state local to components doesn't always work. - -One of the most reliable state management patterns in large Dioxus apps is to `lift-up` and `fan-out`. Lifting up and fanning-out state is the ideal way to structure your app to maximize code reuse, testability, and deterministic rendering. - - -## Lifting State - -When building complex apps with Dioxus, the best approach is to start by placing your state and an UI all within a single component. Once your component has passed a few hundred lines, then it might be worth refactoring it into a few smaller components. - -Here, we're now challenged with how to share state between these various components. - -Let's say we refactored our component to separate an input and a display. - -```rust -fn app(cx: Scope) -> Element { - cx.render(rsx!{ - Title {} - Input {} - }) -} -``` - -Whenever a value is inputted in our `Input` component, we need to somehow propagate those changes into the `Title` component. - -A quick-and-dirty solution - which works for many apps - is to simply share a UseState between the two components. - -```rust -fn app(cx: Scope) -> Element { - let text = use_state(&cx, || "default".to_string()); - - cx.render(rsx!{ - Title { text: text.clone() } - Input { text: text.clone() } - }) -} -``` - -> Note: since we `Cloned` our `text` `UseState` handle, both `Title` and `Input` will be memoized. - -Here, we've "lifted" state out of our `Input` component and brought it up to the closest shared ancestor. In our input component, we can directly use this UseState handle as if it had been defined locally: - -```rust -#[inline_props] -fn Input(cx: Scope, text: UseState) -> Element { - cx.render(rsx!{ - input { oninput: move |evt| text.set(evt.value.clone()) } - }) -} -``` -Similarly, our `Title` component would be straightforward: - -```rust -#[inline_props] -fn Title(cx: Scope, text: UseState) -> Element { - cx.render(rsx!{ - h1 { "{text}" } - }) -} -``` - -For more complicated use cases, we can take advantage of the EventHandler coercion talked about before to pass in any callback. Recall that fields on components that start with "on" are automatically "upgraded" into an `EventHandler` at the call site. - -This lets us abstract over the exact type of state being used to store the data. - -For the `Input` component, we would simply add a new `oninput` field: - -```rust -#[inline_props] -fn Input<'a>(cx: Scope<'a>, oninput: EventHandler<'a, String>) -> Element { - cx.render(rsx!{ - input { oninput: move |evt| oninput.call(evt.value.clone()), } - }) -} -``` - -For our `Title` component, we could also abstract it to take any `&str`: - -```rust -#[inline_props] -fn Title<'a>(cx: Scope<'a>, text: &'a str) -> Element { - cx.render(rsx!{ - h1 { "{text}" } - }) -} -``` - -## Fanning Out - -As your app grows and grows, you might need to start pulling in global state to avoid prop drilling. This tends to solve a lot of problems, but generates even more. - -For instance, let's say we built a beautiful `Title` component. Instead of passing props in, we instead are using a `use_read` hook from Fermi. - -```rust -fn Title(cx: Scope) -> Element { - let title = use_read(&cx, TITLE); - - cx.render(rsx!{ - h1 { "{title}" } - }) -} -``` - -This is great - all is well in the world. We get precise updates, automatic memoization, and a solid abstraction. But, what happens when we want to reuse this component in another project? This component is now deeply intertwined with our global state - which might not be the same in another app. - -In this case, we want to "lift" our global state out of "view" components. With `lifting`, our individual components at "leaf" position of our VirtualDom are "pure", making them easily reusable, testable, and deterministic. - -To enable our title component to be used across apps, we want to lift our atoms upwards and out of the Title component. We would organize a bunch of other components in this section of the app to share some of the same state. - -```rust -fn DocsiteTitlesection(cx: Scope) { - // Use our global state in a wrapper component - let title = use_read(&cx, TITLE); - let subtitle = use_read(&cx, SUBTITLE); - - let username = use_read(&cx, USERNAME); - let points = use_read(&cx, POINTS); - - // and then pass our global state in from the outside - cx.render(rsx!{ - Title { title: title.clone(), subtitle: subtitle.clone() } - User { username: username.clone(), points: points.clone() } - }) -} -``` - -This particular wrapper component unfortunately cannot be reused across apps. However, because it simply wraps other real elements, it doesn't have to. We are free to reuse our TitleBar and UserBar components across apps with ease. We also know that this particular component is plenty performant because the wrapper doesn't have any props and is always memoized. The only times this component re-renders is when any of the atoms change. - -This is the beauty of Dioxus - we always know where our components are likely to be re-rendered. Our wrapper components can easily prevent any large re-renders by simply memoizing their components. This system might not be as elegant or precise as signal systems found in libraries like Sycamore or SolidJS, but it is quite ergonomic. diff --git a/docs/guide/src/state/sharedstate.md b/docs/guide/src/state/sharedstate.md deleted file mode 100644 index bf08f34de..000000000 --- a/docs/guide/src/state/sharedstate.md +++ /dev/null @@ -1,65 +0,0 @@ -# Global State - -If your app has finally gotten large enough where passing values through the tree ends up polluting the intent of your code, then it might be time to turn to global state. - -In Dioxus, global state is shared through the Context API. This guide will show you how to use the Context API to simplify state management. - -## Provide Context and Consume Context - -The simplest way of retrieving shared state in your app is through the Context API. The Context API allows you to provide and consume an item of state between two components. - -Whenever a component provides a context, it is then accessible to any child components. - -> Note: parent components cannot "reach down" and consume state from below their position in the tree. - -The terminology here is important: `provide` context means that the component will expose state and `consume` context means that the child component can acquire a handle to state above it. - -Instead of using keys or statics, Dioxus prefers the `NewType` pattern to search for parent state. This means each state you expose as a context should be its own unique type. - -In practice, you'll have a component that exposes some state: - - -```rust -#[derive(Clone)] -struct Title(String); - -fn app(cx: Scope) -> Element { - cx.use_hook(|_| { - cx.provide_context(Title("Hello".to_string())); - }); - - cx.render(rsx!{ - Child {} - }) -} -``` - -And then in our component, we can consume this state at any time: - -```rust -fn Child(cx: Scope) -> Element { - let name = cx.consume_context::(); - - // -} -``` - -Note: calling "consume" state can be a rather expensive operation to perform during each render. Prefer to consume state within a `use_hook`: - -```rust -fn Child(cx: Scope) -> Element { - // cache our "consume_context" operation - let name = cx.use_hook(|_| cx.consume_context::<Title>()); -} -``` - -All `Context` must be cloned - the item will be cloned into each call of `consume_context`. To make this operation cheaper, consider wrapping your type in an `Rc` or `Arc`. - - -<!-- ## Coroutines - -The `use_coroutine` hook --> - -<!-- # `use_context` and `use_context_provider` - -These --> diff --git a/docs/guide/src/tutorial/index.md b/docs/guide/src/tutorial/index.md deleted file mode 100644 index 875b74bcd..000000000 --- a/docs/guide/src/tutorial/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# Putting it all together - -So far, we've covered the basics of Dioxus. We've talked about: - -- Elements -- Components -- Interactivity -- State Management -- Async Actions -- Styling - -In this chapter, we'll build a real-world weather app that combines everything we've learned into a cute application that you can run locally. It'll let us monitor different locations simultaneously and periodically check for updates. - -The app is running on the web [here]() but is also available: - -- as a desktop app -- a mobile app -- and as an API endpoint here - -![Weather App Image](/static/weather_app.png) - - -Let's get started! Head on to the next chapter where we create our app. diff --git a/docs/guide/src/tutorial/new_app.md b/docs/guide/src/tutorial/new_app.md deleted file mode 100644 index ba28372c3..000000000 --- a/docs/guide/src/tutorial/new_app.md +++ /dev/null @@ -1,60 +0,0 @@ -# New app - -To get started, let's create a new Rust project for our Dog Search Engine. - -```shell -$ cargo new --bin doggo -$ cd doggo -``` - -Make sure our project builds by default - -```shell -$ cargo run - - Compiling doggo v0.1.0 - Finished dev [unoptimized + debuginfo] target(s) in 0.41s - Running `target/debug/doggo` -Hello, world! -``` - -## Adding Dioxus Desktop as a dependency - -We can either edit our Cargo.toml directly: - -```toml -[dependencies] -dioxus = { version = "*", features = ["desktop"]} -``` - -or use `cargo-edit` to add it via the CLI: - -```shell -$ cargo add dioxus --features desktop -``` - -## Setting up a hello world - -Let's edit the project's `main.rs` and add the skeleton of - -```rust -use dioxus::prelude::*; - -fn main() { - dioxus::desktop::launch(app); -} - -fn app(cx: Scope) -> Element { - cx.render(rsx!( - div { "hello world!" } - )) -} -``` - - -## Making sure things run - - - - - diff --git a/docs/reference/.gitignore b/docs/reference/.gitignore deleted file mode 100644 index 25d80f5f3..000000000 --- a/docs/reference/.gitignore +++ /dev/null @@ -1 +0,0 @@ -reference diff --git a/docs/reference/book.toml b/docs/reference/book.toml deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/reference/src/README.md b/docs/reference/src/README.md deleted file mode 100644 index 6de14f333..000000000 --- a/docs/reference/src/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Dioxus: Reference and Advanced Guides - -![dioxuslogo](./images/dioxuslogo_full.png) - -**Dioxus** is a framework and ecosystem for building fast, scalable, and robust user interfaces with the Rust programming language. This guide will help you get started with Dioxus running on the Web, Desktop, Mobile, and more. - -> This book is the reference and Advanced Guides for the Dioxus framework. For a tutorial on how to actually *use* Dioxus, make sure to check out the [official guide](https://dioxuslabs.com/guide/). - - -## Reference and Guides - -With the reference, we aim to maintain documentation of functionality that might not be mentioned in the official guide to keep the information load to a minimum. Some topics not included by the guide but discussed in the reference include: - -- ThreadSafety of the VirtualDom -- Full usage of `rsx!` and included features -- Spread pattern for component properties -- Testing -- Memoization in Depth -- Custom Elements -- Custom Renderers - - -## Contributing - -If this doc is lacking in any way, is confusing, has a typo, or you'd just like to help improve things, feel free to make a PR against the [Dioxus repo](https://github.com/DioxusLabs/dioxus/tree/master/docs/reference). - -All contributions will be licensed under MIT/Apache2. diff --git a/docs/reference/src/SUMMARY.md b/docs/reference/src/SUMMARY.md deleted file mode 100644 index 9f9371b12..000000000 --- a/docs/reference/src/SUMMARY.md +++ /dev/null @@ -1,15 +0,0 @@ -# Summary - -- [Introduction](README.md) - -- [Platforms](platforms/index.md) - - [Web](platforms/web.md) - - [Server Side Rendering](platforms/ssr.md) - - [Desktop](platforms/desktop.md) - - [Mobile](platforms/mobile.md) - - [TUI](platforms/tui.md) - -- [Advanced Guides](guide/index.md) - - [RSX in Depth](guide/rsx_in_depth.md) - - [Custom Renderer](guide/custom_renderer.md) - - [Hot Reloading Rsx](guide/hot_reloading.md) \ No newline at end of file diff --git a/docs/reference/src/guide/index.md b/docs/reference/src/guide/index.md deleted file mode 100644 index 53d2cb54f..000000000 --- a/docs/reference/src/guide/index.md +++ /dev/null @@ -1 +0,0 @@ -# Advanced Guides diff --git a/docs/reference/src/guide/rsx_in_depth.md b/docs/reference/src/guide/rsx_in_depth.md deleted file mode 100644 index 8018aed60..000000000 --- a/docs/reference/src/guide/rsx_in_depth.md +++ /dev/null @@ -1,231 +0,0 @@ -# RSX in Depth - -The RSX macro makes it very easy to assemble complex UIs with a very natural Rust syntax: - - -```rust -rsx!( - div { - button { - onclick: move |e| todos.write().new_todo(), - "Add todo" - } - ul { - class: "todo-list", - todos.iter().map(|(key, todo)| rsx!( - li { - class: "beautiful-todo" - key: "f" - h3 { "{todo.title}" } - p { "{todo.contents}"} - } - )) - } - } -) -``` - -In this section, we'll cover the `rsx!` macro in depth. If you prefer to learn through examples, the `code reference` guide has plenty of examples on how to use `rsx!` effectively. - -### Element structure - -Attributes must come before child elements - -```rust -div { - hidden: "false", - "some text" - child {} - Component {} // uppercase - component() // lowercase is treated like a function call - (0..10).map(|f| rsx!{ "hi {f}" }) // arbitrary rust expressions -} -``` - -Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work: - -- `name: value` sets a property on this element. -- `"text"` adds a new text element -- `tag {}` adds a new child element -- `CustomTag {}` adds a new child component -- `{expr}` pastes the `expr` tokens literally. They must be `IntoIterator<T> where T: IntoVnode` to work properly - -Commas are entirely optional, but might be useful to delineate between elements and attributes. - -The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. To push you in the right direction, all text-based attributes take `std::fmt::Arguments` directly, so you'll want to reach for `format_args!` when the built-in `f-string` interpolation just doesn't cut it. - - -### Ignoring `cx.render` with `rsx!(cx, ...)` - -Sometimes, writing `cx.render` is a hassle. The `rsx! macro will accept any token followed by a comma as the target to call "render" on: - -```rust -cx.render(rsx!( div {} )) -// becomes -rsx!(cx, div {}) -``` - - - -### Conditional Rendering - -Sometimes, you might not want to render an element given a condition. The rsx! macro will accept any tokens directly contained with curly braces, provided they resolve to a type that implements `IntoIterator<VNode>`. This lets us write any Rust expression that resolves to a VNode: - - -```rust -rsx!({ - if enabled { - rsx!(cx, div {"enabled"}) - } else { - rsx!(cx, li {"disabled"}) - } -}) -``` -A convenient way of hiding/showing an element is returning an `Option<VNode>`. When combined with `and_then`, we can succinctly control the display state given some boolean: - -```rust -rsx!({ - a.and_then(rsx!(div {"enabled"})) -}) -``` - -It's important to note that the expression `rsx!()` is typically lazy - this expression must be _rendered_ to produce a VNode. When using match statements, we must render every arm as to avoid the `no two closures are identical` rule that Rust imposes: - -```rust -// this will not compile! -match case { - true => rsx!(div {}), - false => rsx!(div {}) -} - -// the nodes must be rendered first -match case { - true => rsx!(cx, div {}), - false => rsx!(cx, div {}) -} -``` - -### Lists - -Again, because anything that implements `IntoIterator<VNode>` is valid, we can use lists directly in our `rsx!`: - -```rust -let items = vec!["a", "b", "c"]; - -cx.render(rsx!{ - ul { - {items.iter().map(|f| rsx!(li { "a" }))} - } -}) -``` - -Sometimes, it makes sense to render VNodes into a list: - -```rust -let mut items = vec![]; - -for _ in 0..5 { - items.push(rsx!(cx, li {} )) -} - -rsx!(cx, {items} ) -``` - -#### Lists and Keys - -When rendering the VirtualDom to the screen, Dioxus needs to know which elements have been added and which have been removed. These changes are determined through a process called "diffing" - an old set of elements is compared to a new set of elements. If an element is removed, then it won't show up in the new elements, and Dioxus knows to remove it. - -However, with lists, Dioxus does not exactly know how to determine which elements have been added or removed if the order changes or if an element is added or removed from the middle of the list. - -In these cases, it is vitally important to specify a "key" alongside the element. Keys should be persistent between renders. - -```rust -fn render_list(cx: Scope, items: HashMap<String, Todo>) -> DomTree { - rsx!(cx, ul { - {items.iter().map(|key, item| { - li { - key: key, - h2 { "{todo.title}" } - p { "{todo.contents}" } - } - })} - }) -} -``` - -There have been many guides made for keys in React, so we recommend reading up to understand their importance: - -- [React guide on keys](https://reactjs.org/docs/lists-and-keys.html) -- [Importance of keys (Medium)](https://kentcdodds.com/blog/understanding-reacts-key-prop) - -### Complete Reference -```rust -let text = "example"; - -cx.render(rsx!{ - div { - h1 { "Example" }, - - {title} - - // fstring interpolation - "{text}" - - p { - // Attributes - tag: "type", - - // Anything that implements display can be an attribute - abc: 123, - - enabled: true, - - // attributes also supports interpolation - // `class` is not a restricted keyword unlike JS and ClassName - class: "big small wide short {text}", - - class: format_args!("attributes take fmt::Arguments. {}", 99), - - tag: {"these tokens are placed directly"} - - // Children - a { "abcder" }, - - // Children with attributes - h2 { "hello", class: "abc-123" }, - - // Child components - CustomComponent { a: 123, b: 456, key: "1" }, - - // Child components with paths - crate::components::CustomComponent { a: 123, b: 456, key: "1" }, - - // Iterators - { (0..3).map(|i| rsx!( h1 {"{:i}"} )) }, - - // More rsx!, or even html! - { rsx! { div { } } }, - { html! { <div> </div> } }, - - // Matching - // Requires rendering the nodes first. - // rsx! is lazy, and the underlying closures cannot have the same type - // Rendering produces the VNode type - {match rand::gen_range::<i32>(1..3) { - 1 => rsx!(cx, h1 { "big" }) - 2 => rsx!(cx, h2 { "medium" }) - _ => rsx!(cx, h3 { "small" }) - }} - - // Optionals - {true.and_then(|f| rsx!( h1 {"Conditional Rendering"} ))} - - // Child nodes - {cx.props.children} - - // Any expression that is `IntoVNode` - {expr} - } - } -}) -``` diff --git a/docs/reference/src/images/dioxuslogo_full.png b/docs/reference/src/images/dioxuslogo_full.png deleted file mode 100644 index 7d08f2af8..000000000 Binary files a/docs/reference/src/images/dioxuslogo_full.png and /dev/null differ diff --git a/docs/reference/src/platforms/desktop.md b/docs/reference/src/platforms/desktop.md deleted file mode 100644 index 985fbe6d9..000000000 --- a/docs/reference/src/platforms/desktop.md +++ /dev/null @@ -1,47 +0,0 @@ -# Getting Started: Desktop - -One of Dioxus' killer features is the ability to quickly build a native desktop app that looks and feels the same across platforms. Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory. - -Dioxus Desktop is built off Tauri. Right now there aren't any Dioxus abstractions over keyboard shortcuts, menubar, handling, etc, so you'll want to leverage Tauri - mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly. The next major release of Dioxus-Desktop will include components and hooks for notifications, global shortcuts, menubar, etc. - - -## Getting Set up - -Getting Set up with Dioxus-Desktop is quite easy. Make sure you have Rust and Cargo installed, and then create a new project: - -```shell -$ cargo new --bin demo -$ cd demo -``` - -Add Dioxus with the `desktop` feature: - -```shell -$ cargo add dioxus --features desktop -``` - -Edit your `main.rs`: - -```rust -// main.rs -use dioxus::prelude::*; - -fn main() { - dioxus::desktop::launch(app); -} - -fn app(cx: Scope) -> Element { - cx.render(rsx!{ - div { - "hello world!" - } - }) -} -``` - - -To configure the webview, menubar, and other important desktop-specific features, checkout out some of the launch configuration in the [API reference](https://docs.rs/dioxus-desktop/). - -## Future Steps - -Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't! diff --git a/docs/reference/src/platforms/index.md b/docs/reference/src/platforms/index.md deleted file mode 100644 index fbbabd35d..000000000 --- a/docs/reference/src/platforms/index.md +++ /dev/null @@ -1,10 +0,0 @@ -# Platforms - -Dioxus supports many different platforms. Below are a list of guides that walk you through setting up Dioxus for a specific platform. - -### Setup Guides -- [Web](web.md) -- [Server Side Rendering](ssr.md) -- [Desktop](desktop.md) -- [Mobile](mobile.md) -- [TUI](tui.md) \ No newline at end of file diff --git a/docs/reference/src/platforms/tui.md b/docs/reference/src/platforms/tui.md deleted file mode 100644 index f8c97fa04..000000000 --- a/docs/reference/src/platforms/tui.md +++ /dev/null @@ -1,98 +0,0 @@ -# Getting Started: TUI - -TUI support is currently quite experimental. Even the project name will change. But, if you're willing to venture into the realm of the unknown, this guide will get you started. - - -[TUI Support](https://github.com/DioxusLabs/rink/raw/master/examples/example.png) - - -## Getting Set up - - -To tinker with TUI support, start by making a new package and adding our TUI feature. - -```shell -$ cargo new --bin demo -$ cd demo -$ cargo add dioxus --features tui -``` - - - -Then, edit your `main.rs` with the basic template. - -```rust -// main -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!" - } - }) -} -``` - -To run our app: - -```shell -$ cargo run -``` - -Press "ctrl-c" to close the app. To switch from "ctrl-c" to just "q" to quit you can launch the app with a configuration to disable the default quit and use the root TuiContext to quit on your own. - -```rust -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!" - } - }) -} -``` - -## Notes - -- Our TUI package uses flexbox for layout -- 1px is one character lineheight. Your regular CSS px does not translate. -- If your app panics, your terminal is wrecked. This will be fixed eventually. - -## Future Steps - -Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't! diff --git a/docs/reference/src/platforms/web.md b/docs/reference/src/platforms/web.md deleted file mode 100644 index ab5d6505b..000000000 --- a/docs/reference/src/platforms/web.md +++ /dev/null @@ -1,111 +0,0 @@ -# Getting Started: Web - -[*"Pack your things, we're going on an adventure!"*](https://trunkrs.dev) - -Dioxus is well supported for the web through WebAssembly. A build of Dioxus for the web will be roughly equivalent to the size of a React build (70kb vs 65kb), but will load significantly faster due to [WebAssembly's StreamingCompile](https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/) option. - -Building Dioxus apps for the web requires much less configuration than our JavaScript counterparts. - -## Tooling - -To develop your Dioxus app for the web, you'll need a tool to build and serve your assets. We recommend using [trunk](https://trunkrs.dev) which includes a build system, Wasm optimization, a dev server, and support for SASS/CSS. - -Currently, trunk projects can only build the root binary (ie the `main.rs`). To build a new Dioxus compatible project, this should get you up and running. - -First, [install trunk](https://trunkrs.dev/#install): -```shell -$ cargo install trunk -``` - -Make sure the `wasm32-unknown-unknown` target is installed: -```shell -$ rustup target add wasm32-unknown-unknown -``` - -Create a new project: - -```shell -$ cargo new --bin demo -$ cd demo -``` - -Add Dioxus with the `web` features: - -``` -$ cargo add dioxus --features web -``` - -Add an `index.html` for Trunk to use. Make sure your "mount point" element has an ID of "main" (this can be configured later): - -```html -<!DOCTYPE html> -<html> - <head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - </head> - <body> - <div id="main"> </div> - </body> -</html> -``` - -Edit your `main.rs`: -```rust -// main.rs - -use dioxus::prelude::*; - -fn main() { - dioxus::web::launch(app); -} - -fn app(cx: Scope) -> Element { - cx.render(rsx!{ - div { "hello, wasm!" } - }) -} -``` - - -And to serve our app: - -```shell -trunk serve -``` - -To build our app and publish it to Github: - -- Make sure Github Pages is set up for your repo -- Build your app with `trunk build --release` (include `--public-url <repo-name>` to update asset prefixes if using a project site) -- Move your generated HTML/CSS/JS/Wasm from `dist` into the folder configured for Github Pages -- Add and commit with git -- Push to Github - -That's it! - -## Future Build Tool - -We are currently working on our own build tool called [Dioxus Studio](http://github.com/dioxusLabs/studio) which will support: -- an interactive TUI -- on-the-fly reconfiguration -- hot CSS reloading -- two-way data binding between browser and source code -- an interpreter for `rsx!` -- ability to publish to github/netlify/vercel -- bundling for iOS/Desktop/etc - -## Features - -Currently, the web supports: - -- Pre-rendering/Hydration - -## Events - -The regular events in the `html` crate work just fine in the web. - - -## Future Steps - -Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't! diff --git a/examples/core_reference/antipatterns.rs b/examples/core_reference/antipatterns.rs deleted file mode 100644 index 5814e7096..000000000 --- a/examples/core_reference/antipatterns.rs +++ /dev/null @@ -1,185 +0,0 @@ -//! Example: Antipatterns -//! --------------------- -//! -//! This example shows what *not* to do and provides a reason why a given pattern is considered an "AntiPattern". Most -//! anti-patterns are considered wrong due performance reasons or violate the "rules" of Dioxus. These rules are -//! borrowed from other successful UI frameworks, and Dioxus is more focused on providing a familiar, ergonomic interface -//! rather than building new harder-to-misuse patterns. -//! -//! In this list we showcase: -//! - Not adding keys for iterators -//! - Heavily nested fragments -//! - Understanding ordering of set_state -//! - Naming conventions -//! - Rules of hooks -//! -//! Feel free to file a PR or Issue if you run into another antipattern that you think users of Dioxus should know about. -use dioxus::prelude::*; - -/// Antipattern: Iterators without keys -/// ----------------------------------- -/// -/// This is considered an anti-pattern for performance reasons. Dioxus will diff your current and old layout and must -/// take a slower path if it can't correlate old elements with new elements. Lists are particularly susceptible to the -/// "slow" path, so you're strongly encouraged to provide a unique, stable ID between renders. Additionally, providing -/// the *wrong* keys is even worse - props might be assigned to the wrong components! Keys should be: -/// - Unique -/// - Stable -/// - Predictable -/// -/// Dioxus will log an error in the console if it detects that your iterator does not properly generate keys -#[derive(PartialEq, Props)] -struct NoKeysProps { - data: std::collections::HashMap<u32, String>, -} -fn AntipatternNoKeys(cx: Scope<NoKeysProps>) -> Element { - // WRONG: Make sure to add keys! - cx.render(rsx! { - ul { - cx.props.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" })) - } - }); - // RIGHT: Like this: - cx.render(rsx! { - ul { - cx.props.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" })) - } - }) -} - -/// Antipattern: Deeply nested fragments -/// ------------------------------------ -/// -/// This particular antipattern is not necessarily an antipattern in other frameworks but does has a performance impact -/// in Dioxus apps. Fragments don't mount a physical element to the DOM immediately, so Dioxus must recurse into its -/// children to find a physical DOM node. This process is called "normalization". Other frameworks perform an agressive -/// mutative normalization while Dioxus keeps your VNodes immutable. This means that deeply nested fragments make Dioxus -/// perform unnecessary work. Prefer one or two levels of fragments / nested components until presenting a true DOM element. -/// -/// Only Component and Fragment nodes are susceptible to this issue. Dioxus mitigates this with components by providing -/// an API for registering shared state without the ContextProvider pattern. -fn AntipatternNestedFragments(cx: Scope<()>) -> Element { - // Try to avoid heavily nesting fragments - cx.render(rsx!( - Fragment { - Fragment { - Fragment { - Fragment { - Fragment { - div { "Finally have a real node!" } - } - } - } - } - } - )) -} - -/// Antipattern: Using state after it's been updated -/// ----------------------------------------------- -/// -/// This is an antipattern in other frameworks, but less so in Dioxus. However, it's important to highlight that use_state -/// does *not* work the same way as it does in React. Rust provides explicit guards against mutating shared data - a huge -/// problem in JavaScript land. With Rust and Dioxus, it's nearly impossible to misuse `use_state` - you simply can't -/// accidentally modify the state you've received! -/// -/// However, calling set_state will *not* update the current version of state in the component. This should be easy to -/// recognize from the function signature, but Dioxus will not update the "live" version of state. Calling `set_state` -/// merely places a new value in the queue and schedules the component for a future update. -fn AntipatternRelyingOnSetState(cx: Scope) -> Element { - let (state, set_state) = use_state(&cx, || "Hello world").classic(); - set_state("New state"); - // This will return false! `state` will *still* be "Hello world" - assert!(state == &"New state"); - todo!() -} - -/// Antipattern: Capitalization -/// --------------------------- -/// -/// This antipattern is enforced to retain parity with other frameworks and provide useful IDE feedback, but is less -/// critical than other potential misuses. In short: -/// - Only raw elements may start with a lowercase character -/// - All components must start with an uppercase character -/// -/// i.e.: the following component will be rejected when attempted to be used in the rsx! macro -static antipattern_component: Component = |cx| todo!(); - -/// Antipattern: Misusing hooks -/// --------------------------- -/// -/// This pattern is an unfortunate one where Dioxus replicates the same behavior as other frameworks. Dioxus supports -/// "hooks" - i.e. "memory cells" that allow a value to be stored between renders. This allows other hooks to tap into -/// a component's "memory" without explicitly adding all of its data to a struct definition. In Dioxus, hooks are allocated -/// with a bump arena and then immediately sealed. -/// -/// This means that hooks may not be misused: -/// - Called out of order -/// - Called in a conditional -/// - Called in loops or callbacks -/// -/// For the most part, Rust helps with rule #3 but does not save you from misusing rule #1 or #2. Dioxus will panic -/// if hooks do not downcast to the same data between renders. This is validated by TypeId and, eventually, a custom key. -#[derive(PartialEq, Props)] -struct MisuedHooksProps { - should_render_state: bool, -} -fn AntipatternMisusedHooks(cx: Scope<MisuedHooksProps>) -> Element { - if props.should_render_state { - // do not place a hook in the conditional! - // prefer to move it out of the conditional - let (state, set_state) = use_state(&cx, || "hello world").classic(); - cx.render(rsx!(div { "{state}" })) - } else { - cx.render(rsx!(div { "Not rendering state" })) - } -} - -/// Antipattern: Downcasting refs and panicking -/// ------------------------------------------ -/// -/// Occasionally it's useful to get the ref of an element to handle it directly. Elements support downcasting to -/// Dioxus' virtual element types as well as their true native counterparts. Downcasting to Dioxus' virtual elements -/// will never panic, but downcasting to native elements will fail if on an unsupported platform. We recommend avoiding -/// publishing hooks and components that deeply rely on controlling elements using their native `ref`, preferring to -/// use their Dioxus Virtual Element counterpart instead. -/// This particular code *will panic* due to the unwrap. Try to avoid these types of patterns. -/// --------------------------------- -/// TODO: Get this to compile properly -/// let div_ref = use_node_ref(&cx); -/// -/// cx.render(rsx!{ -/// div { ref: div_ref, class: "custom class", -/// button { "click me to see my parent's class" -/// onclick: move |_| if let Some(div_ref) = div_ref { -/// panic!("Div class is {}", div_ref.to_native::<web_sys::Element>().unwrap().class()) -/// } -/// } -/// } -/// }) -static _example: Component = |cx| todo!(); - -/// Antipattern: publishing components and hooks with all features enabled -/// ---------------------------------------------------------------------- -/// -/// The `dioxus` crate combines a bunch of useful utilities together (like the rsx! and html! macros, hooks, and more). -/// However, when publishing your custom hook or component, we highly advise using only the `core` feature on the dioxus -/// crate. This makes your crate compile faster, makes it more stable, and avoids bringing in incompatible libraries that -/// might make it not compile on unsupported platforms. -/// -/// We don't have a code snippet for this, but just prefer to use this line: -/// dioxus = { version = "*", features = ["core"]} -/// instead of this one: -/// dioxus = { version = "*", features = ["web", "desktop", "full"]} -/// in your Cargo.toml -/// -/// This will only include the `core` dioxus crate which is relatively slim and fast to compile and avoids target-specific -/// libraries. -static __example: Component = |cx| todo!(); - -fn Example(cx: Scope) -> Element { - cx.render(rsx! { - AntipatternNoKeys { data: std::collections::HashMap::new() } - AntipatternNestedFragments {} - }) -} diff --git a/examples/core_reference/basics.rs b/examples/core_reference/basics.rs deleted file mode 100644 index 4c076a3a2..000000000 --- a/examples/core_reference/basics.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Example: The basics of Dioxus -//! ---------------------------- -//! -//! This small example covers some of the basics of Dioxus including -//! - Components -//! - Props -//! - Children -//! - the rsx! macro - -use dioxus::prelude::*; - -pub fn Example(cx: Scope) -> Element { - cx.render(rsx! { - div { - Greeting { - name: "Dioxus" - div { "Dioxus is a fun, fast, and portable UI framework for Rust" } - } - } - }) -} - -#[derive(PartialEq, Props)] -struct GreetingProps<'a> { - name: &'static str, - children: Element<'a>, -} - -pub fn Greeting<'a>(cx: Scope<'a, GreetingProps<'a>>) -> Element { - cx.render(rsx! { - div { - h1 { "Hello, {cx.props.name}!" } - p { "Welcome to the Dioxus framework" } - br {} - &cx.props.children - } - }) -} diff --git a/examples/core_reference/children.rs b/examples/core_reference/children.rs deleted file mode 100644 index 1a1bee316..000000000 --- a/examples/core_reference/children.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Example: Children -//! ----------------- -//! -//! Dioxus supports passing children in from the parent. These children are allocated in the parent and just passed -//! into the child. Components that pass in children may not be safely memoized, though in practice it's rare for a -//! change in a parent to not result in a different set of children. -//! -//! In Dioxus, children can *only be a list*. Unlike React, you cannot pass in functions or arbitrary data. This is -//! partially a limitation of having static types, but is rather intentional to encourage the use of attributes where -//! arbitrary child data might normally be used. Check out the `function driven children` example for how to adopt your -//! React pattern to Dioxus' semantics. -//! -//! Dioxus will let you use the `children` method more than once - and it's semantically *okay* - but you'll likely -//! ruin your page if you try to clone elements in this way. Under the hood, Dioxus shares a "mounted ID" for each node, -//! and mounting the same VNode in two places will overwrite the first mounted ID. This will likely lead to dead elements. -//! -//! In the future, this might become a runtime error, so consider it an error today. - -use dioxus::prelude::*; - -fn Example(cx: Scope) -> Element { - cx.render(rsx! { - div { - Banner { - p { "Some Content1" } - } - Banner { - p { "Some Content2" } - } - } - }) -} - -#[derive(Props)] -struct BannerProps<'a> { - children: Element<'a>, -} - -fn Banner<'a>(cx: Scope<'a, BannerProps<'a>>) -> Element<'a> { - cx.render(rsx! { - div { - h1 { "This is a great banner!" } - div { class: "content", &cx.props.children } - footer { "Wow, what a great footer" } - } - }) -} diff --git a/examples/core_reference/conditional_rendering.rs b/examples/core_reference/conditional_rendering.rs deleted file mode 100644 index 8b7f4baaa..000000000 --- a/examples/core_reference/conditional_rendering.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! Example: Conditional Rendering -//! ------------------------------ -//! -//! This example shows how to hide or show elements using conditional rendering. -//! -//! Oftentimes you might want to display a different UI given some sort of condition. This is called "conditional rendering". -//! In Dioxus, you can perform conditional rendering with optionals or matching. -//! -//! The rsx! and html! macros accepts anything that is `IntoIter<Item = impl IntoVnode>`. Options and Results both implement -//! IntoIter for impl VNode, so you can use option/result for conditional rendering. - -use dioxus::prelude::*; - -// Convert a boolean conditional into a hide/show -#[derive(PartialEq, Props)] -pub struct MyProps { - should_show: bool, -} -pub fn Example0(cx: Scope<MyProps>) -> Element { - cx.render(rsx! { - div { - cx.props.should_show.then(|| rsx!{ - h1 { "showing the title!" } - }) - } - }) -} - -// Convert a boolean conditional into an either/or -// Because rsx! is lazy (produces a closure), we cannot use it in two branch arms. To use it in matching/branching, we -// must render it. -// -// Dioxus will let you render any `LazyNodes` into a `VNode` with `cx.render`. `rsx!` also supports the `in cx` syntax -// which will do essentially the same thing as `cx.render`. -// -// In short: -// `rsx!(cx, ...)` is shorthand for `cx.render(rsx!(...))` -#[derive(PartialEq, Props)] -pub struct MyProps1 { - should_show: bool, -} -pub fn Example1(cx: Scope<MyProps1>) -> Element { - cx.render(rsx! { - div { - // With matching - match props.should_show { - true => cx.render(rsx!(div {"it is true!"})), - false => rsx!(cx, div {"it is false!"}), - }, - - // or with just regular conditions - if props.should_show { - rsx!(cx, div {"it is true!"}) - } else { - rsx!(cx, div {"it is false!"}) - }, - - // or with optional chaining - props.should_show - .then(|| rsx!(cx, div {"it is true!"})) - .unwrap_or_else(|| rsx!(cx, div {"it is false!"})) - } - }) -} - -/// Matching can be expanded - -#[derive(PartialEq)] -pub enum Color { - Green, - Yellow, - Red, -} -#[derive(PartialEq, Props)] -pub struct MyProps2 { - color: Color, -} -pub fn Example2(cx: Scope<MyProps2>) -> Element { - cx.render(rsx! { - div { - match props.color { - Color::Green => rsx!(cx, div {"it is Green!"}), - Color::Yellow => rsx!(cx, div {"it is Yellow!"}), - Color::Red => rsx!(cx, div {"it is Red!"}), - } - } - }) -} - -pub fn Example(cx: Scope<()>) -> Element { - let should_show = use_state(&cx, || false); - let mut color_index = use_state(&cx, || 0); - let color = match *color_index % 2 { - 2 => Color::Green, - 1 => Color::Yellow, - _ => Color::Red, - }; - - cx.render(rsx! { - div { - button { - onclick: move |_| should_show.set(!*should_show), - "click to toggle showing the examples" - } - button { - onclick: move |_| color_index += 1, - "click to for the enxt color" - } - Example0 { should_show: *should_show } - Example1 { should_show: *should_show } - Example2 { color: color } - } - }) -} diff --git a/examples/core_reference/controlled_inputs.rs b/examples/core_reference/controlled_inputs.rs deleted file mode 100644 index 8f3292e22..000000000 --- a/examples/core_reference/controlled_inputs.rs +++ /dev/null @@ -1,37 +0,0 @@ -use dioxus::prelude::*; -fn main() {} - -pub fn Example(cx: Scope) -> Element { - cx.render(rsx! { - div { - - } - }) -} - -// A controlled component: -pub fn ControlledSelect(cx: Scope) -> Element { - let value = use_state(&cx, || String::from("Grapefruit")); - cx.render(rsx! { - select { value: "{value}", onchange: move |evt| value.set(evt.value()), - option { value: "Grapefruit", "Grapefruit"} - option { value: "Lime", "Lime"} - option { value: "Coconut", "Coconut"} - option { value: "Mango", "Mango"} - } - }) -} - -// TODO - how do uncontrolled things work? -pub fn UncontrolledSelect(cx: Scope) -> Element { - let value = use_state(&cx, || String::new()); - - cx.render(rsx! { - select { - option { value: "Grapefruit", "Grapefruit"} - option { value: "Lime", "Lime"} - option { value: "Coconut", "Coconut"} - option { value: "Mango", "Mango"} - } - }) -} diff --git a/examples/core_reference/empty.rs b/examples/core_reference/empty.rs deleted file mode 100644 index e773901e9..000000000 --- a/examples/core_reference/empty.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Example: Null/None Children -//! --------------------------- -//! -//! This is a simple pattern that allows you to return no elements! - -use dioxus::prelude::*; - -pub fn Example(cx: Scope) -> Element { - None -} diff --git a/examples/core_reference/fragments.rs b/examples/core_reference/fragments.rs deleted file mode 100644 index 85523de31..000000000 --- a/examples/core_reference/fragments.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Example: Fragments -//! ------------------ -//! -//! Dioxus can return multiple elements without a container through the use of the VNode called a "Fragment". Fragments do not -//! have a mounted root and are inserted inline with their siblings. There are three ways of creating fragments as outlined -//! in the examples below: -//! - By returning multiple elements in Rsx! -//! - By using the `Fragment` component -//! - By using the fragment() method on the node factory - -use dioxus::prelude::*; - -pub fn Example(cx: Scope) -> Element { - cx.render(rsx! { - App1 {} - App2 {} - App3 {} - }) -} - -// Returning multiple elements with rsx! or html! -pub fn App1(cx: Scope) -> Element { - cx.render(rsx! { - h1 { } - h2 { } - h3 { } - }) -} - -// Using the Fragment component -pub fn App2(cx: Scope) -> Element { - cx.render(rsx! { - Fragment { - div {} - div {} - "asd" - } - }) -} - -// Using the `fragment` method on the NodeFactory -pub fn App3(cx: Scope) -> Element { - cx.render(LazyNodes::new(move |fac| { - fac.fragment_from_iter([ - fac.text(format_args!("A")), - fac.text(format_args!("B")), - fac.text(format_args!("A")), - fac.text(format_args!("B")), - ]) - })) -} diff --git a/examples/core_reference/iterators.rs b/examples/core_reference/iterators.rs deleted file mode 100644 index 12922344d..000000000 --- a/examples/core_reference/iterators.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Example: Iterators -//! ------------------ -//! -//! This example demonstrates how to use iterators with Dioxus. -//! Iterators must be used through the curly braces item in element bodies. -//! While you might be inclined to `.collect::<>` into Html, Dioxus prefers you provide an iterator that -//! resolves to VNodes. It's more efficient and easier to write than having to `collect` everywhere. -//! -//! This also makes it easy to write "pull"-style iterators that don't have a known size. -//! -//! However, when the size of an iterator needs to be known for display purposes, collecting is fine. - -use dioxus::prelude::*; - -pub static Example: Component = |cx| { - let example_data = use_state(&cx, || 0); - - cx.render(rsx! { - h3 {"Selected: {example_data}"} - ul { - (0..10).map(|f| rsx! { - li { - onclick: move |_| example_data.set(f), - "ID: {f}" - ul { - (0..10).map(|k| rsx!{ - li { "Sub iterator: {f}.{k}" } - }) - } - } - }) - } - }) -}; diff --git a/examples/core_reference/listener.rs b/examples/core_reference/listener.rs deleted file mode 100644 index 38e8cf090..000000000 --- a/examples/core_reference/listener.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Example: Listeners -//! ------------------ -//! -//! This example demonstrates the various ways listeners may be used in Dioxus. -//! Listeners may be at most `FnMut` - but not `FnOnce`. -//! Listeners may borrow the lifetime of cx (children and hooks), but not of local (stack) data. - -use dioxus::prelude::*; - -pub fn Example(cx: Scope) -> Element { - cx.render(rsx! { - ButtonList {} - NonUpdatingEvents {} - DisablePropagation {} - }) -} - -/// We can use `set_name` in multiple closures; the closures automatically *copy* the reference to set_name. -pub fn ButtonList(cx: Scope) -> Element { - let name = use_state(&cx, || "...?"); - - cx.render(rsx!( - div { - h1 { "Hello, {name}" } - - ["jack", "jill", "john", "jane"] - .iter() - .map(move |n| rsx!(button { onclick: move |_| name.set(n), "{n}" })) - } - )) -} - -/// This shows how listeners may be without a visible change in the display. -/// Check the console. -pub fn NonUpdatingEvents(cx: Scope) -> Element { - cx.render(rsx! { - div { - button { - onclick: move |_| log::trace!("Did not cause any updates!"), - "Click me to log!" - } - } - }) -} - -pub fn DisablePropagation(cx: Scope) -> Element { - cx.render(rsx! { - div { - onclick: move |_| log::trace!("event propagated to the div!") - button { - onclick: move |evt| log::trace!("Button will allow propagation"), - } - } - }) -}