diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 8832453b9..6d5bdac84 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -33,7 +33,7 @@ jobs: - name: Install CLI run: cargo binstall dioxus-cli -y --force --version 0.7.0-alpha.3 - name: Build - run: cd packages/docsite && dx build --verbose --trace --platform web --fullstack true --features fullstack,production --release --ssg + run: cd packages/docsite && dx build --verbose --trace --web --fullstack --features fullstack,production --release --ssg - name: Generate search index run: target/dx/dioxus_docs_site/release/web/dioxus_docs_site --generate-search-index - name: Copy output diff --git a/Cargo.lock b/Cargo.lock index 2f027a14c..2f25a88d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.12" @@ -85,9 +96,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -115,22 +126,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -144,6 +155,9 @@ name = "arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "arg_enum_proc_macro" @@ -171,7 +185,7 @@ dependencies = [ "enumflags2", "futures-channel", "futures-util", - "rand 0.9.1", + "rand 0.9.2", "raw-window-handle 0.6.2", "serde", "serde_repr", @@ -548,6 +562,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bzip2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff" +dependencies = [ + "libbz2-rs-sys", +] + [[package]] name = "cairo-rs" version = "0.18.5" @@ -607,9 +630,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.29" +version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "jobserver", "libc", @@ -697,11 +720,21 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" -version = "4.5.41" +version = "4.5.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f" dependencies = [ "clap_builder", "clap_derive", @@ -709,9 +742,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.41" +version = "4.5.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65" dependencies = [ "anstream", "anstyle", @@ -841,8 +874,7 @@ dependencies = [ [[package]] name = "const-serialize" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e124eb5cc64de7c7f22d84c0aa373314bc857152b3dfa1c1f75a694346d111" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "const-serialize-macro", "serde", @@ -851,8 +883,7 @@ dependencies = [ [[package]] name = "const-serialize-macro" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16638ae95facb33c7d4df8423ac3b46658ce481bd740f1f723169798f75e85da" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "proc-macro2", "quote", @@ -861,9 +892,9 @@ dependencies = [ [[package]] name = "const-str" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "041fbfcf8e7054df725fb9985297e92422cdc80fcf313665f5ca3d761bb63f4c" +checksum = "451d0640545a0553814b4c646eb549343561618838e9b42495f466131fe3ad49" [[package]] name = "const_format" @@ -885,6 +916,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "convert_case" version = "0.4.0" @@ -1154,6 +1191,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +[[package]] +name = "deflate64" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" + [[package]] name = "deranged" version = "0.4.0" @@ -1174,6 +1217,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -1201,13 +1255,13 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] name = "dioxus" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8449dc3173e919f7721518f418cc73400e3a0cb758b61a132c0da24ffff362" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "dioxus-cli-config", "dioxus-config-macro", @@ -1215,17 +1269,18 @@ dependencies = [ "dioxus-core", "dioxus-core-macro", "dioxus-devtools", - "dioxus-document", + "dioxus-document 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "dioxus-fullstack", "dioxus-history", "dioxus-hooks", "dioxus-html", "dioxus-liveview", - "dioxus-logger", + "dioxus-logger 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "dioxus-router", "dioxus-server", "dioxus-signals", "dioxus-ssr", + "dioxus-stores", "dioxus-web", "dioxus_server_macro", "manganis", @@ -1237,8 +1292,7 @@ dependencies = [ [[package]] name = "dioxus-asset-resolver" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3cf5ba3a7a0ce01e68425a9c51bedd478021ff12315f9961adc4eff0bdd6ca2" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "dioxus-cli-config", "http", @@ -1254,8 +1308,7 @@ dependencies = [ [[package]] name = "dioxus-autofmt" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a95826843e3be911a07e20e1d50a29fc5e73fde8552a40826d84e8c06fc697e" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "dioxus-rsx", "prettyplease", @@ -1269,8 +1322,7 @@ dependencies = [ [[package]] name = "dioxus-cli-config" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a835b354643a8ed31e875c7b5b041b49a58bc473e41bc4c0139171aae081026" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "wasm-bindgen", ] @@ -1278,8 +1330,7 @@ dependencies = [ [[package]] name = "dioxus-config-macro" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b5cc31e1c4adcdd15e53e0ef3caa478af99a8c1f8db5b8712bfd5ca32f6268e" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "proc-macro2", "quote", @@ -1288,17 +1339,15 @@ dependencies = [ [[package]] name = "dioxus-config-macros" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab65feae7ce502a169f8e96d5df96f922ba639616a083ec0bc45ccec1991e9d9" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" [[package]] name = "dioxus-core" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2d47ba93db31ce3b73e9871c7e1aacb2e6e50c50e5e73818858823f8a6a46f" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "const_format", - "dioxus-core-types", + "dioxus-core-types 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "futures-channel", "futures-util", "generational-box", @@ -1316,8 +1365,7 @@ dependencies = [ [[package]] name = "dioxus-core-macro" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "557d0be92d07f795c7129ee620402afe2b09b549cc886d5a7a15dce8d93b0e88" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "convert_case 0.8.0", "dioxus-rsx", @@ -1332,11 +1380,15 @@ version = "0.7.0-alpha.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d374253362f3f661a94670bda4a4742cf5323ffabe1c96392b4e575e9f78490" +[[package]] +name = "dioxus-core-types" +version = "0.7.0-alpha.3" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" + [[package]] name = "dioxus-desktop" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20e35b54a4c7a07fbbec820111216d06b41894bc3af8d58fd4a38ca3812f0360" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "async-trait", "base64", @@ -1346,7 +1398,7 @@ dependencies = [ "dioxus-cli-config", "dioxus-core", "dioxus-devtools", - "dioxus-document", + "dioxus-document 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "dioxus-history", "dioxus-hooks", "dioxus-html", @@ -1359,7 +1411,7 @@ dependencies = [ "global-hotkey", "infer", "jni", - "lazy-js-bundle", + "lazy-js-bundle 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "libc", "muda", "ndk", @@ -1367,9 +1419,8 @@ dependencies = [ "ndk-sys", "objc", "objc_id", - "openssl", "percent-encoding", - "rand 0.9.1", + "rand 0.9.2", "rfd", "rustc-hash 2.1.1", "serde", @@ -1390,8 +1441,7 @@ dependencies = [ [[package]] name = "dioxus-devtools" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4840f7842d45297e6db1f43059feb02a2e398d783088e11e99f9728867a21b15" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "dioxus-cli-config", "dioxus-core", @@ -1409,8 +1459,7 @@ dependencies = [ [[package]] name = "dioxus-devtools-types" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d80438473905fee94c026e574cf743745573415658174e18318ce194f420c2" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "dioxus-core", "serde", @@ -1539,12 +1588,30 @@ checksum = "cda5977c05cfb1e16b5762f1c5df0e20f780f4ad0534e0e3332a5448f7ec7d90" dependencies = [ "dioxus-core", "dioxus-core-macro", - "dioxus-core-types", + "dioxus-core-types 0.7.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", "dioxus-html", "futures-channel", "futures-util", "generational-box", - "lazy-js-bundle", + "lazy-js-bundle 0.7.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "dioxus-document" +version = "0.7.0-alpha.3" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" +dependencies = [ + "dioxus-core", + "dioxus-core-macro", + "dioxus-core-types 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", + "dioxus-html", + "futures-channel", + "futures-util", + "generational-box", + "lazy-js-bundle 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "serde", "serde_json", "tracing", @@ -1564,24 +1631,25 @@ dependencies = [ [[package]] name = "dioxus-fullstack" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6053fc12060779d21aada9502d54af4793ec073ca3411387cde5f049af7c8e1c" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "base64", "bytes", "ciborium", + "dioxus-core", "dioxus-devtools", + "dioxus-document 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "dioxus-fullstack-hooks", "dioxus-fullstack-protocol", "dioxus-history", "dioxus-interpreter-js", - "dioxus-lib", "dioxus-server", "dioxus-web", "dioxus_server_macro", "futures-channel", "futures-util", "generational-box", + "http", "serde", "server_fn", "tracing", @@ -1591,8 +1659,7 @@ dependencies = [ [[package]] name = "dioxus-fullstack-hooks" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51dd88171e1b818fff7c2b0659a742fc9c276a7ad0da9bc7f659d7f9512a53ef" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "dioxus-core", "dioxus-fullstack-protocol", @@ -1606,8 +1673,7 @@ dependencies = [ [[package]] name = "dioxus-fullstack-protocol" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dedf8707be09f66eebe2631e75bad5cea5c6835debc3f752b4636121ca33986" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "base64", "ciborium", @@ -1619,8 +1685,7 @@ dependencies = [ [[package]] name = "dioxus-history" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4e91b8b3546330ddeed65d532c0bf538c99233a54105eea62ddbee900f904c" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "dioxus-core", "tracing", @@ -1629,8 +1694,7 @@ dependencies = [ [[package]] name = "dioxus-hooks" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f0a1be0db90c9ea5ef0614908f070270d3bff013f10d620533ce29f3c0ead5" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "dioxus-core", "dioxus-signals", @@ -1646,13 +1710,12 @@ dependencies = [ [[package]] name = "dioxus-html" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4523bf2cb70fbf8369d56caadf3fc56fe1b4f817faea26e25449714a3e699a85" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "async-trait", "dioxus-core", "dioxus-core-macro", - "dioxus-core-types", + "dioxus-core-types 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "dioxus-hooks", "dioxus-html-internal-macro", "dioxus-rsx", @@ -1661,7 +1724,7 @@ dependencies = [ "futures-channel", "generational-box", "keyboard-types", - "lazy-js-bundle", + "lazy-js-bundle 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "rustversion", "serde", "serde_json", @@ -1672,8 +1735,7 @@ dependencies = [ [[package]] name = "dioxus-html-internal-macro" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c427cff078fea6c2b24362f1a061b26a646b41466b4faf0f9aabcabe3b6f4b" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "convert_case 0.8.0", "proc-macro2", @@ -1684,14 +1746,13 @@ dependencies = [ [[package]] name = "dioxus-interpreter-js" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ec2dad59e5e62c426ebbb924deeb7d4e5d75404f74b55a25540634192c5d30" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "dioxus-core", - "dioxus-core-types", + "dioxus-core-types 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "dioxus-html", "js-sys", - "lazy-js-bundle", + "lazy-js-bundle 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "rustc-hash 2.1.1", "serde", "sledgehammer_bindgen", @@ -1704,8 +1765,7 @@ dependencies = [ [[package]] name = "dioxus-isrg" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334eab221c41fc1d7b0eae3c8fbd2f9659c51f5f690d95eb03a6613c44cb76d1" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "chrono", "http", @@ -1716,34 +1776,16 @@ dependencies = [ "walkdir", ] -[[package]] -name = "dioxus-lib" -version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3340a7eeebc207010d27dcf2b347c748dfd6a8605dc3624019f0c7a06e0d5d8b" -dependencies = [ - "dioxus-config-macro", - "dioxus-core", - "dioxus-core-macro", - "dioxus-document", - "dioxus-history", - "dioxus-hooks", - "dioxus-html", - "dioxus-rsx", - "dioxus-signals", -] - [[package]] name = "dioxus-liveview" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81368daff72f054a71a9e7dd9ae2e9ea9c0b81a59684306cceddc14bf5fe9120" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "axum", "dioxus-cli-config", "dioxus-core", "dioxus-devtools", - "dioxus-document", + "dioxus-document 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "dioxus-history", "dioxus-html", "dioxus-interpreter-js", @@ -1774,6 +1816,18 @@ dependencies = [ "tracing-wasm", ] +[[package]] +name = "dioxus-logger" +version = "0.7.0-alpha.3" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" +dependencies = [ + "console_error_panic_hook", + "dioxus-cli-config", + "tracing", + "tracing-subscriber", + "tracing-wasm", +] + [[package]] name = "dioxus-playground" version = "0.1.0" @@ -1782,9 +1836,9 @@ dependencies = [ "dioxus", "dioxus-autofmt", "dioxus-core", - "dioxus-core-types", + "dioxus-core-types 0.7.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", "dioxus-devtools", - "dioxus-document", + "dioxus-document 0.7.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", "dioxus-html", "dioxus-rsx", "dioxus-rsx-hotreload", @@ -1808,14 +1862,17 @@ dependencies = [ [[package]] name = "dioxus-router" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1407fe9efc73196704e362a07e0c2c2f70158bab255dedeee49a964a8cf32bdb" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "dioxus-cli-config", + "dioxus-core", + "dioxus-core-macro", "dioxus-fullstack-hooks", "dioxus-history", - "dioxus-lib", + "dioxus-hooks", + "dioxus-html", "dioxus-router-macro", + "dioxus-signals", "percent-encoding", "rustversion", "tracing", @@ -1825,8 +1882,7 @@ dependencies = [ [[package]] name = "dioxus-router-macro" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff755fd06ac6e3b5d6bc1f2a43c4fbd9dd610de3fe5dd6e6b6685e7d8c88082" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "base16", "digest", @@ -1840,8 +1896,7 @@ dependencies = [ [[package]] name = "dioxus-rsx" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b133bd25890d95e71cd1d9610006ac381980864ce8557c8afaf7939fef489a" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", @@ -1856,7 +1911,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74f90071c42370cefa83b1fb132245b540b464d4dfd25853041609dce06709dc" dependencies = [ "dioxus-core", - "dioxus-core-types", + "dioxus-core-types 0.7.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", "dioxus-rsx", "internment", "proc-macro2", @@ -1929,8 +1984,7 @@ dependencies = [ [[package]] name = "dioxus-server" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fdf761acb3dc6b8efa8f0e2230c0f871aa90799317b4cec5a1896c3ef974557" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "async-trait", "axum", @@ -1939,14 +1993,18 @@ dependencies = [ "ciborium", "dashmap", "dioxus-cli-config", + "dioxus-core", + "dioxus-core-macro", "dioxus-devtools", + "dioxus-document 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "dioxus-fullstack-hooks", "dioxus-fullstack-protocol", "dioxus-history", + "dioxus-html", "dioxus-interpreter-js", "dioxus-isrg", - "dioxus-lib", "dioxus-router", + "dioxus-signals", "dioxus-ssr", "enumset", "futures-channel", @@ -1975,8 +2033,7 @@ dependencies = [ [[package]] name = "dioxus-signals" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ba3afcb142d1cb95b099c06ab2e6b82d54cf165f2d51801c4393cded0d81d09" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "dioxus-core", "futures-channel", @@ -1991,27 +2048,46 @@ dependencies = [ [[package]] name = "dioxus-ssr" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d642476161fbb123808071ceb36fd7f871130e52495ce855b7b9538647f5a30" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "askama_escape 0.13.0", "dioxus-core", - "dioxus-core-types", + "dioxus-core-types 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "rustc-hash 2.1.1", ] +[[package]] +name = "dioxus-stores" +version = "0.7.0-alpha.3" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" +dependencies = [ + "dioxus-core", + "dioxus-signals", + "dioxus-stores-macro", +] + +[[package]] +name = "dioxus-stores-macro" +version = "0.7.0-alpha.3" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" +dependencies = [ + "convert_case 0.8.0", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "dioxus-web" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d99c0e715e69a4df91b9c976b0f3a2b801070532a7e0a234d60e72796257d7ec" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "async-trait", "dioxus-cli-config", "dioxus-core", - "dioxus-core-types", + "dioxus-core-types 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "dioxus-devtools", - "dioxus-document", + "dioxus-document 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "dioxus-fullstack-protocol", "dioxus-history", "dioxus-html", @@ -2022,7 +2098,7 @@ dependencies = [ "generational-box", "gloo-timers", "js-sys", - "lazy-js-bundle", + "lazy-js-bundle 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "rustc-hash 2.1.1", "serde", "serde-wasm-bindgen", @@ -2052,6 +2128,7 @@ dependencies = [ "dioxus-docs-examples", "dioxus-search", "dioxus-web", + "docsrs-search", "futures", "futures-util", "getrandom 0.2.16", @@ -2078,8 +2155,7 @@ dependencies = [ [[package]] name = "dioxus_server_macro" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf52bbd615bbb143ec6c1595233ce683324e5e6e90045b33b23004f7005ffe1" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "proc-macro2", "quote", @@ -2175,6 +2251,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "docsrs-search" +version = "0.1.0" +dependencies = [ + "dioxus-search", + "reqwest", + "tempdir", + "walkdir", + "zip", +] + [[package]] name = "downcast-rs" version = "1.2.1" @@ -2343,9 +2430,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -2432,6 +2519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", + "libz-rs-sys", "miniz_oxide", ] @@ -2513,6 +2601,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "futf" version = "0.1.5" @@ -2573,9 +2667,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ "fastrand", "futures-core", @@ -2722,8 +2816,7 @@ dependencies = [ [[package]] name = "generational-box" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7614274b2b7e58f11fd586adec596b90582b1e5d4383287ce727d671aac8eac9" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "parking_lot", "tracing", @@ -3032,9 +3125,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -3129,6 +3222,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "html5ever" version = "0.25.2" @@ -3288,9 +3390,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64", "bytes", @@ -3539,6 +3641,15 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "internment" version = "0.8.6" @@ -3570,9 +3681,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" dependencies = [ "bitflags 2.9.1", "cfg-if", @@ -3734,6 +3845,11 @@ version = "0.7.0-alpha.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76fb91c51001c2669299e378aaff2f871c3405fd5eca5c4a95699254e337986d" +[[package]] +name = "lazy-js-bundle" +version = "0.7.0-alpha.3" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" + [[package]] name = "lazy_static" version = "1.5.0" @@ -3770,6 +3886,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + [[package]] name = "libc" version = "0.2.174" @@ -3803,14 +3925,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.2", + "windows-targets 0.53.3", +] + +[[package]] +name = "liblzma" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0791ab7e08ccc8e0ce893f6906eb2703ed8739d8e89b57c0714e71bad09024c8" +dependencies = [ + "liblzma-sys", +] + +[[package]] +name = "liblzma-sys" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736" +dependencies = [ + "cc", + "libc", + "pkg-config", ] [[package]] name = "libredox" -version = "0.1.4" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags 2.9.1", "libc", @@ -3845,6 +3987,15 @@ dependencies = [ "x11", ] +[[package]] +name = "libz-rs-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221" +dependencies = [ + "zlib-rs", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -3960,8 +4111,7 @@ dependencies = [ [[package]] name = "manganis" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ef9f58c311468650835cd59fa4df4781de86ff85584fd0af8f871b83ed412b" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "const-serialize", "manganis-core", @@ -3971,20 +4121,18 @@ dependencies = [ [[package]] name = "manganis-core" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b87d087bfd7381da41fa26028d83b07056d20830ea77f18d376d91cd1bc29d3" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "const-serialize", "dioxus-cli-config", - "dioxus-core-types", + "dioxus-core-types 0.7.0-alpha.3 (git+https://github.com/dioxuslabs/dioxus)", "serde", ] [[package]] name = "manganis-macro" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468f091cf869b5d778780055f8fccc12320daf9d97a74225ba27fba66fc6113" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "dunce", "macro-string", @@ -4229,9 +4377,9 @@ name = "model" version = "0.1.0" dependencies = [ "axum", - "dioxus-document", + "dioxus-document 0.7.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", "dioxus-dx-wire-format", - "dioxus-logger", + "dioxus-logger 0.7.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", "gloo-net", "gloo-utils", "reqwest", @@ -4243,9 +4391,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b89bf91c19bf036347f1ab85a81c560f08c0667c8601bece664d860a600988" +checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" dependencies = [ "crossbeam-channel", "dpi", @@ -4259,7 +4407,7 @@ dependencies = [ "once_cell", "png", "thiserror 2.0.12", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -4658,15 +4806,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" -[[package]] -name = "openssl-src" -version = "300.5.1+3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "735230c832b28c000e3bc117119e6466a663ec73506bc0a9907ea4187508e42a" -dependencies = [ - "cc", -] - [[package]] name = "openssl-sys" version = "0.9.109" @@ -4675,7 +4814,6 @@ checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] @@ -4756,6 +4894,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -5002,7 +5150,7 @@ checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" dependencies = [ "base64", "indexmap", - "quick-xml 0.38.0", + "quick-xml 0.38.1", "serde", "time", ] @@ -5034,9 +5182,9 @@ checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "postcard" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c1de96e20f51df24ca73cafcc4690e044854d803259db27a00a461cb3b9d17a" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" dependencies = [ "cobs", "embedded-io 0.4.0", @@ -5060,6 +5208,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppmd-rust" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c834641d8ad1b348c9ee86dec3b9840d805acd5f24daa5f90c788951a52ff59b" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -5087,9 +5241,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" dependencies = [ "proc-macro2", "syn 2.0.104", @@ -5259,9 +5413,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.38.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b" +checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4" dependencies = [ "memchr", ] @@ -5281,6 +5435,19 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + [[package]] name = "rand" version = "0.7.3" @@ -5308,9 +5475,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -5346,6 +5513,21 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.5.1" @@ -5473,20 +5655,29 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags 2.9.1", ] [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", @@ -5537,6 +5728,15 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "reqwest" version = "0.12.22" @@ -5546,6 +5746,7 @@ dependencies = [ "base64", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2", @@ -5652,7 +5853,7 @@ name = "runner" version = "0.1.0" dependencies = [ "dioxus", - "dioxus-document", + "dioxus-document 0.7.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", "dioxus-playground", "example-projects", ] @@ -5683,9 +5884,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -5736,9 +5937,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "once_cell", "rustls-pki-types", @@ -5955,9 +6156,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa 1.0.15", "memchr", @@ -6026,7 +6227,7 @@ dependencies = [ "axum-client-ip", "dioxus", "dioxus-dx-wire-format", - "dioxus-logger", + "dioxus-logger 0.7.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", "example-projects", "fs_extra", "futures", @@ -6187,9 +6388,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -6285,12 +6486,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.10" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6404,8 +6605,7 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subsecond" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e9884adf2ac9e1f7ee7924be9130e000619fb10ecaa108ba39f20ba773e9ab4" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "js-sys", "libc", @@ -6423,8 +6623,7 @@ dependencies = [ [[package]] name = "subsecond-types" version = "0.7.0-alpha.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cacd233c591f4f27a52b436d38156ce13e666d3da74cf26bf44777a330475f4" +source = "git+https://github.com/dioxuslabs/dioxus#a610aaefc384ea2e01f2b72a27f33964d02b6d30" dependencies = [ "serde", ] @@ -6600,6 +6799,16 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + [[package]] name = "tempfile" version = "3.20.0" @@ -6742,9 +6951,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.46.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", @@ -6758,7 +6967,7 @@ dependencies = [ "socket2", "tokio-macros", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6829,15 +7038,14 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", "futures-sink", "futures-util", - "hashbrown 0.15.4", "pin-project-lite", "tokio", ] @@ -7096,9 +7304,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da75ec677957aa21f6e0b361df0daab972f13a5bee3606de0638fd4ee1c666a" +checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2" dependencies = [ "crossbeam-channel", "dirs", @@ -7132,7 +7340,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.9.1", + "rand 0.9.2", "sha1", "thiserror 2.0.12", "utf-8", @@ -7150,7 +7358,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand 0.9.1", + "rand 0.9.2", "rustls", "sha1", "thiserror 2.0.12", @@ -7467,13 +7675,13 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.44", + "rustix 1.0.8", "scoped-tls", "smallvec", "wayland-sys", @@ -7481,21 +7689,21 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ "bitflags 2.9.1", - "rustix 0.38.44", + "rustix 1.0.8", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-protocols" -version = "0.32.8" +version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -7505,9 +7713,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" dependencies = [ "proc-macro2", "quick-xml 0.37.5", @@ -7516,9 +7724,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" dependencies = [ "dlib", "log", @@ -7824,7 +8032,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -7875,10 +8083,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -8358,6 +8567,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] [[package]] name = "zerotrie" @@ -8372,9 +8595,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -8392,6 +8615,79 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "zip" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aed4ac33e8eb078c89e6cbb1d5c4c7703ec6d299fc3e7c3695af8f8b423468b" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.3.3", + "hmac", + "indexmap", + "liblzma", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" + +[[package]] +name = "zopfli" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zune-core" version = "0.4.12" @@ -8409,9 +8705,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a" +checksum = "fc1f7e205ce79eb2da3cd71c5f55f3589785cb7c79f6a03d1c8d1491bda5d089" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index a0ef90f5c..6a85f442e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "packages/search/search", "packages/search/search-macro", "packages/search/search-shared", + "packages/docsrs-search", # Utilities "packages/notion-to-blog", @@ -149,30 +150,29 @@ codegen-units = 1 # manganis-core = { path = "../dioxus/packages/manganis/manganis-core" } # manganis-macro = { path = "../dioxus/packages/manganis/manganis-macro" } -# dioxus = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-lib = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-core = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-core-macro = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-config-macro = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-router = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-router-macro = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-html = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-html-internal-macro = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-hooks = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-web = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-ssr = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-desktop = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-interpreter-js = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-liveview = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-rsx = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-signals = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-cli-config = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# generational-box = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus_server_macro = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-fullstack = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-autofmt = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-devtools = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# dioxus-devtools-types = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# manganis = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# manganis-core = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } -# manganis-macro = { git = "https://github.com/dioxuslabs/dioxus", rev ="e00ebec8048d8ca934fff918d2d1432bf6ce7640" } +dioxus = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-core = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-core-macro = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-config-macro = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-router = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-router-macro = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-html = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-html-internal-macro = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-hooks = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-web = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-ssr = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-desktop = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-interpreter-js = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-liveview = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-rsx = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-signals = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-cli-config = { git = "https://github.com/dioxuslabs/dioxus" } +generational-box = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus_server_macro = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-fullstack = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-autofmt = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-devtools = { git = "https://github.com/dioxuslabs/dioxus" } +dioxus-devtools-types = { git = "https://github.com/dioxuslabs/dioxus" } +manganis = { git = "https://github.com/dioxuslabs/dioxus" } +manganis-core = { git = "https://github.com/dioxuslabs/dioxus" } +manganis-macro = { git = "https://github.com/dioxuslabs/dioxus" } diff --git a/packages/docs-router/src/docs/router_03.rs b/packages/docs-router/src/docs/router_03.rs new file mode 100644 index 000000000..a44a0e722 --- /dev/null +++ b/packages/docs-router/src/docs/router_03.rs @@ -0,0 +1,8746 @@ +use dioxus::prelude::*; +#[derive( + Clone, + Copy, + dioxus_router::prelude::Routable, + PartialEq, + Eq, + Hash, + Debug, + serde::Serialize, + serde::Deserialize, +)] +pub enum BookRoute { + #[route("/#:section")] + Index { section: IndexSection }, + #[route("/getting_started/#:section")] + GettingStartedIndex { section: GettingStartedIndexSection }, + #[route("/getting_started/desktop#:section")] + GettingStartedDesktop { + section: GettingStartedDesktopSection, + }, + #[route("/getting_started/web#:section")] + GettingStartedWeb { section: GettingStartedWebSection }, + #[route("/getting_started/hot_reload#:section")] + GettingStartedHotReload { + section: GettingStartedHotReloadSection, + }, + #[route("/getting_started/ssr#:section")] + GettingStartedSsr { section: GettingStartedSsrSection }, + #[route("/getting_started/liveview#:section")] + GettingStartedLiveview { + section: GettingStartedLiveviewSection, + }, + #[route("/getting_started/tui#:section")] + GettingStartedTui { section: GettingStartedTuiSection }, + #[route("/getting_started/mobile#:section")] + GettingStartedMobile { + section: GettingStartedMobileSection, + }, + #[route("/describing_ui/#:section")] + DescribingUiIndex { section: DescribingUiIndexSection }, + #[route("/describing_ui/special_attributes#:section")] + DescribingUiSpecialAttributes { + section: DescribingUiSpecialAttributesSection, + }, + #[route("/describing_ui/components#:section")] + DescribingUiComponents { + section: DescribingUiComponentsSection, + }, + #[route("/describing_ui/component_props#:section")] + DescribingUiComponentProps { + section: DescribingUiComponentPropsSection, + }, + #[route("/describing_ui/component_children#:section")] + DescribingUiComponentChildren { + section: DescribingUiComponentChildrenSection, + }, + #[route("/interactivity/#:section")] + InteractivityIndex { section: InteractivityIndexSection }, + #[route("/interactivity/event_handlers#:section")] + InteractivityEventHandlers { + section: InteractivityEventHandlersSection, + }, + #[route("/interactivity/hooks#:section")] + InteractivityHooks { section: InteractivityHooksSection }, + #[route("/interactivity/user_input#:section")] + InteractivityUserInput { + section: InteractivityUserInputSection, + }, + #[route("/interactivity/sharing_state#:section")] + InteractivitySharingState { + section: InteractivitySharingStateSection, + }, + #[route("/interactivity/custom_hooks#:section")] + InteractivityCustomHooks { + section: InteractivityCustomHooksSection, + }, + #[route("/interactivity/dynamic_rendering#:section")] + InteractivityDynamicRendering { + section: InteractivityDynamicRenderingSection, + }, + #[route("/interactivity/router#:section")] + InteractivityRouter { section: InteractivityRouterSection }, + #[route("/async/#:section")] + AsyncIndex { section: AsyncIndexSection }, + #[route("/async/use_future#:section")] + AsyncUseFuture { section: AsyncUseFutureSection }, + #[route("/async/use_coroutine#:section")] + AsyncUseCoroutine { section: AsyncUseCoroutineSection }, + #[route("/async/spawn#:section")] + AsyncSpawn { section: AsyncSpawnSection }, + #[route("/best_practices/#:section")] + BestPracticesIndex { section: BestPracticesIndexSection }, + #[route("/best_practices/error_handling#:section")] + BestPracticesErrorHandling { + section: BestPracticesErrorHandlingSection, + }, + #[route("/best_practices/antipatterns#:section")] + BestPracticesAntipatterns { + section: BestPracticesAntipatternsSection, + }, + #[route("/publishing/#:section")] + PublishingIndex { section: PublishingIndexSection }, + #[route("/publishing/desktop#:section")] + PublishingDesktop { section: PublishingDesktopSection }, + #[route("/publishing/web#:section")] + PublishingWeb { section: PublishingWebSection }, + #[route("/custom_renderer/#:section")] + CustomRendererIndex { section: CustomRendererIndexSection }, + #[route("/roadmap#:section")] + Roadmap { section: RoadmapSection }, + #[route("/contributing#:section")] + Contributing { section: ContributingSection }, +} +impl BookRoute { + /// Get the markdown for a page by its ID + pub const fn page_markdown(id: use_mdbook::mdbook_shared::PageId) -> &'static str { + match id.0 { + 26usize => { + "# Best Practices\n\n## Reusable Components\n\nAs 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.\n\nOrganize your components in modules to keep the codebase easy to navigate!\n\n## Minimize State Dependencies\n\nWhile 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:\n\n* Keep state local to a component if possible\n* When sharing state through props, only pass down the specific data necessary" + } + 6usize => { + "# Liveview\n\nLiveview allows apps to *run* on the server and *render* in the browser. It uses WebSockets to communicate between the server and the browser.\n\nExamples:\n\n* [`Axum Example`](https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/axum.rs)\n* [`Salvo Example`](https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/salvo.rs)\n* [`Warp Example`](https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/warp.rs)\n\n## Support\n\nLiveview is currently limited in capability when compared to the Web platform. Liveview apps run on the server in 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.\n\n## Setup\n\nFor this guide, we're going to show how to use Dioxus Liveview with [Axum](https://docs.rs/axum/latest/axum/).\n\nMake sure you have Rust and Cargo installed, and then create a new project:\n\n````shell\ncargo new --bin demo\ncd app\n````\n\nAdd Dioxus and the liveview renderer with the Axum feature as dependencies:\n\n````shell\ncargo add dioxus\ncargo add dioxus-liveview --features axum\n````\n\nNext, add all the Axum dependencies. This will be different if you're using a different Web Framework\n\n````\ncargo add tokio --features full\ncargo add axum\n````\n\nYour dependencies should look roughly like this:\n\n````toml\n[dependencies]\naxum = \"0.4.5\"\ndioxus = { version = \"*\" }\ndioxus-liveview = { version = \"*\", features = [\"axum\"] }\ntokio = { version = \"1.15.0\", features = [\"full\"] }\n````\n\nNow, set up your Axum app to respond on an endpoint.\n\n````rust@hello_world_liveview.rs\n#[tokio::main]\nasync fn main() {\n let addr: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into();\n\n let view = dioxus_liveview::LiveViewPool::new();\n\n let app = Router::new()\n // The root route contains the glue code to connect to the WebSocket\n .route(\n \"/\",\n get(move || async move {\n Html(format!(\n r#\"\n \n \n Dioxus LiveView with Axum \n
\n {glue}\n \n \"#,\n // Create the glue code to connect to the WebSocket on the \"/ws\" route\n glue = dioxus_liveview::interpreter_glue(&format!(\"ws://{addr}/ws\"))\n ))\n }),\n )\n // The WebSocket route is what Dioxus uses to communicate with the browser\n .route(\n \"/ws\",\n get(move |ws: WebSocketUpgrade| async move {\n ws.on_upgrade(move |socket| async move {\n // When the WebSocket is upgraded, launch the LiveView with the app component\n _ = view.launch(dioxus_liveview::axum_socket(socket), app).await;\n })\n }),\n );\n\n println!(\"Listening on http://{}\", addr);\n\n axum::Server::bind(&addr.to_string().parse().unwrap())\n .serve(app.into_make_service())\n .await\n .unwrap();\n}\n````\n\nAnd then add our app component:\n\n````rust@hello_world_liveview.rs\nfn app(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n \"Hello, world!\"\n }\n })\n}\n````\n\nAnd that's it!" + } + 0usize => { + "# Introduction\n\n![dioxuslogo](/assets/blog/release-03/dioxuslogo_full.png)\n\nDioxus 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.\n\n````rust\nfn app(cx: Scope) -> Element {\n let mut count = use_state(cx, || 0);\n\n cx.render(rsx!(\n h1 { \"High-Five counter: {count}\" }\n button { onclick: move |_| count += 1, \"Up high!\" }\n button { onclick: move |_| count -= 1, \"Down low!\" }\n ))\n}\n````\n\nDioxus is heavily inspired by React. If you know React, getting started with Dioxus will be a breeze.\n\n > \n > 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.\n\n## Features\n\n* Desktop apps running natively (no Electron!) in less than 10 lines of code.\n* Incredibly ergonomic and powerful state management.\n* Comprehensive inline documentation – hover and guides for all HTML elements, listeners, and events.\n* Extremely memory efficient – 0 global allocations for steady-state components.\n* Multi-channel asynchronous scheduler for first-class async support.\n* And more! Read the [full release post](https://dioxuslabs.com/blog/introducing-dioxus/).\n\n### Multiplatform\n\nDioxus 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.\n\nRight now, we have several 1st-party renderers:\n\n* WebSys (for WASM): Great support\n* Tao/Tokio (for Desktop apps): Good support\n* Tao/Tokio (for Mobile apps): Poor support\n* SSR (for generating static markup)\n* TUI/Rink (for terminal-based apps): Experimental\n\n## Stability\n\nDioxus has not reached a stable release yet.\n\nWeb: Since the web is a fairly mature platform, we expect there to be very little API churn for web-based features.\n\nDesktop: APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart.\n\nSSR: We don't expect the SSR API to change drastically in the future." + } + 7usize => { + "# Terminal UI\n\nYou can build a text-based interface that will run in the terminal using Dioxus.\n\n![Hello World screenshot](https://github.com/DioxusLabs/rink/raw/master/examples/example.png)\n\n > \n > 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.\n\n## Support\n\nTUI support is currently quite experimental. But, if you're willing to venture into the realm of the unknown, this guide will get you started.\n\n* It uses flexbox for the layout\n* It only supports a subset of the attributes and elements\n* Regular widgets will not work in the tui render, but the tui renderer has its own widget components that start with a capital letter. See the [widgets example](https://github.com/DioxusLabs/dioxus/blob/master/packages/tui/examples/tui_widgets.rs)\n* 1px is one character line height. Your regular CSS px does not translate\n* If your app panics, your terminal is wrecked. This will be fixed eventually\n\n## Getting Set up\n\nStart by making a new package and adding Dioxus and the TUI renderer as dependancies.\n\n````shell\ncargo new --bin demo\ncd demo\ncargo add dioxus\ncargo add dioxus-tui\n````\n\nThen, edit your `main.rs` with the basic template.\n\n````rust@hello_world_tui.rs\n#![allow(non_snake_case)]\n// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types\nuse dioxus::prelude::*;\n\nfn main() {\n // launch the app in the terminal\n dioxus_tui::launch(App);\n}\n\n// create a component that renders a div with the text \"Hello, world!\"\nfn App(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n \"Hello, world!\"\n }\n })\n}\n\n````\n\nTo run our app:\n\n````shell\ncargo run\n````\n\nPress \"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.\n\n````rust@hello_world_tui_no_ctrl_c.rs\n// todo remove deprecated\n#![allow(non_snake_case, deprecated)]\n\nuse dioxus::events::{KeyCode, KeyboardEvent};\nuse dioxus::prelude::*;\nuse dioxus_tui::TuiContext;\n\nfn main() {\n dioxus_tui::launch_cfg(\n App,\n dioxus_tui::Config::new()\n .without_ctrl_c_quit()\n // Some older terminals only support 16 colors or ANSI colors\n // If your terminal is one of these, change this to BaseColors or ANSI\n .with_rendering_mode(dioxus_tui::RenderingMode::Rgb),\n );\n}\n\nfn App(cx: Scope) -> Element {\n let tui_ctx: TuiContext = cx.consume_context().unwrap();\n\n cx.render(rsx! {\n div {\n width: \"100%\",\n height: \"10px\",\n background_color: \"red\",\n justify_content: \"center\",\n align_items: \"center\",\n onkeydown: move |k: KeyboardEvent| if let KeyCode::Q = k.key_code {\n tui_ctx.quit();\n },\n\n \"Hello world!\"\n }\n })\n}\n\n````" + } + 23usize => { + "# UseFuture\n\n[`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.\n\nFor example, we can make an API request (using [reqwest](https://docs.rs/reqwest/latest/reqwest/index.html)) inside `use_future`:\n\n````rust@use_future.rs\nlet future = use_future(cx, (), |_| async move {\n reqwest::get(\"https://dog.ceo/api/breeds/image/random\")\n .await\n .unwrap()\n .json::()\n .await\n});\n````\n\nThe code inside `use_future` will be submitted to the Dioxus scheduler once the component has rendered.\n\nWe 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.\n\nWe can then render that result:\n\n````rust@use_future.rs\ncx.render(match future.value() {\n Some(Ok(response)) => rsx! {\n button {\n onclick: move |_| future.restart(),\n \"Click to fetch another doggo\"\n }\n div {\n img {\n max_width: \"500px\",\n max_height: \"500px\",\n src: \"{response.image_url}\",\n }\n }\n },\n Some(Err(_)) => rsx! { div { \"Loading dogs failed\" } },\n None => rsx! { div { \"Loading dogs...\" } },\n})\n````\n\n## Restarting the Future\n\nThe `UseFuture` handle provides a `restart` method. It can be used to execute the future again, producing a new value.\n\n## Dependencies\n\nOften, you will need to run the future again every time some value (e.g. a prop) changes. Rather than calling `restart` 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:\n\n````rust@use_future.rs\nlet future = use_future(cx, (breed,), |(breed,)| async move {\n reqwest::get(format!(\"https://dog.ceo/api/breed/{breed}/images/random\"))\n .await\n .unwrap()\n .json::()\n .await\n});\n````" + } + 34usize => { + "# Contributing\n\nDevelopment 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 (but first check if someone hasn't [done it already](https://github.com/DioxusLabs/dioxus/issues)).\n\n[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.\n\n## Improving Docs\n\nIf 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.\n\n## Working on the Ecosystem\n\nPart 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 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.\n\n## Bugs & Features\n\nIf you've fixed [an open issue](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!\n\nAll pull requests (including those made by a team member) must be approved by at least one other team member.\nLarger, more nuanced decisions about design, architecture, breaking changes, trade-offs, etc. are made by team consensus." + } + 27usize => { + "# Error handling\n\nA selling point of Rust for web development is the reliability of always knowing where errors can occur and being forced to handle them\n\nHowever, we haven't talked about error handling at all in this guide! In this chapter, we'll cover some strategies in handling errors to ensure your app never crashes.\n\n## The simplest – returning None\n\nAstute 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:\n\n````rust\nfn App(cx: Scope) -> Element {\n None\n}\n````\n\nThis lets us add in some syntactic sugar for operations we think *shouldn't* fail, but we're still not confident enough to \"unwrap\" on.\n\n > \n > The nature of `Option` might change in the future as the `try` trait gets upgraded.\n\n````rust\nfn App(cx: Scope) -> Element {\n // immediately return \"None\"\n let name = cx.use_hook(|_| Some(\"hi\"))?;\n}\n````\n\n## Early return on result\n\nBecause Rust can't accept both Options and Results with the existing try infrastructure, you'll need to manually handle Results. This can be done by converting them into Options or by explicitly handling them.\n\n````rust\nfn App(cx: Scope) -> Element {\n // Convert Result to Option\n let name = cx.use_hook(|_| \"1.234\").parse().ok()?;\n\n\n // Early return\n let count = cx.use_hook(|_| \"1.234\");\n let val = match count.parse() {\n Ok(val) => val\n Err(err) => return cx.render(rsx!{ \"Parsing failed\" })\n };\n}\n````\n\nNotice that while hooks in Dioxus do not like being called in conditionals or loops, they *are* okay with early returns. Returning an error state early is a completely valid way of handling errors.\n\n## Match results\n\nThe next \"best\" way of handling errors in Dioxus is to match on the error locally. This is the most robust way of handling errors, though it doesn't scale to architectures beyond a single component.\n\nTo do this, we simply have an error state built into our component:\n\n````rust\nlet err = use_state(cx, || None);\n````\n\nWhenever we perform an action that generates an error, we'll set that error state. We can then match on the error in a number of ways (early return, return Element, etc).\n\n````rust\nfn Commandline(cx: Scope) -> Element {\n let error = use_state(cx, || None);\n\n cx.render(match *error {\n Some(error) => rsx!(\n h1 { \"An error occured\" }\n )\n None => rsx!(\n input {\n oninput: move |_| error.set(Some(\"bad thing happened!\")),\n }\n )\n })\n}\n````\n\n## Passing error states through components\n\nIf you're dealing with a handful of components with minimal nesting, you can just pass the error handle into child components.\n\n````rust\nfn Commandline(cx: Scope) -> Element {\n let error = use_state(cx, || None);\n\n if let Some(error) = **error {\n return cx.render(rsx!{ \"An error occured\" });\n }\n\n cx.render(rsx!{\n Child { error: error.clone() }\n Child { error: error.clone() }\n Child { error: error.clone() }\n Child { error: error.clone() }\n })\n}\n````\n\nMuch like before, our child components can manually set the error during their own actions. The advantage to this pattern is that we can easily isolate error states to a few components at a time, making our app more predictable and robust.\n\n## Going global\n\nA strategy for handling cascaded errors in larger apps is through signaling an error using global state. This particular pattern involves creating an \"error\" context, and then setting it wherever relevant. This particular method is not as \"sophisticated\" as React's error boundary, but it is more fitting for Rust.\n\nTo get started, consider using a built-in hook like `use_context` and `use_context_provider` or Fermi. Of course, it's pretty easy to roll your own hook too.\n\nAt the \"top\" of our architecture, we're going to want to explicitly declare a value that could be an error.\n\n````rust\nenum InputError {\n None,\n TooLong,\n TooShort,\n}\n\nstatic INPUT_ERROR: Atom = |_| InputError::None;\n````\n\nThen, in our top level component, we want to explicitly handle the possible error state for this part of the tree.\n\n````rust\nfn TopLevel(cx: Scope) -> Element {\n let error = use_read(cx, INPUT_ERROR);\n\n match error {\n TooLong => return cx.render(rsx!{ \"FAILED: Too long!\" }),\n TooShort => return cx.render(rsx!{ \"FAILED: Too Short!\" }),\n _ => {}\n }\n}\n````\n\nNow, whenever a downstream component has an error in its actions, it can simply just set its own error state:\n\n````rust\nfn Commandline(cx: Scope) -> Element {\n let set_error = use_set(cx, INPUT_ERROR);\n\n cx.render(rsx!{\n input {\n oninput: move |evt| {\n if evt.value.len() > 20 {\n set_error(InputError::TooLong);\n }\n }\n }\n })\n}\n````\n\nThis approach to error handling is best in apps that have \"well defined\" error states. Consider using a crate like `thiserror` or `anyhow` to simplify the generation of the error types.\n\nThis pattern is widely popular in many contexts and is particularly helpful whenever your code generates a non-recoverable error. You can gracefully capture these \"global\" error states without panicking or mucking up state." + } + 12usize => { + "# Component Props\n\nJust 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.\n\n## derive(Props)\n\nComponent 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`.\n\nThere are 2 flavors of Props structs:\n\n* Owned props:\n * Don't have an associated lifetime\n * Implement `PartialEq`, allow for memoization (if the props don't change, Dioxus won't re-render the component)\n* Borrowed props:\n * [Borrow](https://doc.rust-lang.org/beta/rust-by-example/scope/borrow.html) from a parent component\n * Cannot be memoized due to lifetime constraints\n\n### Owned Props\n\nOwned Props are very simple – they don't borrow anything. Example:\n\n````rust@component_owned_props.rs\n// Remember: Owned props must implement `PartialEq`!\n#[derive(PartialEq, Props)]\nstruct LikesProps {\n score: i32,\n}\n\nfn Likes(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n \"This post has \",\n b { \"{cx.props.score}\" },\n \" likes\"\n }\n })\n}\n````\n\nYou can then pass prop values to the component the same way you would pass attributes to an element:\n\n````rust@component_owned_props.rs\nfn App(cx: Scope) -> Element {\n cx.render(rsx! {\n Likes {\n score: 42,\n },\n })\n}\n````\n\n![Screenshot: Likes component](/assets/blog/release-03/component_owned_props_screenshot.png)\n\n### Borrowed Props\n\nOwned props work 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.\n\nRust allows for something more efficient – borrowing the String as a `&str` – this is what Borrowed Props are for!\n\n````rust@component_borrowed_props.rs\n#[derive(Props)]\nstruct TitleCardProps<'a> {\n title: &'a str,\n}\n\nfn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element {\n cx.render(rsx! {\n h1 { \"{cx.props.title}\" }\n })\n}\n````\n\nWe can then use the component like this:\n\n````rust@component_borrowed_props.rs\nfn App(cx: Scope) -> Element {\n let hello = \"Hello Dioxus!\";\n\n cx.render(rsx!(TitleCard { title: hello }))\n}\n````\n\n![Screenshot: TitleCard component](/assets/blog/release-03/component_borrowed_props_screenshot.png)\n\nBorrowed props can be very useful, but they do not allow for memorization so they will *always* rerun when the parent scope is rerendered. Because of this Borrowed Props should be reserved for components that are cheap to rerun or places where cloning data is an issue. Using Borrowed Props everywhere will result in large parts of your app rerunning every interaction.\n\n## Prop Options\n\nThe `#[derive(Props)]` macro has some features that let you customize the behavior of props.\n\n### Optional Props\n\nYou can create optional fields by using the `Option<…>` type for a field:\n\n````rust@component_props_options.rs\n#[derive(Props)]\nstruct OptionalProps<'a> {\n title: &'a str,\n subtitle: Option<&'a str>,\n}\n\nfn Title<'a>(cx: Scope<'a, OptionalProps>) -> Element<'a> {\n cx.render(rsx!(h1{\n \"{cx.props.title}: \",\n cx.props.subtitle.unwrap_or(\"No subtitle provided\"),\n }))\n}\n````\n\nThen, you can choose to either provide them or not:\n\n````rust@component_props_options.rs\nTitle {\ntitle: \"Some Title\",\n},\nTitle {\ntitle: \"Some Title\",\nsubtitle: \"Some Subtitle\",\n},\n// Providing an Option explicitly won't compile though:\n// Title {\n// title: \"Some Title\",\n// subtitle: None,\n// },\n````\n\n### Explicitly Required `Option`s\n\nIf you want to explicitly require an `Option`, and not an optional prop, you can annotate it with `#[props(!optional)]`:\n\n````rust@component_props_options.rs\n#[derive(Props)]\nstruct ExplicitOptionProps<'a> {\n title: &'a str,\n #[props(!optional)]\n subtitle: Option<&'a str>,\n}\n\nfn ExplicitOption<'a>(cx: Scope<'a, ExplicitOptionProps>) -> Element<'a> {\n cx.render(rsx!(h1 {\n \"{cx.props.title}: \",\n cx.props.subtitle.unwrap_or(\"No subtitle provided\"),\n }))\n}\n````\n\nThen, you have to explicitly pass either `Some(\"str\")` or `None`:\n\n````rust@component_props_options.rs\nExplicitOption {\ntitle: \"Some Title\",\nsubtitle: None,\n},\nExplicitOption {\ntitle: \"Some Title\",\nsubtitle: Some(\"Some Title\"),\n},\n// This won't compile:\n// ExplicitOption {\n// title: \"Some Title\",\n// },\n````\n\n### Default Props\n\nYou can use `#[props(default = 42)]` to make a field optional and specify its default value:\n\n````rust@component_props_options.rs\n#[derive(PartialEq, Props)]\nstruct DefaultProps {\n // default to 42 when not provided\n #[props(default = 42)]\n number: i64,\n}\n\nfn DefaultComponent(cx: Scope) -> Element {\n cx.render(rsx!(h1 { \"{cx.props.number}\" }))\n}\n````\n\nThen, similarly to optional props, you don't have to provide it:\n\n````rust@component_props_options.rs\nDefaultComponent {\nnumber: 5,\n},\nDefaultComponent {},\n````\n\n### Automatic Conversion with `.into`\n\nIt 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`:\n\n````rust@component_props_options.rs\n#[derive(PartialEq, Props)]\nstruct IntoProps {\n #[props(into)]\n string: String,\n}\n\nfn IntoComponent(cx: Scope) -> Element {\n cx.render(rsx!(h1 { \"{cx.props.string}\" }))\n}\n````\n\nThen, you can use it so:\n\n````rust@component_props_options.rs\nIntoComponent {\nstring: \"some &str\",\n},\n````\n\n## The inline_props macro\n\nSo 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!\n\n`inline_props` allows you to do just that. Instead of typing the \"full\" version:\n\n````rust\n#[derive(Props, PartialEq)]\nstruct TitleCardProps {\n title: String,\n}\n\nfn TitleCard(cx: Scope) -> Element {\n cx.render(rsx!{\n h1 { \"{cx.props.title}\" }\n })\n}\n````\n\n...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:\n\n````rust\n#[inline_props]\nfn TitleCard(cx: Scope, title: String) -> Element {\n cx.render(rsx!{\n h1 { \"{title}\" }\n })\n}\n````\n\n > \n > 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." + } + 15usize => { + "# Event Handlers\n\nEvent handlers are used to respond to user actions. For example, an event handler could be triggered when the user clicks, scrolls, moves the mouse, or types a character.\n\nEvent handlers are attached to elements. For example, we usually don't care about all the clicks that happen within an app, only those on a particular button.\n\nEvent 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 it listens for is triggered and will be passed that event.\n\nFor example, to handle clicks on an element, we can specify an `onclick` handler:\n\n````rust@event_click.rs\ncx.render(rsx! {\nbutton {\n onclick: move |event| println!(\"Clicked! Event: {event:?}\"),\n \"click me!\"\n}\n})\n````\n\n## The Event object\n\nEvent handlers receive an [`Event`](https://docs.rs/dioxus-core/latest/dioxus_core/struct.Event.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.\n\nIn the example above, this event data was logged to the terminal:\n\n````\nClicked! Event: UiEvent { bubble_state: Cell { value: true }, data: MouseData { coordinates: Coordinates { screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } }\nClicked! Event: UiEvent { bubble_state: Cell { value: true }, data: MouseData { coordinates: Coordinates { screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } }\n````\n\nTo learn what the different event types for HTML provide, read the [events module docs](https://docs.rs/dioxus-html/latest/dioxus_html/events/index.html).\n\n### Event propagation\n\nSome events will trigger first on the element the event originated at upward. For example, a click event on a `button` inside a `div` would first trigger the button's event listener and then the div's event listener.\n\n > \n > For more information about event propigation see [the mdn docs on event bubling](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling)\n\nIf you want to prevent this behavior, you can call `stop_propogation()` on the event:\n\n````rust@event_nested.rs\ncx.render(rsx! {\ndiv {\n onclick: move |_event| {},\n \"outer\",\n button {\n onclick: move |event| {\n // now, outer won't be triggered\n event.stop_propagation();\n },\n \"inner\"\n }\n}\n})\n````\n\n## Prevent Default\n\nSome events have a default behavior. For keyboard events, this might be entering the typed character. For mouse events, this might be selecting some text.\n\nIn 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:\n\n````rust@event_prevent_default.rs\ncx.render(rsx! {\ninput {\n prevent_default: \"oninput\",\n prevent_default: \"onclick\",\n}\n})\n````\n\nAny event handlers will still be called.\n\n > \n > 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 based on the data in the event.\n\n## Handler Props\n\nSometimes, 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:\n\n````rust@event_handler_prop.rs\n#[derive(Props)]\npub struct FancyButtonProps<'a> {\n on_click: EventHandler<'a, MouseEvent>,\n}\n\npub fn FancyButton<'a>(cx: Scope<'a, FancyButtonProps<'a>>) -> Element<'a> {\n cx.render(rsx!(button {\n class: \"fancy-button\",\n onclick: move |evt| cx.props.on_click.call(evt),\n \"click me pls.\"\n }))\n}\n````\n\nThen, you can use it like any other handler:\n\n````rust@event_handler_prop.rs\ncx.render(rsx! {\n FancyButton {\n on_click: move |event| println!(\"Clicked! {event:?}\")\n }\n})\n````\n\n > \n > 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.\n > \n > You can also put custom data in the event, rather than e.g. `MouseData`" + } + 30usize => { + "# Publishing\n\nCongrats! You've made your first Dioxus app that actually does some pretty cool stuff. This app uses your operating system's WebView library, so it's portable to be distributed for other platforms.\n\nIn this section, we'll cover how to bundle your app for macOS, Windows, and Linux.\n\n## Install `cargo-bundle`\n\nThe first thing we'll do is install [`cargo-bundle`](https://github.com/burtonageo/cargo-bundle). This extension to cargo will make it very easy to package our app for the various platforms.\n\nAccording to the `cargo-bundle` github page,\n\n*\"cargo-bundle is a tool used to generate installers or app bundles for GUI executables built with cargo. It can create .app bundles for Mac OS X and iOS, .deb packages for Linux, and .msi installers for Windows (note however that iOS and Windows support is still experimental). Support for creating .rpm packages (for Linux) and .apk packages (for Android) is still pending.\"*\n\nTo install, simply run\n\n`cargo install cargo-bundle`\n\n## Setting up your project\n\nTo get a project setup for bundling, we need to add some flags to our `Cargo.toml` file.\n\n````toml\n[package]\nname = \"example\"\n# ...other fields...\n\n[package.metadata.bundle]\nname = \"DogSearch\"\nidentifier = \"com.dogs.dogsearch\"\nversion = \"1.0.0\"\ncopyright = \"Copyright (c) Jane Doe 2016. All rights reserved.\"\ncategory = \"Developer Tool\"\nshort_description = \"Easily search for Dog photos\"\nlong_description = \"\"\"\nThis app makes it quick and easy to browse photos of dogs from over 200 bree\n\"\"\"\n````\n\n## Building\n\nFollowing cargo-bundle's instructions, we simply `cargo-bundle --release` to produce a final app with all the optimizations and assets builtin.\n\nOnce you've ran `cargo-bundle --release`, your app should be accessible in\n\n`target/release/bundle//`.\n\nFor example, a macOS app would look like this:\n\n![Published App](/assets/static/publish.png)\n\nNice! 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.\n\n > \n > 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." + } + 24usize => { + "# Coroutines\n\nAnother tool in your async toolbox are coroutines. Coroutines are futures that can be manually stopped, started, paused, and resumed.\n\nLike regular futures, code in a coroutine will run until the next `await` point before yielding. This low-level control over asynchronous tasks is quite powerful, allowing for infinitely looping tasks like WebSocket polling, background timers, and other periodic actions.\n\n## use_coroutine\n\nThe `use_coroutine` hook allows you to create a coroutine. Most coroutines we write will be polling loops using async/await.\n\n````rust\nfn app(cx: Scope) -> Element {\n let ws: &UseCoroutine<()> = use_coroutine(cx, |rx| async move {\n // Connect to some sort of service\n let mut conn = connect_to_ws_server().await;\n\n // Wait for data on the service\n while let Some(msg) = conn.next().await {\n // handle messages\n }\n });\n}\n````\n\nFor many services, a simple async loop will handle the majority of use cases.\n\nHowever, if we want to temporarily disable the coroutine, we can \"pause\" it using the `pause` method, and \"resume\" it using the `resume` method:\n\n````rust\nlet sync: &UseCoroutine<()> = use_coroutine(cx, |rx| async move {\n // code for syncing\n});\n\nif sync.is_running() {\n cx.render(rsx!{\n button {\n onclick: move |_| sync.pause(),\n \"Disable syncing\"\n }\n })\n} else {\n cx.render(rsx!{\n button {\n onclick: move |_| sync.resume(),\n \"Enable syncing\"\n }\n })\n}\n````\n\nThis 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.\n\n## Yielding Values\n\nTo yield values from a coroutine, simply bring in a `UseState` handle and set the value whenever your coroutine completes its work.\n\nThe future must be `'static` – so any values captured by the task cannot carry any references to `cx`, such as a `UseState`.\n\nYou can use [to_owned](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned) to create a clone of the hook handle which can be moved into the async closure.\n\n````rust\nlet sync_status = use_state(cx, || Status::Launching);\nlet sync_task = use_coroutine(cx, |rx: UnboundedReceiver| {\n let sync_status = sync_status.to_owned();\n async move {\n loop {\n delay_ms(1000).await;\n sync_status.set(Status::Working);\n }\n }\n})\n````\n\nTo 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.\n\n````rust\nlet sync_status = use_state(cx, || Status::Launching);\nlet load_status = use_state(cx, || Status::Launching);\nlet sync_task = use_coroutine(cx, |rx: UnboundedReceiver| {\n to_owned![sync_status, load_status];\n async move {\n // ...\n }\n})\n````\n\n## Sending Values\n\nYou might've noticed the `use_coroutine` closure takes an argument called `rx`. What is that? Well, a common pattern in complex apps is to handle a bunch of async code at once. With libraries like Redux Toolkit, managing multiple promises at once can be challenging and a common source of bugs.\n\nWith Coroutines, we can centralize our async logic. The `rx` parameter is an Channel that allows code external to the coroutine to send data *into* the coroutine. Instead of looping on an external service, we can loop on the channel itself, processing messages from within our app without needing to spawn a new future. To send data into the coroutine, we would call \"send\" on the handle.\n\n````rust\nenum ProfileUpdate {\n SetUsername(String),\n SetAge(i32)\n}\n\nlet profile = use_coroutine(cx, |mut rx: UnboundedReciver| async move {\n let mut server = connect_to_server().await;\n\n while let Ok(msg) = rx.next().await {\n match msg {\n ProfileUpdate::SetUsername(name) => server.update_username(name).await,\n ProfileUpdate::SetAge(age) => server.update_age(age).await,\n }\n }\n});\n\n\ncx.render(rsx!{\n button {\n onclick: move |_| profile.send(ProfileUpdate::SetUsername(\"Bob\".to_string())),\n \"Update username\"\n }\n})\n````\n\nFor sufficiently complex apps, we could build a bunch of different useful \"services\" that loop on channels to update the app.\n\n````rust\nlet profile = use_coroutine(cx, profile_service);\nlet editor = use_coroutine(cx, editor_service);\nlet sync = use_coroutine(cx, sync_service);\n\nasync fn profile_service(rx: UnboundedReceiver) {\n // do stuff\n}\n\nasync fn sync_service(rx: UnboundedReceiver) {\n // do stuff\n}\n\nasync fn editor_service(rx: UnboundedReceiver) {\n // do stuff\n}\n````\n\nWe can combine coroutines with [Fermi](https://docs.rs/fermi/latest/fermi/index.html) 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.\n\n````rust\nstatic USERNAME: Atom = |_| \"default\".to_string();\n\nfn app(cx: Scope) -> Element {\n let atoms = use_atom_root(cx);\n\n use_coroutine(cx, |rx| sync_service(rx, atoms.clone()));\n\n cx.render(rsx!{\n Banner {}\n })\n}\n\nfn Banner(cx: Scope) -> Element {\n let username = use_read(cx, USERNAME);\n\n cx.render(rsx!{\n h1 { \"Welcome back, {username}\" }\n })\n}\n````\n\nNow, in our sync service, we can structure our state however we want. We only need to update the view values when ready.\n\n````rust\nenum SyncAction {\n SetUsername(String),\n}\n\nasync fn sync_service(mut rx: UnboundedReceiver, atoms: AtomRoot) {\n let username = atoms.write(USERNAME);\n let errors = atoms.write(ERRORS);\n\n while let Ok(msg) = rx.next().await {\n match msg {\n SyncAction::SetUsername(name) => {\n if set_name_on_server(&name).await.is_ok() {\n username.set(name);\n } else {\n errors.make_mut().push(\"SetUsernameFailed\");\n }\n }\n }\n }\n}\n````\n\n## Automatic injection into the Context API\n\nCoroutine handles are automatically injected through the context API. You can use the `use_coroutine_handle` hook with the message type as a generic to fetch a handle.\n\n````rust\nfn Child(cx: Scope) -> Element {\n let sync_task = use_coroutine_handle::(cx);\n\n sync_task.send(SyncAction::SetUsername);\n}\n````" + } + 32usize => { + "# Custom Renderer\n\nDioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer.\n\nGreat news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require processing `DomEdits` and sending `UserEvents`.\n\n## The specifics:\n\nImplementing the renderer is fairly straightforward. The renderer needs to:\n\n1. Handle the stream of edits generated by updates to the virtual DOM\n1. Register listeners and pass events into the virtual DOM's event system\n\nEssentially, your renderer needs to process edits and generate events to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen.\n\nInternally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves.\n\nFor reference, check out the [javascript interpreter](https://github.com/DioxusLabs/dioxus/tree/master/packages/interpreter) or [tui renderer](https://github.com/DioxusLabs/dioxus/tree/master/packages/tui) as a starting point for your custom renderer.\n\n## Templates\n\nDioxus is built around the concept of [Templates](https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html). Templates describe a UI tree known at compile time with dynamic parts filled at runtime. This is useful internally to make skip diffing static nodes, but it is also useful for the renderer to reuse parts of the UI tree. This can be useful for things like a list of items. Each item could contain some static parts and some dynamic parts. The renderer can use the template to create a static part of the UI once, clone it for each element in the list, and then fill in the dynamic parts.\n\n## Mutations\n\nThe `Mutation` type is a serialized enum that represents an operation that should be applied to update the UI. The variants roughly follow this set:\n\n````rust\nenum Mutation {\n AppendChildren,\n AssignId,\n CreatePlaceholder,\n CreateTextNode,\n HydrateText,\n LoadTemplate,\n ReplaceWith,\n ReplacePlaceholder,\n InsertAfter,\n InsertBefore,\n SetAttribute,\n SetText,\n NewEventListener,\n RemoveEventListener,\n Remove,\n PushRoot,\n}\n````\n\nThe Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia.org/wiki/Stack_machine) where the \"push_root\" method pushes a new \"real\" DOM node onto the stack and \"append_child\" and \"replace_with\" both remove nodes from the stack.\n\n### An Example\n\nFor the sake of understanding, let's consider this example – a very simple UI declaration:\n\n````rust\nrsx!( h1 {\"count {x}\"} )\n````\n\nTo get things started, Dioxus must first navigate to the container of this h1 tag. To \"navigate\" here, the internal diffing algorithm generates the DomEdit `PushRoot` where the ID of the root is the container.\n\nWhen the renderer receives this instruction, it pushes the actual Node onto its own stack. The real renderer's stack will look like this:\n\n````rust\ninstructions: [\n PushRoot(Container)\n]\nstack: [\n ContainerNode,\n]\n````\n\nNext, Dioxus will encounter the h1 node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the DomEdit `CreateElement`. When the renderer receives this instruction, it will create an unmounted node and push it into its own stack:\n\n````rust\ninstructions: [\n PushRoot(Container),\n CreateElement(h1),\n]\nstack: [\n ContainerNode,\n h1,\n]\n````\n\nNext, Dioxus sees the text node, and generates the `CreateTextNode` DomEdit:\n\n````rust\ninstructions: [\n PushRoot(Container),\n CreateElement(h1),\n CreateTextNode(\"hello world\")\n]\nstack: [\n ContainerNode,\n h1,\n \"hello world\"\n]\n````\n\nRemember, the text node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the text node to the h1 element. It depends on the situation, but in this case, we use `AppendChildren`. This pops the text node off the stack, leaving the h1 element as the next element in line.\n\n````rust\ninstructions: [\n PushRoot(Container),\n CreateElement(h1),\n CreateTextNode(\"hello world\"),\n AppendChildren(1)\n]\nstack: [\n ContainerNode,\n h1\n]\n````\n\nWe call `AppendChildren` again, popping off the h1 node and attaching it to the parent:\n\n````rust\ninstructions: [\n PushRoot(Container),\n CreateElement(h1),\n CreateTextNode(\"hello world\"),\n AppendChildren(1),\n AppendChildren(1)\n]\nstack: [\n ContainerNode,\n]\n````\n\nFinally, the container is popped since we don't need it anymore.\n\n````rust\ninstructions: [\n PushRoot(Container),\n CreateElement(h1),\n CreateTextNode(\"hello world\"),\n AppendChildren(1),\n AppendChildren(1),\n PopRoot\n]\nstack: []\n````\n\nOver time, our stack looked like this:\n\n````rust\n[]\n[Container]\n[Container, h1]\n[Container, h1, \"hello world\"]\n[Container, h1]\n[Container]\n[]\n````\n\nNotice how our stack is empty once UI has been mounted. Conveniently, this approach completely separates the Virtual DOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits make Dioxus independent of platform specifics.\n\nDioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing.\n\nIt's important to note that there *is* one layer of connectedness between Dioxus and the renderer. Dioxus saves and loads elements (the PushRoot edit) with an ID. Inside the VirtualDOM, this is just tracked as a u64.\n\nWhenever a `CreateElement` edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when PushRoot(ID) is generated. Dioxus reclaims the IDs of elements when removed. To stay in sync with Dioxus you can use a sparse Vec (Vec\\\\>) with possibly unoccupied items. You can use the ids as indexes into the Vec for elements, and grow the Vec when an id does not exist.\n\nThis little demo serves to show exactly how a Renderer would need to process an edit stream to build UIs. A set of serialized DomEditss for various demos is available for you to test your custom renderer against.\n\n## Event loop\n\nLike most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important that your custom renderer can handle those too.\n\nThe code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is:\n\n````rust\npub async fn run(&mut self) -> dioxus_core::error::Result<()> {\n // Push the body element onto the WebsysDom's stack machine\n let mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom());\n websys_dom.stack.push(root_node);\n\n // Rebuild or hydrate the virtualdom\n let mutations = self.internal_dom.rebuild();\n websys_dom.apply_mutations(mutations);\n\n // Wait for updates from the real dom and progress the virtual dom\n loop {\n let user_input_future = websys_dom.wait_for_event();\n let internal_event_future = self.internal_dom.wait_for_work();\n\n match select(user_input_future, internal_event_future).await {\n Either::Left((_, _)) => {\n let mutations = self.internal_dom.work_with_deadline(|| false);\n websys_dom.apply_mutations(mutations);\n },\n Either::Right((event, _)) => websys_dom.handle_event(event),\n }\n\n // render\n }\n}\n````\n\nIt's important that you decode the real events from your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `UserEvent` type. Right now, the VirtualEvent system is modeled almost entirely around the HTML spec, but we are interested in slimming it down.\n\n````rust\nfn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {\n match event.type_().as_str() {\n \"keydown\" => {\n let event: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();\n UserEvent::KeyboardEvent(UserEvent {\n scope_id: None,\n priority: EventPriority::Medium,\n name: \"keydown\",\n // This should be whatever element is focused\n element: Some(ElementId(0)),\n data: Arc::new(KeyboardData{\n char_code: event.char_code(),\n key: event.key(),\n key_code: event.key_code(),\n alt_key: event.alt_key(),\n ctrl_key: event.ctrl_key(),\n meta_key: event.meta_key(),\n shift_key: event.shift_key(),\n location: event.location(),\n repeat: event.repeat(),\n which: event.which(),\n })\n })\n }\n _ => todo!()\n }\n}\n````\n\n## Custom raw elements\n\nIf 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.\n\nThese custom elements are defined as unit structs with trait implementations.\n\nFor example, the `div` element is (approximately!) defined as such:\n\n````rust\nstruct div;\nimpl div {\n /// Some glorious documentation about the class property.\n const TAG_NAME: &'static str = \"div\";\n const NAME_SPACE: Option<&'static str> = None;\n // define the class attribute\n pub fn class<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {\n cx.attr(\"class\", val, None, false)\n }\n // more attributes\n}\n````\n\nYou've probably noticed that many elements in the `rsx!` macros support on-hover documentation. The approach we take to custom elements means that the unit struct is created immediately where the element is used in the macro. When the macro is expanded, the doc comments still apply to the unit struct, giving tons of in-editor feedback, even inside a proc macro.\n\n# Native Core\n\nIf you are creating a renderer in rust, native-core provides some utilities to implement a renderer. It provides an abstraction over DomEdits and handles the layout for you.\n\n## RealDom\n\nThe `RealDom` is a higher-level abstraction over updating the Dom. It updates with `DomEdits` and provides a way to incrementally update the state of nodes based on what attributes change.\n\n### Example\n\nLet's build a toy renderer with borders, size, and text color.\nBefore we start let's take a look at an example element we can render:\n\n````rust\ncx.render(rsx!{\n div{\n color: \"red\",\n p{\n border: \"1px solid black\",\n \"hello world\"\n }\n }\n})\n````\n\nIn this tree, the color depends on the parent's color. The size depends on the children's size, the current text, and the text size. The border depends on only the current node.\n\nIn the following diagram arrows represent dataflow:\n\n[![](https://mermaid.ink/img/pako:eNqdVNFqgzAU_RXJXizUUZPJmIM-jO0LukdhpCbO0JhIGteW0n9fNK1Oa0brfUnu9VxyzzkXjyCVhIIYZFzu0hwr7X2-JcIzsa3W3wqXuZdKoele22oddfa1Y0Tnfn31muvMfqeCDNq3GmvaNROmaKqZFO1DPTRhP8MOd1fTWYNDvzlmQbBMJZcq9JtjNgY1mLVUhBqQPQeojl3wGCw5PsjqnIe-zXqEL8GZ2Kz0gVMPmoeU3ND4IcuiaLGY2zRouuKncv_qGKv3VodpJe0JVU6QCQ5kgqMyWQVr8hbk4hm1PBcmsuwmnrCVH94rP7xN_ucp8sOB_EPSfz9drYVrkpc_AmH8_yTjJueUc-ntpOJkgt2os9tKjcYlt-DLUiD3UsB2KZCLcwjv3Aq33-g2v0M0xXA0MBy5DUdXi-gcJZriuLmAOSioKjAj5ld8rMsJ0DktaAJicyVYbRKQiJPBVSUx438QpqUCcYb5ls4BrrRcHUTaFizqnWGzR8W5evoFI-bJdw)](https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNqdVNFqgzAU_RXJXizUUZPJmIM-jO0LukdhpCbO0JhIGteW0n9fNK1Oa0brfUnu9VxyzzkXjyCVhIIYZFzu0hwr7X2-JcIzsa3W3wqXuZdKoele22oddfa1Y0Tnfn31muvMfqeCDNq3GmvaNROmaKqZFO1DPTRhP8MOd1fTWYNDvzlmQbBMJZcq9JtjNgY1mLVUhBqQPQeojl3wGCw5PsjqnIe-zXqEL8GZ2Kz0gVMPmoeU3ND4IcuiaLGY2zRouuKncv_qGKv3VodpJe0JVU6QCQ5kgqMyWQVr8hbk4hm1PBcmsuwmnrCVH94rP7xN_ucp8sOB_EPSfz9drYVrkpc_AmH8_yTjJueUc-ntpOJkgt2os9tKjcYlt-DLUiD3UsB2KZCLcwjv3Aq33-g2v0M0xXA0MBy5DUdXi-gcJZriuLmAOSioKjAj5ld8rMsJ0DktaAJicyVYbRKQiJPBVSUx438QpqUCcYb5ls4BrrRcHUTaFizqnWGzR8W5evoFI-bJdw)\n\nTo help in building a Dom, native-core provides four traits: State, ChildDepState, ParentDepState, NodeDepState, and a RealDom struct. The ChildDepState, ParentDepState, and NodeDepState provide a way to describe how some information in a node relates to that of its relatives. By providing how to build a single node from its relations, native-core will derive a way to update the state of all nodes for you with `#[derive(State)]`. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom.\n\n````rust\nuse dioxus_native_core::node_ref::*;\nuse dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};\nuse dioxus_native_core_macro::{sorted_str_slice, State};\n\n#[derive(Default, Copy, Clone)]\nstruct Size(f32, f32);\n// Size only depends on the current node and its children, so it implements ChildDepState\nimpl ChildDepState for Size {\n // Size accepts a font size context\n type Ctx = f32;\n // Size depends on the Size part of each child\n type DepState = Self;\n // Size only cares about the width, height, and text parts of the current node\n const NODE_MASK: NodeMask =\n NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!([\"width\", \"height\"]))).with_text();\n fn reduce<'a>(\n &mut self,\n node: NodeView,\n children: impl Iterator,\n ctx: &Self::Ctx,\n ) -> bool\n where\n Self::DepState: 'a,\n {\n let mut width;\n let mut height;\n if let Some(text) = node.text() {\n // if the node has text, use the text to size our object\n width = text.len() as f32 * ctx;\n height = *ctx;\n } else {\n // otherwise, the size is the maximum size of the children\n width = children\n .by_ref()\n .map(|item| item.0)\n .reduce(|accum, item| if accum >= item { accum } else { item })\n .unwrap_or(0.0);\n\n height = children\n .map(|item| item.1)\n .reduce(|accum, item| if accum >= item { accum } else { item })\n .unwrap_or(0.0);\n }\n // if the node contains a width or height attribute it overrides the other size\n for a in node.attributes(){\n match a.name{\n \"width\" => width = a.value.as_float32().unwrap(),\n \"height\" => height = a.value.as_float32().unwrap(),\n // because Size only depends on the width and height, no other attributes will be passed to the member\n _ => panic!()\n }\n }\n // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed\n let changed = (width != self.0) || (height != self.1);\n *self = Self(width, height);\n changed\n }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Default)]\nstruct TextColor {\n r: u8,\n g: u8,\n b: u8,\n}\n// TextColor only depends on the current node and its parent, so it implements ParentDepState\nimpl ParentDepState for TextColor {\n type Ctx = ();\n // TextColor depends on the TextColor part of the parent\n type DepState = Self;\n // TextColor only cares about the color attribute of the current node\n const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&[\"color\"]));\n fn reduce(\n &mut self,\n node: NodeView,\n parent: Option<&Self::DepState>,\n _ctx: &Self::Ctx,\n ) -> bool {\n // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags\n let new = match node.attributes().next().map(|attr| attr.name) {\n // if there is a color tag, translate it\n Some(\"red\") => TextColor { r: 255, g: 0, b: 0 },\n Some(\"green\") => TextColor { r: 0, g: 255, b: 0 },\n Some(\"blue\") => TextColor { r: 0, g: 0, b: 255 },\n Some(_) => panic!(\"unknown color\"),\n // otherwise check if the node has a parent and inherit that color\n None => match parent {\n Some(parent) => *parent,\n None => Self::default(),\n },\n };\n // check if the member has changed\n let changed = new != *self;\n *self = new;\n changed\n }\n}\n\n#[derive(Debug, Clone, PartialEq, Default)]\nstruct Border(bool);\n// TextColor only depends on the current node, so it implements NodeDepState\nimpl NodeDepState<()> for Border {\n type Ctx = ();\n \n // Border does not depended on any other member in the current node\n const NODE_MASK: NodeMask =\n NodeMask::new_with_attrs(AttributeMask::Static(&[\"border\"]));\n fn reduce(&mut self, node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool {\n // check if the node contians a border attribute\n let new = Self(node.attributes().next().map(|a| a.name == \"border\").is_some());\n // check if the member has changed\n let changed = new != *self;\n *self = new;\n changed\n }\n}\n\n// State provides a derive macro, but anotations on the members are needed in the form #[dep_type(dep_member, CtxType)]\n#[derive(State, Default, Clone)]\nstruct ToyState {\n // the color member of it's parent and no context\n #[parent_dep_state(color)]\n color: TextColor,\n // depends on the node, and no context\n #[node_dep_state()]\n border: Border,\n // depends on the layout_width member of children and f32 context (for text size)\n #[child_dep_state(size, f32)]\n size: Size,\n}\n````\n\nNow that we have our state, we can put it to use in our dom. Re can update the dom with update_state to update the structure of the dom (adding, removing, and changing properties of nodes) and then apply_mutations to update the ToyState for each of the nodes that changed.\n\n````rust\nfn main(){\n fn app(cx: Scope) -> Element {\n cx.render(rsx!{\n div{\n color: \"red\",\n \"hello world\"\n }\n })\n }\n let vdom = VirtualDom::new(app);\n let rdom: RealDom = RealDom::new();\n\n let mutations = dom.rebuild();\n // update the structure of the real_dom tree\n let to_update = rdom.apply_mutations(vec![mutations]);\n let mut ctx = AnyMap::new();\n // set the font size to 3.3\n ctx.insert(3.3f32);\n // update the ToyState for nodes in the real_dom tree\n let _to_rerender = rdom.update_state(&dom, to_update, ctx).unwrap();\n\n // we need to run the vdom in a async runtime\n tokio::runtime::Builder::new_current_thread()\n .enable_all()\n .build()?\n .block_on(async {\n loop{\n let wait = vdom.wait_for_work();\n let mutations = vdom.work_with_deadline(|| false);\n let to_update = rdom.apply_mutations(mutations);\n let mut ctx = AnyMap::new();\n ctx.insert(3.3);\n let _to_rerender = rdom.update_state(vdom, to_update, ctx).unwrap();\n\n // render...\n }\n })\n}\n````\n\n## Layout\n\nFor most platforms, the layout of the Elements will stay the same. The layout_attributes module provides a way to apply HTML attributes to a stretch layout style.\n\n## Conclusion\n\nThat should be it! You should have nearly all the knowledge required on how to implement your own renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderers, video game UI, and even augmented reality! If you're interested in contributing to any of these projects, don't be afraid to reach out or join the [community](https://discord.gg/XgGxMSkvUM)." + } + 4usize => { + "# Setting Up Hot Reload\n\n1. Hot reloading allows much faster iteration times inside of rsx calls by interpreting them and streaming the edits.\n1. It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program.\n1. Currently the cli only implements hot reloading for the web renderer.\n\n# Setup\n\nInstall [dioxus-cli](https://github.com/DioxusLabs/cli).\nHot reloading is automatically enabled when using the web renderer on debug builds.\n\n# Usage\n\n1. run:\n\n````\ndioxus serve --hot-reload\n````\n\n2. change some code within a rsx macro\n2. open your localhost in a browser\n2. save and watch the style change without recompiling\n\n# Limitations\n\n1. The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will trigger a full recompile to capture the expression.\n1. Components and Iterators can contain arbitrary rust code and will trigger a full recompile when changed." + } + 17usize => { + "# User Input\n\nInterfaces 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.\n\n## Controlled Inputs\n\nWith 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:\n\n````rust@input_controlled.rs\nfn App(cx: Scope) -> Element {\n let name = use_state(cx, || \"bob\".to_string());\n\n cx.render(rsx! {\n input {\n // we tell the component what to render\n value: \"{name}\",\n // and what to do when the value changes\n oninput: move |evt| name.set(evt.value.clone()),\n }\n })\n}\n````\n\nNotice the flexibility – you can:\n\n* Also display the same contents in another element, and they will be in sync\n* Transform the input every time it is modified (e.g. to make sure it is upper case)\n* Validate the input every time it changes\n* Have custom logic happening when the input changes (e.g. network request for autocompletion)\n* Programmatically change the value (e.g. a \"randomize\" button that fills the input with nonsense)\n\n## Uncontrolled Inputs\n\nAs 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 will be editable anyway (this is built into the browser). This approach can be more performant, but less flexible. For example, it's harder to keep the input in sync with another element.\n\nSince 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`):\n\n````rust@input_uncontrolled.rs\nfn App(cx: Scope) -> Element {\n cx.render(rsx! {\n form {\n onsubmit: move |event| {\n println!(\"Submitted! {event:?}\")\n },\n input { name: \"name\", },\n input { name: \"age\", },\n input { name: \"date\", },\n input { r#type: \"submit\", },\n }\n })\n}\n````\n\n````\nSubmitted! UiEvent { data: FormData { value: \"\", values: {\"age\": \"very old\", \"date\": \"1966\", \"name\": \"Fred\"} } }\n````" + } + 3usize => { + "# Web\n\nBuild 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` and `dioxus-web` crates.\n\nA build of Dioxus for the web will be roughly equivalent to the size of a React build (70kb vs 65kb) but it will load significantly faster because [WebAssembly can be compiled as it is streamed](https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/).\n\nExamples:\n\n* [TodoMVC](https://github.com/DioxusLabs/example-projects/tree/master/todomvc)\n* [ECommerce](https://github.com/DioxusLabs/example-projects/tree/master/ecommerce-site)\n\n[![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/example-projects/blob/master/todomvc)\n\n > \n > Note: Because of the limitations of Wasm, [not every crate will work](https://rustwasm.github.io/docs/book/reference/which-crates-work-with-wasm.html) with your web apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc).\n\n## Support\n\nThe Web is the best-supported target platform for Dioxus.\n\n* Because your app will be compiled to WASM you have access to browser APIs through [wasm-bingen](https://rustwasm.github.io/docs/wasm-bindgen/introduction.html).\n* Dioxus provides hydration to resume apps that are rendered on the server. See the [hydration example](https://github.com/DioxusLabs/dioxus/blob/master/packages/web/examples/hydrate.rs) for more details.\n\n## Tooling\n\nTo develop your Dioxus app for the web, you'll need a tool to build and serve your assets. We recommend using [dioxus-cli](https://github.com/DioxusLabs/cli) which includes a build system, Wasm optimization, a dev server, and support hot reloading:\n\n````shell\ncargo install dioxus-cli\n````\n\nMake sure the `wasm32-unknown-unknown` target for rust is installed:\n\n````shell\nrustup target add wasm32-unknown-unknown\n````\n\n## Creating a Project\n\nCreate a new crate:\n\n````shell\ncargo new --bin demo\ncd demo\n````\n\nAdd Dioxus and the web renderer as dependencies (this will edit your `Cargo.toml`):\n\n````bash\ncargo add dioxus\ncargo add dioxus-web\n````\n\nEdit your `main.rs`:\n\n````rust@hello_world_web.rs\n#![allow(non_snake_case)]\n// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types\nuse dioxus::prelude::*;\n\nfn main() {\n // launch the web app\n dioxus_web::launch(App);\n}\n\n// create a component that renders a div with the text \"Hello, world!\"\nfn App(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n \"Hello, world!\"\n }\n })\n}\n\n````\n\nAnd to serve our app:\n\n````bash\ndioxus serve\n````" + } + 21usize => { + "# Router\n\nIn 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. For a desktop app, these scenes might be different views in your app.\n\nTo unify these platforms, Dioxus provides a first-party solution for scene management called Dioxus Router.\n\n## What is it?\n\nFor an app like the Dioxus landing page (https://dioxuslabs.com), we want to have several different scenes:\n\n* Homepage\n* Blog\n\nEach of these scenes is independent – we don't want to render both the homepage and blog at the same time.\n\nThe Dioxus router makes it easy to create these scenes. To make sure we're using the router, add the `dioxus-router` package to your `Cargo.toml`.\n\n````shell\ncargo add dioxus-router\n````\n\n## Using the router\n\nUnlike other routers in the Rust ecosystem, our router is built declaratively. This makes it possible to compose our app layout simply by arranging components.\n\n````rust\nrsx!{\n // All of our routes will be rendered inside this Router component\n Router {\n // if the current location is \"/home\", render the Home component\n Route { to: \"/home\", Home {} }\n // if the current location is \"/blog\", render the Blog component\n Route { to: \"/blog\", Blog {} }\n }\n}\n````\n\nWhenever we visit this app, we will get either the Home component or the Blog component rendered depending on which route we enter at. If neither of these routes match the current location, then nothing will render.\n\nWe can fix this one of two ways:\n\n* A fallback 404 page\n\n````rust\nrsx!{\n Router {\n Route { to: \"/home\", Home {} }\n Route { to: \"/blog\", Blog {} }\n // if the current location doesn't match any of the above routes, render the NotFound component\n Route { to: \"\", NotFound {} }\n }\n}\n````\n\n* Redirect 404 to home\n\n````rust\nrsx!{\n Router {\n Route { to: \"/home\", Home {} }\n Route { to: \"/blog\", Blog {} }\n // if the current location doesn't match any of the above routes, redirect to \"/home\"\n Redirect { from: \"\", to: \"/home\" }\n }\n}\n````\n\n## Links\n\nFor our app to navigate these routes, we can provide clickable elements called Links. These simply wrap `` elements that, when clicked, navigate the app to the given location.\n\n````rust\nrsx!{\n Link {\n to: \"/home\",\n \"Go home!\"\n }\n}\n````\n\n## More reading\n\nThis page is just a very brief overview of the router. For more information, check out [the router book](https://dioxuslabs.com/router/guide/) or some of [the router examples](https://github.com/DioxusLabs/dioxus/blob/master/examples/router.rs)." + } + 5usize => { + "# Server-Side Rendering\n\nThe Dioxus VirtualDom can be rendered server-side.\n\n[Example: Dioxus DocSite](https://github.com/dioxusLabs/docsite)\n\n## Multithreaded Support\n\nThe 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.\n\nTo solve this, you'll want to spawn a VirtualDom on its own thread and communicate with it via channels.\n\nWhen 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.\n\n## Setup\n\nFor this guide, we're going to show how to use Dioxus SSR with [Axum](https://docs.rs/axum/latest/axum/).\n\nMake sure you have Rust and Cargo installed, and then create a new project:\n\n````shell\ncargo new --bin demo\ncd app\n````\n\nAdd Dioxus and the ssr renderer as dependencies:\n\n````shell\ncargo add dioxus\ncargo add dioxus-ssr\n````\n\nNext, add all the Axum dependencies. This will be different if you're using a different Web Framework\n\n````\ncargo add tokio --features full\ncargo add axum\n````\n\nYour dependencies should look roughly like this:\n\n````toml\n[dependencies]\naxum = \"0.4.5\"\ndioxus = { version = \"*\" }\ndioxus-ssr = { version = \"*\" }\ntokio = { version = \"1.15.0\", features = [\"full\"] }\n````\n\nNow, set up your Axum app to respond on an endpoint.\n\n````rust\nuse axum::{response::Html, routing::get, Router};\nuse dioxus::prelude::*;\n\n#[tokio::main]\nasync fn main() {\n let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));\n println!(\"listening on http://{}\", addr);\n\n axum::Server::bind(&addr)\n .serve(\n Router::new()\n .route(\"/\", get(app_endpoint))\n .into_make_service(),\n )\n .await\n .unwrap();\n}\n````\n\nAnd then add our endpoint. We can either render `rsx!` directly:\n\n````rust\nasync fn app_endpoint() -> Html {\n // render the rsx! macro to HTML\n Html(dioxus_ssr::render_lazy(rsx! {\n div { \"hello world!\" }\n }))\n}\n````\n\nOr we can render VirtualDoms.\n\n````rust\nasync fn app_endpoint() -> Html {\n // create a component that renders a div with the text \"hello world\"\n fn app(cx: Scope) -> Element {\n cx.render(rsx!(div { \"hello world\" }))\n }\n // create a VirtualDom with the app component\n let mut app = VirtualDom::new(app);\n // rebuild the VirtualDom before rendering\n let _ = app.rebuild();\n\n // render the VirtualDom to HTML\n Html(dioxus_ssr::render_vdom(&app))\n}\n````\n\nAnd that's it!\n\n > \n > 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." + } + 2usize => { + "# Desktop Overview\n\nBuild a standalone native desktop app that looks and feels the same across operating systems.\n\nApps built with Dioxus are typically \\<5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory.\n\nExamples:\n\n* [File Explorer](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer)\n* [WiFi Scanner](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner)\n\n[![File ExplorerExample](https://github.com/DioxusLabs/example-projects/raw/master/file-explorer/assets/image.png)](https://github.com/DioxusLabs/example-projects/tree/master/file-explorer)\n\n## Support\n\nThe 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 web renderer-based DOM renderer with WGPU integrations.\n\nDioxus 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.\n\n# Getting started\n\n## Platform-Specific Dependencies\n\nDioxus desktop renders through a web view. Depending on your platform, you might need to install some dependancies.\n\n### Windows\n\nWindows Desktop apps depend on WebView2 – a library that 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:\n\n1. A tiny \"evergreen\" *bootstrapper* that fetches an installer from Microsoft's CDN\n1. A tiny *installer* that fetches Webview2 from Microsoft's CDN\n1. A statically linked version of Webview2 in your final binary for offline users\n\nFor development purposes, use Option 1.\n\n### Linux\n\nWebview Linux apps require WebkitGtk. When distributing, this can be part of your dependency tree in your `.rpm` or `.deb`. However, likely, your users will already have WebkitGtk.\n\n````bash\nsudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev\n````\n\nWhen using Debian/bullseye `libappindicator3-dev` is no longer available but replaced by `libayatana-appindicator3-dev`.\n\n````bash\n# on Debian/bullseye use:\nsudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev\n````\n\nIf 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).\n\n### MacOS\n\nCurrently – 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).\n\n## Creating a Project\n\nCreate a new crate:\n\n````shell\ncargo new --bin demo\ncd demo\n````\n\nAdd Dioxus and the desktop renderer as dependencies (this will edit your `Cargo.toml`):\n\n````shell\ncargo add dioxus\ncargo add dioxus-desktop\n````\n\nEdit your `main.rs`:\n\n````rust@hello_world_desktop.rs\n#![allow(non_snake_case)]\n// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types\nuse dioxus::prelude::*;\n\nfn main() {\n // launch the dioxus app in a webview\n dioxus_desktop::launch(App);\n}\n\n// define a component that renders a div with the text \"Hello, world!\"\nfn App(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n \"Hello, world!\"\n }\n })\n}\n````" + } + 11usize => { + "# Components\n\nJust 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, you should break down the functionality of an app in logical parts called components.\n\nA component is a Rust function, named in UpperCamelCase, that takes a `Scope` parameter and returns an `Element` describing the UI it wants to render. In fact, our `App` function is a component!\n\n````rust@hello_world_desktop.rs\n// define a component that renders a div with the text \"Hello, world!\"\nfn App(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n \"Hello, world!\"\n }\n })\n}\n````\n\n > \n > You'll probably want to add `#![allow(non_snake_case)]` to the top of your crate to avoid warnings about UpperCamelCase component names\n\nA 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:\n\n````rust@components.rs\npub fn About(cx: Scope) -> Element {\n cx.render(rsx!(p {\n b {\"Dioxus Labs\"}\n \" An Open Source project dedicated to making Rust UI wonderful.\"\n }))\n}\n````\n\nThen, you can render your component in another component, similarly to how elements are rendered:\n\n````rust@components.rs\nfn App(cx: Scope) -> Element {\n cx.render(rsx! {\n About {},\n About {},\n })\n}\n````\n\n![Screenshot containing the About component twice](/assets/blog/release-03/screenshot_about_component.png)\n\n > \n > 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!" + } + 14usize => { + "# Interactivity\n\nSo 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." + } + 16usize => { + "# Hooks and Component State\n\nSo far our components have had no state like a normal rust functions. 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 opened a drop-down, and render different things accordingly.\n\nHooks allow us to create state in our components. Hooks are Rust functions that take a reference to `ScopeState` (in a component, you can pass `cx`), and provide you with functionality and state.\n\n## use_state Hook\n\n[`use_state`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_state.html) is one of the simplest hooks.\n\n* You provide a closure that determines the initial value\n* `use_state` gives you the current value, and a way to update it by setting it to something else\n* When the value updates, `use_state` makes the component re-render, and provides you with the new value\n\nFor example, you might have seen the counter example, in which state (a number) is tracked using the `use_state` hook:\n\n````rust@hooks_counter.rs\nfn App(cx: Scope) -> Element {\n // count will be initialized to 0 the first time the component is rendered\n let mut count = use_state(cx, || 0);\n\n cx.render(rsx!(\n h1 { \"High-Five counter: {count}\" }\n button {\n onclick: move |_| {\n // changing the count will cause the component to re-render\n count += 1\n },\n \"Up high!\"\n }\n button {\n onclick: move |_| {\n // changing the count will cause the component to re-render\n count -= 1\n },\n \"Down low!\"\n }\n ))\n}\n````\n\n![Screenshot: counter app](/assets/blog/release-03/counter.png)\n\nEvery 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!\n\n > \n > `use_state` returns your value wrapped in a smart pointer of type [`UseState`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseState.html). This is why you can both read the value and update it, even within an event handler.\n\nYou can use multiple hooks in the same component if you want:\n\n````rust@hooks_counter_two_state.rs\nfn App(cx: Scope) -> Element {\n let mut count_a = use_state(cx, || 0);\n let mut count_b = use_state(cx, || 0);\n\n cx.render(rsx!(\n h1 { \"Counter_a: {count_a}\" }\n button { onclick: move |_| count_a += 1, \"a++\" }\n button { onclick: move |_| count_a -= 1, \"a--\" }\n h1 { \"Counter_b: {count_b}\" }\n button { onclick: move |_| count_b += 1, \"b++\" }\n button { onclick: move |_| count_b -= 1, \"b--\" }\n ))\n}\n````\n\n![Screenshot: app with two counters](/assets/blog/release-03/counter_two_state.png)\n\n## Rules of Hooks\n\nThe 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.\n\nBut 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?\n\n````rust@hooks_counter_two_state.rs\nlet mut count_a = use_state(cx, || 0);\nlet mut count_b = use_state(cx, || 0);\n````\n\nThis is only possible because the two hooks are always called in the same order, so Dioxus knows which is which. Because the order you call hooks matters, you must follow certain rules when using hooks:\n\n1. Hooks may be only used in components or other hooks (we'll get to that later)\n1. On every call to the component function\n 1. The same hooks must be called\n 1. In the same order\n1. Hooks name's should start with `use_` so you don't accidentally confuse them with regular functions\n\nThese rules mean that there are certain things you can't do with hooks:\n\n### No Hooks in Conditionals\n\n````rust@hooks_bad.rs\n// ❌ don't call hooks in conditionals!\n// We must ensure that the same hooks will be called every time\n// But `if` statements only run if the conditional is true!\n// So we might violate rule 2.\nif you_are_happy && you_know_it {\nlet something = use_state(cx, || \"hands\");\nprintln!(\"clap your {something}\")\n}\n\n// ✅ instead, *always* call use_state\n// You can put other stuff in the conditional though\nlet something = use_state(cx, || \"hands\");\nif you_are_happy && you_know_it {\nprintln!(\"clap your {something}\")\n}\n````\n\n### No Hooks in Closures\n\n````rust@hooks_bad.rs\n// ❌ don't call hooks inside closures!\n// We can't guarantee that the closure, if used, will be called in the same order every time\nlet _a = || {\nlet b = use_state(cx, || 0);\nb.get()\n};\n\n// ✅ instead, move hook `b` outside\nlet b = use_state(cx, || 0);\nlet _a = || b.get();\n````\n\n### No Hooks in Loops\n\n````rust@hooks_bad.rs\n// `names` is a Vec<&str>\n\n// ❌ Do not use hooks in loops!\n// In this case, if the length of the Vec changes, we break rule 2\nfor _name in &names {\nlet is_selected = use_state(cx, || false);\nprintln!(\"selected: {is_selected}\");\n}\n\n// ✅ Instead, use a hashmap with use_ref\nlet selection_map = use_ref(cx, HashMap::<&str, bool>::new);\n\nfor name in &names {\nlet is_selected = selection_map.read()[name];\nprintln!(\"selected: {is_selected}\");\n}\n````\n\n## use_ref Hook\n\n`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?\n\nFor 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.\n\nThankfully, 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.\n\nHere's a simple example that keeps a list of events in a `use_ref`. We can acquire write access to the state with `.with_mut()`, and then just `.push` a new value to the state:\n\n````rust@hooks_use_ref.rs\nfn App(cx: Scope) -> Element {\n let list = use_ref(cx, Vec::new);\n\n cx.render(rsx!(\n p { \"Current list: {list.read():?}\" }\n button {\n onclick: move |event| {\n list.with_mut(|list| list.push(event));\n },\n \"Click me!\"\n }\n ))\n}\n````\n\n > \n > 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." + } + 25usize => { + "# Spawning Futures\n\nThe `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`:\n\n````rust@spawn.rs\nlet logged_in = use_state(cx, || false);\n\nlet log_in = move |_| {\n cx.spawn({\n let logged_in = logged_in.to_owned();\n\n async move {\n let resp = reqwest::Client::new()\n .post(\"http://example.com/login\")\n .send()\n .await;\n\n match resp {\n Ok(_data) => {\n println!(\"Login successful!\");\n logged_in.set(true);\n }\n Err(_err) => {\n println!(\n \"Login failed - you need a login server running on localhost:8080.\"\n )\n }\n }\n }\n });\n};\n\ncx.render(rsx! {\n button {\n onclick: log_in,\n \"Login\",\n }\n})\n````\n\n > \n > Note: `spawn` will always spawn a *new* future. You most likely don't want to call it on every render.\n\nCalling `spawn` will give you a `JoinHandle` which lets you cancel or pause the future.\n\n## Spawning Tokio Tasks\n\nSometimes, 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:\n\n````rust@spawn.rs\ncx.spawn(async {\n let _ = tokio::spawn(async {}).await;\n\n let _ = tokio::task::spawn_local(async {\n // some !Send work\n })\n .await;\n});\n````" + } + 29usize => "# Publishing", + 31usize => { + "## Publishing with Github Pages\n\nTo build our app and publish it to Github:\n\n* Make sure GitHub Pages is set up for your repo\n* Build your app with `trunk build --release` (include `--public-url ` to update asset prefixes if using a project site)\n* Move your generated HTML/CSS/JS/Wasm from `dist` into the folder configured for Github Pages\n* Add and commit with git\n* Push to GitHub" + } + 9usize => { + "# Describing the UI\n\nDioxus is a *declarative* framework. This means that instead of telling Dioxus what to do (e.g. to \"create an element\" or \"set the color to red\") we simply *declare* what we want the UI to look like using RSX.\n\nYou have already seen a simple example of RSX syntax in the \"hello world\" application:\n\n````rust@hello_world_desktop.rs\n// define a component that renders a div with the text \"Hello, world!\"\nfn App(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n \"Hello, world!\"\n }\n })\n}\n````\n\nHere, 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.\n\n## RSX Features\n\nRSX 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:\n\n````rust@rsx_overview.rs\ncx.render(rsx!(div {\n// attributes / listeners\n// children\n}))\n````\n\n````html\n
\n````\n\n### Attributes\n\nAttributes (and [listeners](../interactivity/index.md)) modify the behavior or appearance of the element they are attached to. They are specified inside the `{}` brackets, using the `name: value` syntax. You can provide the value as a literal in the RSX:\n\n````rust@rsx_overview.rs\ncx.render(rsx!(a {\nhref: \"https://www.youtube.com/watch?v=dQw4w9WgXcQ\",\nclass: \"primary_button\",\ncolor: \"red\",\n}))\n````\n\n````html\n
\n````\n\n > \n > Note: All attributes defined in `dioxus-html` follow the snake_case naming convention. They transform their `snake_case` names to HTML's `camelCase` attributes.\n\n > \n > Note: Styles can be used directly outside of the `style:` attribute. In the above example, `color: \"red\"` is turned into `style=\"color: red\"`.\n\n#### Custom Attributes\n\nDioxus 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:\n\n````rust@rsx_overview.rs\ncx.render(rsx!(b {\n \"customAttribute\": \"value\",\n}))\n````\n\n````html\n\n\n````\n\n### Interpolation\n\nSimilarly 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:\n\n````rust@rsx_overview.rs\nlet coordinates = (42, 0);\nlet country = \"es\";\ncx.render(rsx!(div {\nclass: \"country-{country}\",\n\"position\": \"{coordinates:?}\",\n// arbitrary expressions are allowed,\n// as long as they don't contain `{}`\ndiv {\n \"{country.to_uppercase()}\"\n},\ndiv {\n \"{7*6}\"\n},\n// {} can be escaped with {{}}\ndiv {\n \"{{}}\"\n},\n}))\n````\n\n````html\n
\n
ES
\n
42
\n
{}
\n
\n````\n\n### Children\n\nTo add children to an element, put them inside the `{}` brackets after all attributes and listeners in the element. They can be other elements, text, or [components](components.md). For example, you could have an `ol` (ordered list) element, containing 3 `li` (list item) elements, each of which contains some text:\n\n````rust@rsx_overview.rs\ncx.render(rsx!(ol {\nli {\"First Item\"}\nli {\"Second Item\"}\nli {\"Third Item\"}\n}))\n````\n\n````html\n
    \n
  1. First Item
  2. \n
  3. Second Item
  4. \n
  5. Third Item
  6. \n
\n````\n\n### Fragments\n\nYou can render multiple elements at the top level of `rsx!` and they will be automatically grouped.\n\n````rust@rsx_overview.rs\ncx.render(rsx!(\np {\"First Item\"},\np {\"Second Item\"},\n))\n````\n\n````html\n

First Item

\n

Second Item

\n````\n\n### Expressions\n\nYou can include arbitrary Rust expressions as children within RSX that implements [IntoDynNode](https://docs.rs/dioxus-core/0.3/dioxus_core/trait.IntoDynNode.html). This is useful for displaying data from an [iterator](https://doc.rust-lang.org/stable/book/ch13-02-iterators.html#processing-a-series-of-items-with-iterators):\n\n````rust@rsx_overview.rs\nlet text = \"Dioxus\";\ncx.render(rsx!(span {\ntext.to_uppercase(),\n// create a list of text from 0 to 9\n(0..10).map(|i| rsx!{ i.to_string() })\n}))\n````\n\n````html\nDIOXUS0123456789\n````\n\n### Loops\n\nIn addition to iterators you can also use for loops directly within RSX:\n\n````rust@rsx_overview.rs\ncx.render(rsx!{\n// use a for loop where the body itself is RSX\ndiv {\n // create a list of text from 0 to 9\n for i in 0..3 {\n // NOTE: the body of the loop is RSX not a rust statement\n div {\n \"{i}\"\n }\n }\n}\n// iterator equivalent\ndiv {\n (0..3).map(|i| rsx!{ div { \"{i}\" } })\n}\n})\n````\n\n````html\n
0
\n
1
\n
2
\n
0
\n
1
\n
2
\n````\n\n### If statements\n\nYou can also use if statements without an else branch within RSX:\n\n````rust@rsx_overview.rs\ncx.render(rsx!{\n// use if statements without an else\nif true {\n rsx!(div { \"true\" })\n}\n})\n````\n\n````html\n
true
\n````" + } + 8usize => { + "# Mobile App\n\nBuild a mobile app with Dioxus!\n\nExample: [Todo App](https://github.com/DioxusLabs/example-projects/blob/master/ios_demo)\n\n## Support\n\nMobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with either the platform's WebView or experimentally through [WGPU](https://github.com/DioxusLabs/blitz). WebView doesn't support animations, transparency, and native widgets.\n\nMobile 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.\n\nThis guide is primarily targeted at iOS apps, however, you can follow it while using the `android` guide in `cargo-mobile`.\n\n## Getting Set up\n\nGetting set up with mobile can be quite challenging. The tooling here isn't great (yet) and might take some hacking around to get things working. macOS M1 is broadly unexplored and might not work for you.\n\nWe're going to be using `cargo-mobile` to build for mobile. First, install it:\n\n````shell\ncargo install --git https://github.com/BrainiumLLC/cargo-mobile\n````\n\nAnd then initialize your app for the right platform. Use the `winit` template for now. Right now, there's no \"Dioxus\" template in cargo-mobile.\n\n````shell\ncargo mobile init\n````\n\nWe're going to completely clear out the `dependencies` it generates for us, swapping out `winit` with `dioxus-mobile`.\n\n````toml\n\n[package]\nname = \"dioxus-ios-demo\"\nversion = \"0.1.0\"\nauthors = []\nedition = \"2018\"\n\n\n# leave the `lib` declaration\n[lib]\ncrate-type = [\"staticlib\", \"cdylib\", \"rlib\"]\n\n\n# leave the binary it generates for us\n[[bin]]\nname = \"dioxus-ios-demo-desktop\"\npath = \"gen/bin/desktop.rs\"\n\n# clear all the dependencies\n[dependencies]\nmobile-entry-point = \"0.1.0\"\ndioxus = { version = \"*\"}\ndioxus-desktop = { version = \"*\" }\nsimple_logger = \"*\"\n````\n\nEdit your `lib.rs`:\n\n````rust\nuse dioxus::prelude::*;\n\nfn main() {\n dioxus_desktop::launch(app);\n}\n\nfn app(cx: Scope) -> Element {\n cx.render(rsx!{\n div {\n \"hello world!\"\n }\n })\n}\n````" + } + 10usize => { + "# Special Attributes\n\nWhile most attributes are simply passed on to the HTML, some have special behaviors.\n\n## The HTML Escape Hatch\n\nIf 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`.\n\nFor 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):\n\n````rust@dangerous_inner_html.rs\n// this should come from a trusted source\nlet contents = \"live dangerously\";\n\ncx.render(rsx! {\ndiv {\n dangerous_inner_html: \"{contents}\",\n}\n})\n````\n\n > \n > 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.\n > \n > 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.\n\n## Boolean Attributes\n\nMost 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.\n\nSo this RSX wouldn't actually render the `hidden` attribute:\n\n````rust@boolean_attribute.rs\ncx.render(rsx! {\ndiv {\n hidden: \"false\",\n \"hello\"\n}\n})\n````\n\n````html\n
hello
\n````\n\nNot all attributes work like this however. *Only the following attributes* have this behavior:\n\n* `allowfullscreen`\n* `allowpaymentrequest`\n* `async`\n* `autofocus`\n* `autoplay`\n* `checked`\n* `controls`\n* `default`\n* `defer`\n* `disabled`\n* `formnovalidate`\n* `hidden`\n* `ismap`\n* `itemscope`\n* `loop`\n* `multiple`\n* `muted`\n* `nomodule`\n* `novalidate`\n* `open`\n* `playsinline`\n* `readonly`\n* `required`\n* `reversed`\n* `selected`\n* `truespeed`\n\nFor any other attributes, a value of `\"false\"` will be sent directly to the DOM." + } + 1usize => { + "# Getting Started\n\nThis section will help you set up your Dioxus project!\n\n## Prerequisites\n\n### An Editor\n\nDioxus 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.\n\n### Rust\n\nHead over to [https://rust-lang.org](http://rust-lang.org) and install the Rust compiler.\n\nWe strongly recommend going through the [official Rust book](https://doc.rust-lang.org/book/ch01-00-getting-started.html) *completely*. However, we hope that a Dioxus app can serve as a great first Rust project. With Dioxus, you'll learn about:\n\n* Error handling\n* Structs, Functions, Enums\n* Closures\n* Macros\n\nWe've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need deep knowledge of async, lifetimes, or smart pointers until you start building complex Dioxus apps.\n\n## Setup Guides\n\nDioxus supports multiple platforms. Choose the platform you want to target below to get platform-specific setup instructions:\n\n* [Web](web.md): runs in the browser through WebAssembly\n* [Server Side Rendering](ssr.md): renders to HTML text on the server\n* [Liveview](liveview.md): runs on the server, renders in the browser using WebSockets\n* [Desktop](desktop.md): runs in a web view on desktop\n* [Mobile](mobile.md): runs in a web view on mobile\n* [Terminal UI](tui.md): renders text-based graphics in the terminal" + } + 18usize => { + "# Sharing State\n\nOften, multiple components need to access the same state. Depending on your needs, there are several ways to implement this.\n\n## Lifting State\n\nOne 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.\n\nSuppose 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).\n\n > \n > 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, maintainable, and performant (this is even more important for larger, complex apps).\n\nWe start with a `Meme` component, responsible for rendering a meme with a given caption:\n\n````rust@meme_editor.rs\n#[inline_props]\nfn Meme<'a>(cx: Scope<'a>, caption: &'a str) -> Element<'a> {\n let container_style = r#\"\n position: relative;\n width: fit-content;\n \"#;\n\n let caption_container_style = r#\"\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 16px 8px;\n \"#;\n\n let caption_style = r\"\n font-size: 32px;\n margin: 0;\n color: white;\n text-align: center;\n \";\n\n cx.render(rsx!(\n div {\n style: \"{container_style}\",\n img {\n src: \"https://i.imgflip.com/2zh47r.jpg\",\n height: \"500px\",\n },\n div {\n style: \"{caption_container_style}\",\n p {\n style: \"{caption_style}\",\n \"{caption}\"\n }\n }\n }\n ))\n}\n````\n\n > \n > 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!\n\nWe 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:\n\n````rust@meme_editor.rs\n#[inline_props]\nfn CaptionEditor<'a>(\n cx: Scope<'a>,\n caption: &'a str,\n on_input: EventHandler<'a, FormEvent>,\n) -> Element<'a> {\n let input_style = r\"\n border: none;\n background: cornflowerblue;\n padding: 8px 16px;\n margin: 0;\n border-radius: 4px;\n color: white;\n \";\n\n cx.render(rsx!(input {\n style: \"{input_style}\",\n value: \"{caption}\",\n oninput: move |event| on_input.call(event),\n }))\n}\n````\n\nFinally, a third component will render the other two as children. It will be responsible for keeping the state and passing down the relevant props.\n\n````rust@meme_editor.rs\nfn MemeEditor(cx: Scope) -> Element {\n let container_style = r\"\n display: flex;\n flex-direction: column;\n gap: 16px;\n margin: 0 auto;\n width: fit-content;\n \";\n\n let caption = use_state(cx, || \"me waiting for my rust code to compile\".to_string());\n\n cx.render(rsx! {\n div {\n style: \"{container_style}\",\n h1 { \"Meme Editor\" },\n Meme {\n caption: caption,\n },\n CaptionEditor {\n caption: caption,\n on_input: move |event: FormEvent| {caption.set(event.value.clone());},\n },\n }\n })\n}\n````\n\n![Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: \"me waiting for a language feature\"](/assets/blog/release-03/meme_editor_screenshot.png)\n\n## Using Context\n\nSometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient.\n\nSuppose 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.\n\n > \n > 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!\n\nNow, 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!\n\nDioxus 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.\n\nFirst, we have to create a struct for our dark mode configuration:\n\n````rust@meme_editor_dark_mode.rs\nstruct DarkMode(bool);\n````\n\nNow, in a top-level component (like `App`), we can provide the `DarkMode` context to all children components:\n\n````rust@meme_editor_dark_mode.rs\nuse_shared_state_provider(cx, || DarkMode(false));\n````\n\nAs a result, any child component of `App` (direct or not), can access the `DarkMode` context.\n\n````rust@meme_editor_dark_mode.rs\nlet dark_mode_context = use_shared_state::(cx);\n````\n\n > \n > `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`.\n\nFor 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):\n\n````rust@meme_editor_dark_mode.rs\npub fn DarkModeToggle(cx: Scope) -> Element {\n let dark_mode = use_shared_state::(cx).unwrap();\n\n let style = if dark_mode.read().0 {\n \"color:white\"\n } else {\n \"\"\n };\n\n cx.render(rsx!(label {\n style: \"{style}\",\n \"Dark Mode\",\n input {\n r#type: \"checkbox\",\n oninput: move |event| {\n let is_enabled = event.value == \"true\";\n dark_mode.write().0 = is_enabled;\n },\n },\n }))\n}\n````" + } + 28usize => { + "# Antipatterns\n\nThis example shows what not to do and provides a reason why a given pattern is considered an \"AntiPattern\". Most anti-patterns are considered wrong for performance or code re-usability reasons.\n\n## Unnecessarily Nested Fragments\n\nFragments 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.\n\nOnly 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.\n\n````rust@anti_patterns.rs\n// ❌ Don't unnecessarily nest fragments\nlet _ = cx.render(rsx!(\n Fragment {\n Fragment {\n Fragment {\n Fragment {\n Fragment {\n div { \"Finally have a real node!\" }\n }\n }\n }\n }\n }\n));\n\n// ✅ Render shallow structures\ncx.render(rsx!(\n div { \"Finally have a real node!\" }\n))\n````\n\n## Incorrect Iterator Keys\n\nAs described in the [dynamic rendering chapter](../interactivity/dynamic_rendering.md#the-key-attribute), 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 will never change.\n\n````rust@anti_patterns.rs\nlet data: &HashMap<_, _> = &cx.props.data;\n\n// ❌ No keys\ncx.render(rsx! {\n ul {\n data.values().map(|value| rsx!(\n li { \"List item: {value}\" }\n ))\n }\n});\n\n// ❌ Using index as keys\ncx.render(rsx! {\n ul {\n cx.props.data.values().enumerate().map(|(index, value)| rsx!(\n li { key: \"{index}\", \"List item: {value}\" }\n ))\n }\n});\n\n// ✅ Using unique IDs as keys:\ncx.render(rsx! {\n ul {\n cx.props.data.iter().map(|(key, value)| rsx!(\n li { key: \"{key}\", \"List item: {value}\" }\n ))\n }\n})\n````\n\n## Avoid Interior Mutability in Props\n\nWhile it is technically acceptable to have a `Mutex` or a `RwLock` in the props, they will be difficult to use.\n\nSuppose 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.\n\n## Avoid Updating State During Render\n\nEvery time you update the state, Dioxus needs to re-render the component – this is inefficient! Consider refactoring your code to avoid this.\n\nAlso, if you unconditionally update the state during render, it will be re-rendered in an infinite loop." + } + 20usize => { + "# Dynamic Rendering\n\nSometimes 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!\n\n## Conditional Rendering\n\nTo render different elements based on a condition, you could use an `if-else` statement:\n\n````rust@conditional_rendering.rs\nif *is_logged_in {\ncx.render(rsx! {\n \"Welcome!\"\n button {\n onclick: move |_| on_log_out.call(()),\n \"Log Out\",\n }\n})\n} else {\ncx.render(rsx! {\n button {\n onclick: move |_| on_log_in.call(()),\n \"Log In\",\n }\n})\n}\n````\n\n > \n > You could also use `match` statements, or any Rust function to conditionally render different things.\n\n### Improving the `if-else` Example\n\nYou may have noticed some repeated code in the `if-else` example above. Repeating code like this is both bad for maintainability and performance. Dioxus will skip diffing static elements like the button, but when switching between multiple `rsx` calls it cannot perform this optimization. For this example either approach is fine, but for components with large parts that are reused between conditionals, it can be more of an issue.\n\nWe can improve this example by splitting up the dynamic parts and inserting them where they are needed.\n\n````rust@conditional_rendering.rs\ncx.render(rsx! {\n// We only render the welcome message if we are logged in\n// You can use if statements in the middle of a render block to conditionally render elements\nif *is_logged_in {\n // Notice the body of this if statment is rsx code, not an expression\n \"Welcome!\"\n}\nbutton {\n // depending on the value of `is_logged_in`, we will call a different event handler\n onclick: move |_| if *is_logged_in {\n on_log_in.call(())\n }\n else{\n on_log_out.call(())\n },\n if *is_logged_in {\n // if we are logged in, the button should say \"Log Out\"\n \"Log Out\"\n } else {\n // if we are not logged in, the button should say \"Log In\"\n \"Log In\"\n }\n}\n})\n````\n\n### Inspecting `Element` props\n\nSince `Element` is a `Option`, components accepting `Element` as a prop can inspect its contents, and render different things based on that. Example:\n\n````rust@component_children_inspect.rs\nfn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {\n match cx.props.children {\n Some(VNode { dynamic_nodes, .. }) => {\n todo!(\"render some stuff\")\n }\n _ => {\n todo!(\"render some other stuff\")\n }\n }\n}\n````\n\nYou 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.\n\n## Rendering Nothing\n\nTo render nothing, you can return `None` from a component. This is useful if you want to conditionally hide something:\n\n````rust@conditional_rendering.rs\nif *is_logged_in {\nreturn None;\n}\n\ncx.render(rsx! {\na {\n \"You must be logged in to comment\"\n}\n})\n````\n\nThis works because the `Element` type is just an alias for `Option`\n\n > \n > 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.\n\n## Rendering Lists\n\nOften, you'll want to render a collection of components. For example, you might want to render a list of all comments on a post.\n\nFor this, Dioxus accepts iterators that produce `Element`s. So we need to:\n\n* Get an iterator over all of our items (e.g., if you have a `Vec` of comments, iterate over it with `iter()`)\n* `.map` the iterator to convert each item into a `LazyNode` using `rsx!(...)`\n * Add a unique `key` attribute to each iterator item\n* Include this iterator in the final RSX (or use it inline)\n\nExample: suppose you have a list of comments you want to render. Then, you can render them like this:\n\n````rust@rendering_lists.rs\nlet comment_field = use_state(cx, String::new);\nlet mut next_id = use_state(cx, || 0);\nlet comments = use_ref(cx, Vec::::new);\n\nlet comments_lock = comments.read();\nlet comments_rendered = comments_lock.iter().map(|comment| {\nrsx!(CommentComponent {\n key: \"{comment.id}\",\n comment: comment.clone(),\n})\n});\n\ncx.render(rsx!(\nform {\n onsubmit: move |_| {\n comments.write().push(Comment {\n content: comment_field.get().clone(),\n id: *next_id.get(),\n });\n next_id += 1;\n\n comment_field.set(String::new());\n },\n input {\n value: \"{comment_field}\",\n oninput: |event| comment_field.set(event.value.clone()),\n }\n input {\n r#type: \"submit\",\n }\n},\ncomments_rendered,\n))\n````\n\n### Inline for loops\n\nBecause of how common it is to render a list of items, Dioxus provides a shorthand for this. Instead of using `.iter, `.map`, and `rsx`, you can use a `for\\` loop with a body of rsx code:\n\n````rust@rendering_lists.rs\nlet comment_field = use_state(cx, String::new);\nlet mut next_id = use_state(cx, || 0);\nlet comments = use_ref(cx, Vec::::new);\n\ncx.render(rsx!(\nform {\n onsubmit: move |_| {\n comments.write().push(Comment {\n content: comment_field.get().clone(),\n id: *next_id.get(),\n });\n next_id += 1;\n\n comment_field.set(String::new());\n },\n input {\n value: \"{comment_field}\",\n oninput: |event| comment_field.set(event.value.clone()),\n }\n input {\n r#type: \"submit\",\n }\n},\nfor comment in &*comments.read() {\n // Notice the body of this for loop is rsx code, not an expression\n CommentComponent {\n key: \"{comment.id}\",\n comment: comment.clone(),\n }\n}\n))\n````\n\n### The key Attribute\n\nEvery time you re-render your list, Dioxus needs to keep track of which items go where to determine what updates need to be made to the UI.\n\nFor 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!\n\nTo 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 matter where you get the key from, as long as it meets the requirements:\n\n* Keys must be unique in a list\n* The same item should always get associated with the same key\n* 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\n\nYou might be tempted to use an item's index in the list as its key. 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.\n\n > \n > 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." + } + 33usize => { + "# Roadmap & Feature-set\n\nThis feature set and roadmap can help you decide if what Dioxus can do today works for you.\n\nIf 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).\n\nGenerally, here's the status of each platform:\n\n* **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.\n\n* **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`.\n\n* **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.\n\n* **Mobile**: Mobile support is very young. You'll be figuring things out as you go and there are not many support crates for peripherals.\n\n* **LiveView**: LiveView support is very young. You'll be figuring things out as you go. Thankfully, none of it is too hard and any work can be upstreamed into Dioxus.\n\n## Features\n\n---\n\n|Feature|Status|Description|\n|-------|------|-----------|\n|Conditional Rendering|✅|if/then to hide/show component|\n|Map, Iterator|✅|map/filter/reduce to produce rsx!|\n|Keyed Components|✅|advanced diffing with keys|\n|Web|✅|renderer for web browser|\n|Desktop (webview)|✅|renderer for desktop|\n|Shared State (Context)|✅|share state through the tree|\n|Hooks|✅|memory cells in components|\n|SSR|✅|render directly to string|\n|Component Children|✅|cx.children() as a list of nodes|\n|Headless components|✅|components that don't return real elements|\n|Fragments|✅|multiple elements without a real root|\n|Manual Props|✅|Manually pass in props with spread syntax|\n|Controlled Inputs|✅|stateful wrappers around inputs|\n|CSS/Inline Styles|✅|syntax for inline styles/attribute groups|\n|Custom elements|✅|Define new element primitives|\n|Suspense|✅|schedule future render from future/promise|\n|Integrated error handling|✅|Gracefully handle errors with ? syntax|\n|NodeRef|✅|gain direct access to nodes|\n|Re-hydration|✅|Pre-render to HTML to speed up first contentful paint|\n|Jank-Free Rendering|✅|Large diffs are segmented across frames for silky-smooth transitions|\n|Effects|✅|Run effects after a component has been committed to render|\n|Portals|🛠|Render nodes outside of the traditional tree structure|\n|Cooperative Scheduling|🛠|Prioritize important events over non-important events|\n|Server Components|🛠|Hybrid components for SPA and Server|\n|Bundle Splitting|👀|Efficiently and asynchronously load the app|\n|Lazy Components|👀|Dynamically load the new components as the page is loaded|\n|1st class global state|✅|redux/recoil/mobx on top of context|\n|Runs natively|✅|runs as a portable binary w/o a runtime (Node)|\n|Subtree Memoization|✅|skip diffing static element subtrees|\n|High-efficiency templates|✅|rsx! calls are translated to templates on the DOM's side|\n|Compile-time correct|✅|Throw errors on invalid template layouts|\n|Heuristic Engine|✅|track component memory usage to minimize future allocations|\n|Fine-grained reactivity|👀|Skip diffing for fine-grain updates|\n\n* ✅ = implemented and working\n* 🛠 = actively being worked on\n* 👀 = not yet implemented or being worked on\n\n## Roadmap\n\nThese Features are planned for the future of Dioxus:\n\n### Core\n\n* [x] Release of Dioxus Core\n* [x] Upgrade documentation to include more theory and be more comprehensive\n* [x] Support for HTML-side templates for lightning-fast dom manipulation\n* [ ] Support for multiple renderers for same virtualdom (subtrees)\n* [ ] Support for ThreadSafe (Send + Sync)\n* [ ] Support for Portals\n\n### SSR\n\n* [x] SSR Support + Hydration\n* [ ] Integrated suspense support for SSR\n\n### Desktop\n\n* [ ] Declarative window management\n* [ ] Templates for building/bundling\n* [ ] Fully native renderer\n* [ ] Access to Canvas/WebGL context natively\n\n### Mobile\n\n* [ ] Mobile standard library\n * [ ] GPS\n * [ ] Camera\n * [ ] filesystem\n * [ ] Biometrics\n * [ ] WiFi\n * [ ] Bluetooth\n * [ ] Notifications\n * [ ] Clipboard\n* [ ] Animations\n* [ ] Native Renderer\n\n### Bundling (CLI)\n\n* [x] Translation from HTML into RSX\n* [x] Dev server\n* [x] Live reload\n* [x] Translation from JSX into RSX\n* [ ] Hot module replacement\n* [ ] Code splitting\n* [ ] Asset macros\n* [ ] Css pipeline\n* [ ] Image pipeline\n\n### Essential hooks\n\n* [x] Router\n* [x] Global state management\n* [ ] Resize observer\n\n## Work in Progress\n\n### Build Tool\n\nWe are currently working on our own build tool called [Dioxus CLI](https://github.com/DioxusLabs/cli) which will support:\n\n* an interactive TUI\n* on-the-fly reconfiguration\n* hot CSS reloading\n* two-way data binding between browser and source code\n* an interpreter for `rsx!`\n* ability to publish to github/netlify/vercel\n* bundling for iOS/Desktop/etc\n\n### Server Component Support\n\nWhile 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.\n\n### Native rendering\n\nWe are currently working on a native renderer for Dioxus using WGPU called [Blitz](https://github.com/DioxusLabs/blitz/). This will allow you to build apps that are rendered natively for iOS, Android, and Desktop." + } + 22usize => { + "# Working with Async\n\nOften, apps need to interact with file systems, network interfaces, hardware, or timers. This chapter provides an overview of using async code in Dioxus.\n\n## The Runtime\n\nBy 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.\n\nDioxus 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`." + } + 13usize => { + "# Component Children\n\nIn 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`:\n\n````rust@component_element_props.rs\n#[derive(Props)]\nstruct ClickableProps<'a> {\n href: &'a str,\n body: Element<'a>,\n}\n\nfn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {\n cx.render(rsx!(\n a {\n href: \"{cx.props.href}\",\n class: \"fancy-button\",\n &cx.props.body\n }\n ))\n}\n````\n\nThen, when rendering the component, you can pass in the output of `cx.render(rsx!(...))`:\n\n````rust@component_element_props.rs\ncx.render(rsx! {\n Clickable {\n href: \"https://www.youtube.com/watch?v=C-M2hs3sXGo\",\n body: cx.render(rsx!(\"How to \" i {\"not\"} \" be seen\")),\n }\n})\n````\n\n > \n > Note: Since `Element<'a>` is a borrowed prop, there will be no memoization.\n\n > \n > Warning: While it may compile, do not include the same `Element` more than once in the RSX. The resulting behavior is unspecified.\n\n## The children field\n\nRather 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:\n\n````rust@component_children.rs\n#[derive(Props)]\nstruct ClickableProps<'a> {\n href: &'a str,\n children: Element<'a>,\n}\n\nfn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {\n cx.render(rsx!(\n a {\n href: \"{cx.props.href}\",\n class: \"fancy-button\",\n &cx.props.children\n }\n ))\n}\n````\n\nThis 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!\n\n````rust@component_children.rs\ncx.render(rsx! {\n Clickable {\n href: \"https://www.youtube.com/watch?v=C-M2hs3sXGo\",\n \"How to \" i {\"not\"} \" be seen\"\n }\n})\n````" + } + 19usize => { + "# Custom Hooks\n\nHooks are a great way to encapsulate business logic. If none of the existing hooks work for your problem, you can write your own.\n\n## Composing Hooks\n\nTo avoid repetition, you can encapsulate business logic based on existing hooks to create a new hook.\n\nFor example, if many components need to access an `AppSettings` struct, you can create a \"shortcut\" hook:\n\n````rust@hooks_composed.rs\nfn use_settings(cx: &ScopeState) -> UseSharedState {\n use_shared_state::(cx).expect(\"App settings not provided\")\n}\n````\n\n## Custom Hook Logic\n\nYou 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!\n\n`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.\n\n > \n > 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)\n\nInside the initialization closure, you will typically make calls to other `cx` methods. For example:\n\n* 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.\n* 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" + } + _ => panic!("Invalid page ID:"), + } + } + pub fn sections(&self) -> &'static [use_mdbook::mdbook_shared::Section] { + &self.page().sections + } + pub fn page(&self) -> &'static use_mdbook::mdbook_shared::Page { + LAZY_BOOK.get_page(self) + } + pub fn page_id(&self) -> use_mdbook::mdbook_shared::PageId { + match self { + BookRoute::Index { .. } => use_mdbook::mdbook_shared::PageId(0usize), + BookRoute::GettingStartedIndex { .. } => use_mdbook::mdbook_shared::PageId(1usize), + BookRoute::GettingStartedDesktop { .. } => use_mdbook::mdbook_shared::PageId(2usize), + BookRoute::GettingStartedWeb { .. } => use_mdbook::mdbook_shared::PageId(3usize), + BookRoute::GettingStartedHotReload { .. } => use_mdbook::mdbook_shared::PageId(4usize), + BookRoute::GettingStartedSsr { .. } => use_mdbook::mdbook_shared::PageId(5usize), + BookRoute::GettingStartedLiveview { .. } => use_mdbook::mdbook_shared::PageId(6usize), + BookRoute::GettingStartedTui { .. } => use_mdbook::mdbook_shared::PageId(7usize), + BookRoute::GettingStartedMobile { .. } => use_mdbook::mdbook_shared::PageId(8usize), + BookRoute::DescribingUiIndex { .. } => use_mdbook::mdbook_shared::PageId(9usize), + BookRoute::DescribingUiSpecialAttributes { .. } => { + use_mdbook::mdbook_shared::PageId(10usize) + } + BookRoute::DescribingUiComponents { .. } => use_mdbook::mdbook_shared::PageId(11usize), + BookRoute::DescribingUiComponentProps { .. } => { + use_mdbook::mdbook_shared::PageId(12usize) + } + BookRoute::DescribingUiComponentChildren { .. } => { + use_mdbook::mdbook_shared::PageId(13usize) + } + BookRoute::InteractivityIndex { .. } => use_mdbook::mdbook_shared::PageId(14usize), + BookRoute::InteractivityEventHandlers { .. } => { + use_mdbook::mdbook_shared::PageId(15usize) + } + BookRoute::InteractivityHooks { .. } => use_mdbook::mdbook_shared::PageId(16usize), + BookRoute::InteractivityUserInput { .. } => use_mdbook::mdbook_shared::PageId(17usize), + BookRoute::InteractivitySharingState { .. } => { + use_mdbook::mdbook_shared::PageId(18usize) + } + BookRoute::InteractivityCustomHooks { .. } => { + use_mdbook::mdbook_shared::PageId(19usize) + } + BookRoute::InteractivityDynamicRendering { .. } => { + use_mdbook::mdbook_shared::PageId(20usize) + } + BookRoute::InteractivityRouter { .. } => use_mdbook::mdbook_shared::PageId(21usize), + BookRoute::AsyncIndex { .. } => use_mdbook::mdbook_shared::PageId(22usize), + BookRoute::AsyncUseFuture { .. } => use_mdbook::mdbook_shared::PageId(23usize), + BookRoute::AsyncUseCoroutine { .. } => use_mdbook::mdbook_shared::PageId(24usize), + BookRoute::AsyncSpawn { .. } => use_mdbook::mdbook_shared::PageId(25usize), + BookRoute::BestPracticesIndex { .. } => use_mdbook::mdbook_shared::PageId(26usize), + BookRoute::BestPracticesErrorHandling { .. } => { + use_mdbook::mdbook_shared::PageId(27usize) + } + BookRoute::BestPracticesAntipatterns { .. } => { + use_mdbook::mdbook_shared::PageId(28usize) + } + BookRoute::PublishingIndex { .. } => use_mdbook::mdbook_shared::PageId(29usize), + BookRoute::PublishingDesktop { .. } => use_mdbook::mdbook_shared::PageId(30usize), + BookRoute::PublishingWeb { .. } => use_mdbook::mdbook_shared::PageId(31usize), + BookRoute::CustomRendererIndex { .. } => use_mdbook::mdbook_shared::PageId(32usize), + BookRoute::Roadmap { .. } => use_mdbook::mdbook_shared::PageId(33usize), + BookRoute::Contributing { .. } => use_mdbook::mdbook_shared::PageId(34usize), + } + } +} +impl Default for BookRoute { + fn default() -> Self { + BookRoute::Index { + section: IndexSection::Empty, + } + } +} +pub static LAZY_BOOK: use_mdbook::Lazy> = + use_mdbook::Lazy::new(|| { + let mut page_id_mapping = ::std::collections::HashMap::new(); + let mut pages = Vec::new(); + pages.push((0usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Introduction".to_string(), + url: BookRoute::Index { + section: IndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Introduction".to_string(), + id: "introduction".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Features".to_string(), + id: "features".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Multiplatform".to_string(), + id: "multiplatform".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Stability".to_string(), + id: "stability".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(0usize), + } + })); + page_id_mapping.insert( + BookRoute::Index { + section: IndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(0usize), + ); + pages.push((1usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Getting Started".to_string(), + url: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Getting Started".to_string(), + id: "getting-started".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Prerequisites".to_string(), + id: "prerequisites".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "An Editor".to_string(), + id: "an-editor".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Rust".to_string(), + id: "rust".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup Guides".to_string(), + id: "setup-guides".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(1usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(1usize), + ); + pages.push((2usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Desktop".to_string(), + url: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Desktop Overview".to_string(), + id: "desktop-overview".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Support".to_string(), + id: "support".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Getting started".to_string(), + id: "getting-started".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Platform-Specific Dependencies".to_string(), + id: "platform-specific-dependencies".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Windows".to_string(), + id: "windows".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Linux".to_string(), + id: "linux".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "MacOS".to_string(), + id: "macos".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating a Project".to_string(), + id: "creating-a-project".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(2usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(2usize), + ); + pages.push((3usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Web".to_string(), + url: BookRoute::GettingStartedWeb { + section: GettingStartedWebSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Web".to_string(), + id: "web".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Support".to_string(), + id: "support".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Tooling".to_string(), + id: "tooling".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating a Project".to_string(), + id: "creating-a-project".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(3usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedWeb { + section: GettingStartedWebSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(3usize), + ); + pages.push((4usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Hot Reload".to_string(), + url: BookRoute::GettingStartedHotReload { + section: GettingStartedHotReloadSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Setting Up Hot Reload".to_string(), + id: "setting-up-hot-reload".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup".to_string(), + id: "setup".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Usage".to_string(), + id: "usage".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Limitations".to_string(), + id: "limitations".to_string(), + level: 1usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(4usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedHotReload { + section: GettingStartedHotReloadSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(4usize), + ); + pages.push((5usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Server-Side Rendering".to_string(), + url: BookRoute::GettingStartedSsr { + section: GettingStartedSsrSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Server-Side Rendering".to_string(), + id: "server-side-rendering".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Multithreaded Support".to_string(), + id: "multithreaded-support".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup".to_string(), + id: "setup".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(5usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedSsr { + section: GettingStartedSsrSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(5usize), + ); + pages.push((6usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Liveview".to_string(), + url: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Liveview".to_string(), + id: "liveview".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Support".to_string(), + id: "support".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup".to_string(), + id: "setup".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(6usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(6usize), + ); + pages.push((7usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Terminal UI".to_string(), + url: BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Terminal UI".to_string(), + id: "terminal-ui".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Support".to_string(), + id: "support".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Getting Set up".to_string(), + id: "getting-set-up".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(7usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(7usize), + ); + pages.push((8usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Mobile".to_string(), + url: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Mobile App".to_string(), + id: "mobile-app".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Support".to_string(), + id: "support".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Getting Set up".to_string(), + id: "getting-set-up".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(8usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(8usize), + ); + pages.push((9usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Describing the UI".to_string(), + url: BookRoute::DescribingUiIndex { + section: DescribingUiIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Describing the UI".to_string(), + id: "describing-the-ui".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "RSX Features".to_string(), + id: "rsx-features".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Attributes".to_string(), + id: "attributes".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom Attributes".to_string(), + id: "custom-attributes".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Interpolation".to_string(), + id: "interpolation".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Children".to_string(), + id: "children".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Fragments".to_string(), + id: "fragments".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Expressions".to_string(), + id: "expressions".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Loops".to_string(), + id: "loops".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "If statements".to_string(), + id: "if-statements".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(9usize), + } + })); + page_id_mapping.insert( + BookRoute::DescribingUiIndex { + section: DescribingUiIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(9usize), + ); + pages.push((10usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Special Attributes".to_string(), + url: BookRoute::DescribingUiSpecialAttributes { + section: DescribingUiSpecialAttributesSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Special Attributes".to_string(), + id: "special-attributes".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The HTML Escape Hatch".to_string(), + id: "the-html-escape-hatch".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Boolean Attributes".to_string(), + id: "boolean-attributes".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(10usize), + } + })); + page_id_mapping.insert( + BookRoute::DescribingUiSpecialAttributes { + section: DescribingUiSpecialAttributesSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(10usize), + ); + pages.push((11usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Components".to_string(), + url: BookRoute::DescribingUiComponents { + section: DescribingUiComponentsSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Components".to_string(), + id: "components".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(11usize), + } + })); + page_id_mapping.insert( + BookRoute::DescribingUiComponents { + section: DescribingUiComponentsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(11usize), + ); + pages.push((12usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Props".to_string(), + url: BookRoute::DescribingUiComponentProps { + section: DescribingUiComponentPropsSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Component Props".to_string(), + id: "component-props".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "derive(Props)".to_string(), + id: "deriveprops".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Owned Props".to_string(), + id: "owned-props".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Borrowed Props".to_string(), + id: "borrowed-props".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Prop Options".to_string(), + id: "prop-options".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Optional Props".to_string(), + id: "optional-props".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Explicitly Required Options".to_string(), + id: "explicitly-required-options".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Default Props".to_string(), + id: "default-props".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Automatic Conversion with .into".to_string(), + id: "automatic-conversion-with-into".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The inline_props macro".to_string(), + id: "the-inline-props-macro".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(12usize), + } + })); + page_id_mapping.insert( + BookRoute::DescribingUiComponentProps { + section: DescribingUiComponentPropsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(12usize), + ); + pages.push((13usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Component Children".to_string(), + url: BookRoute::DescribingUiComponentChildren { + section: DescribingUiComponentChildrenSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Component Children".to_string(), + id: "component-children".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The children field".to_string(), + id: "the-children-field".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(13usize), + } + })); + page_id_mapping.insert( + BookRoute::DescribingUiComponentChildren { + section: DescribingUiComponentChildrenSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(13usize), + ); + pages.push((14usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Interactivity".to_string(), + url: BookRoute::InteractivityIndex { + section: InteractivityIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Interactivity".to_string(), + id: "interactivity".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(14usize), + } + })); + page_id_mapping.insert( + BookRoute::InteractivityIndex { + section: InteractivityIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(14usize), + ); + pages.push((15usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Event Listeners".to_string(), + url: BookRoute::InteractivityEventHandlers { + section: InteractivityEventHandlersSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Event Handlers".to_string(), + id: "event-handlers".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The Event object".to_string(), + id: "the-event-object".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Event propagation".to_string(), + id: "event-propagation".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Prevent Default".to_string(), + id: "prevent-default".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Handler Props".to_string(), + id: "handler-props".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(15usize), + } + })); + page_id_mapping.insert( + BookRoute::InteractivityEventHandlers { + section: InteractivityEventHandlersSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(15usize), + ); + pages.push((16usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Hooks & Component State".to_string(), + url: BookRoute::InteractivityHooks { + section: InteractivityHooksSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Hooks and Component State".to_string(), + id: "hooks-and-component-state".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "use_state Hook".to_string(), + id: "use-state-hook".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Rules of Hooks".to_string(), + id: "rules-of-hooks".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No Hooks in Conditionals".to_string(), + id: "no-hooks-in-conditionals".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No Hooks in Closures".to_string(), + id: "no-hooks-in-closures".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No Hooks in Loops".to_string(), + id: "no-hooks-in-loops".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "use_ref Hook".to_string(), + id: "use-ref-hook".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(16usize), + } + })); + page_id_mapping.insert( + BookRoute::InteractivityHooks { + section: InteractivityHooksSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(16usize), + ); + pages.push((17usize, { + ::use_mdbook::mdbook_shared::Page { + title: "User Input".to_string(), + url: BookRoute::InteractivityUserInput { + section: InteractivityUserInputSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "User Input".to_string(), + id: "user-input".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Controlled Inputs".to_string(), + id: "controlled-inputs".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Uncontrolled Inputs".to_string(), + id: "uncontrolled-inputs".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(17usize), + } + })); + page_id_mapping.insert( + BookRoute::InteractivityUserInput { + section: InteractivityUserInputSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(17usize), + ); + pages.push((18usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Sharing State".to_string(), + url: BookRoute::InteractivitySharingState { + section: InteractivitySharingStateSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Sharing State".to_string(), + id: "sharing-state".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Lifting State".to_string(), + id: "lifting-state".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Using Context".to_string(), + id: "using-context".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(18usize), + } + })); + page_id_mapping.insert( + BookRoute::InteractivitySharingState { + section: InteractivitySharingStateSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(18usize), + ); + pages.push((19usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Custom Hooks".to_string(), + url: BookRoute::InteractivityCustomHooks { + section: InteractivityCustomHooksSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Custom Hooks".to_string(), + id: "custom-hooks".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Composing Hooks".to_string(), + id: "composing-hooks".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom Hook Logic".to_string(), + id: "custom-hook-logic".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(19usize), + } + })); + page_id_mapping.insert( + BookRoute::InteractivityCustomHooks { + section: InteractivityCustomHooksSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(19usize), + ); + pages.push((20usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Dynamic Rendering".to_string(), + url: BookRoute::InteractivityDynamicRendering { + section: InteractivityDynamicRenderingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Dynamic Rendering".to_string(), + id: "dynamic-rendering".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conditional Rendering".to_string(), + id: "conditional-rendering".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Improving the if-else Example".to_string(), + id: "improving-the-if-else-example".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Inspecting Element props".to_string(), + id: "inspecting-element-props".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Rendering Nothing".to_string(), + id: "rendering-nothing".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Rendering Lists".to_string(), + id: "rendering-lists".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Inline for loops".to_string(), + id: "inline-for-loops".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The key Attribute".to_string(), + id: "the-key-attribute".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(20usize), + } + })); + page_id_mapping.insert( + BookRoute::InteractivityDynamicRendering { + section: InteractivityDynamicRenderingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(20usize), + ); + pages.push((21usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Routing".to_string(), + url: BookRoute::InteractivityRouter { + section: InteractivityRouterSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Router".to_string(), + id: "router".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "What is it?".to_string(), + id: "what-is-it".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Using the router".to_string(), + id: "using-the-router".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Links".to_string(), + id: "links".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "More reading".to_string(), + id: "more-reading".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(21usize), + } + })); + page_id_mapping.insert( + BookRoute::InteractivityRouter { + section: InteractivityRouterSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(21usize), + ); + pages.push((22usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Async".to_string(), + url: BookRoute::AsyncIndex { + section: AsyncIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Working with Async".to_string(), + id: "working-with-async".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The Runtime".to_string(), + id: "the-runtime".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(22usize), + } + })); + page_id_mapping.insert( + BookRoute::AsyncIndex { + section: AsyncIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(22usize), + ); + pages.push((23usize, { + ::use_mdbook::mdbook_shared::Page { + title: "UseFuture".to_string(), + url: BookRoute::AsyncUseFuture { + section: AsyncUseFutureSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "UseFuture".to_string(), + id: "usefuture".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Restarting the Future".to_string(), + id: "restarting-the-future".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dependencies".to_string(), + id: "dependencies".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(23usize), + } + })); + page_id_mapping.insert( + BookRoute::AsyncUseFuture { + section: AsyncUseFutureSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(23usize), + ); + pages.push((24usize, { + ::use_mdbook::mdbook_shared::Page { + title: "UseCoroutine".to_string(), + url: BookRoute::AsyncUseCoroutine { + section: AsyncUseCoroutineSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Coroutines".to_string(), + id: "coroutines".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "use_coroutine".to_string(), + id: "use-coroutine".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Yielding Values".to_string(), + id: "yielding-values".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Sending Values".to_string(), + id: "sending-values".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Automatic injection into the Context API".to_string(), + id: "automatic-injection-into-the-context-api".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(24usize), + } + })); + page_id_mapping.insert( + BookRoute::AsyncUseCoroutine { + section: AsyncUseCoroutineSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(24usize), + ); + pages.push((25usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Spawning Futures".to_string(), + url: BookRoute::AsyncSpawn { + section: AsyncSpawnSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Spawning Futures".to_string(), + id: "spawning-futures".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Spawning Tokio Tasks".to_string(), + id: "spawning-tokio-tasks".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(25usize), + } + })); + page_id_mapping.insert( + BookRoute::AsyncSpawn { + section: AsyncSpawnSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(25usize), + ); + pages.push((26usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Best Practices".to_string(), + url: BookRoute::BestPracticesIndex { + section: BestPracticesIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Best Practices".to_string(), + id: "best-practices".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Reusable Components".to_string(), + id: "reusable-components".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Minimize State Dependencies".to_string(), + id: "minimize-state-dependencies".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(26usize), + } + })); + page_id_mapping.insert( + BookRoute::BestPracticesIndex { + section: BestPracticesIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(26usize), + ); + pages.push((27usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Error Handling".to_string(), + url: BookRoute::BestPracticesErrorHandling { + section: BestPracticesErrorHandlingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Error handling".to_string(), + id: "error-handling".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The simplest – returning None".to_string(), + id: "the-simplest--returning-none".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Early return on result".to_string(), + id: "early-return-on-result".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Match results".to_string(), + id: "match-results".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Passing error states through components".to_string(), + id: "passing-error-states-through-components".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Going global".to_string(), + id: "going-global".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(27usize), + } + })); + page_id_mapping.insert( + BookRoute::BestPracticesErrorHandling { + section: BestPracticesErrorHandlingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(27usize), + ); + pages.push((28usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Antipatterns".to_string(), + url: BookRoute::BestPracticesAntipatterns { + section: BestPracticesAntipatternsSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Antipatterns".to_string(), + id: "antipatterns".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Unnecessarily Nested Fragments".to_string(), + id: "unnecessarily-nested-fragments".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Incorrect Iterator Keys".to_string(), + id: "incorrect-iterator-keys".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Avoid Interior Mutability in Props".to_string(), + id: "avoid-interior-mutability-in-props".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Avoid Updating State During Render".to_string(), + id: "avoid-updating-state-during-render".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(28usize), + } + })); + page_id_mapping.insert( + BookRoute::BestPracticesAntipatterns { + section: BestPracticesAntipatternsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(28usize), + ); + pages.push((29usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Publishing".to_string(), + url: BookRoute::PublishingIndex { + section: PublishingIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Publishing".to_string(), + id: "publishing".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(29usize), + } + })); + page_id_mapping.insert( + BookRoute::PublishingIndex { + section: PublishingIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(29usize), + ); + pages.push((30usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Desktop".to_string(), + url: BookRoute::PublishingDesktop { + section: PublishingDesktopSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Publishing".to_string(), + id: "publishing".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Install cargo-bundle".to_string(), + id: "install-cargo-bundle".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setting up your project".to_string(), + id: "setting-up-your-project".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Building".to_string(), + id: "building".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(30usize), + } + })); + page_id_mapping.insert( + BookRoute::PublishingDesktop { + section: PublishingDesktopSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(30usize), + ); + pages.push((31usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Web".to_string(), + url: BookRoute::PublishingWeb { + section: PublishingWebSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Publishing with Github Pages".to_string(), + id: "publishing-with-github-pages".to_string(), + level: 2usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(31usize), + } + })); + page_id_mapping.insert( + BookRoute::PublishingWeb { + section: PublishingWebSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(31usize), + ); + pages.push((32usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Custom Renderer".to_string(), + url: BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Custom Renderer".to_string(), + id: "custom-renderer".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The specifics:".to_string(), + id: "the-specifics".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Templates".to_string(), + id: "templates".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Mutations".to_string(), + id: "mutations".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "An Example".to_string(), + id: "an-example".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Event loop".to_string(), + id: "event-loop".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom raw elements".to_string(), + id: "custom-raw-elements".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Native Core".to_string(), + id: "native-core".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "RealDom".to_string(), + id: "realdom".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Example".to_string(), + id: "example".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Layout".to_string(), + id: "layout".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conclusion".to_string(), + id: "conclusion".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(32usize), + } + })); + page_id_mapping.insert( + BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(32usize), + ); + pages.push((33usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Roadmap".to_string(), + url: BookRoute::Roadmap { + section: RoadmapSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Roadmap & Feature-set".to_string(), + id: "roadmap--feature-set".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Features".to_string(), + id: "features".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Roadmap".to_string(), + id: "roadmap".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Core".to_string(), + id: "core".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "SSR".to_string(), + id: "ssr".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Desktop".to_string(), + id: "desktop".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Mobile".to_string(), + id: "mobile".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Bundling (CLI)".to_string(), + id: "bundling-cli".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Essential hooks".to_string(), + id: "essential-hooks".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Work in Progress".to_string(), + id: "work-in-progress".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Build Tool".to_string(), + id: "build-tool".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Server Component Support".to_string(), + id: "server-component-support".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Native rendering".to_string(), + id: "native-rendering".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(33usize), + } + })); + page_id_mapping.insert( + BookRoute::Roadmap { + section: RoadmapSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(33usize), + ); + pages.push((34usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Contributing".to_string(), + url: BookRoute::Contributing { + section: ContributingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Contributing".to_string(), + id: "contributing".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Improving Docs".to_string(), + id: "improving-docs".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Working on the Ecosystem".to_string(), + id: "working-on-the-ecosystem".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Bugs & Features".to_string(), + id: "bugs--features".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(34usize), + } + })); + page_id_mapping.insert( + BookRoute::Contributing { + section: ContributingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(34usize), + ); + ::use_mdbook::mdbook_shared::MdBook { + summary: ::use_mdbook::mdbook_shared::Summary { + title: Some("Summary".to_string()), + prefix_chapters: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Introduction".to_string(), + location: Some(BookRoute::Index { + section: IndexSection::Empty, + }), + number: None, + nested_items: vec![], + }), + ], + numbered_chapters: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Getting Started".to_string(), + location: Some(BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Desktop".to_string(), + location: Some(BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Web".to_string(), + location: Some(BookRoute::GettingStartedWeb { + section: GettingStartedWebSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32, 2u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Hot Reload".to_string(), + location: Some(BookRoute::GettingStartedHotReload { + section: GettingStartedHotReloadSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![1u32, 2u32, 1u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Server-Side Rendering".to_string(), + location: Some(BookRoute::GettingStartedSsr { + section: GettingStartedSsrSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32, 3u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Liveview".to_string(), + location: Some(BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32, 4u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Terminal UI".to_string(), + location: Some(BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32, 5u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Mobile".to_string(), + location: Some(BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32, 6u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Describing the UI".to_string(), + location: Some(BookRoute::DescribingUiIndex { + section: DescribingUiIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Special Attributes".to_string(), + location: Some(BookRoute::DescribingUiSpecialAttributes { + section: DescribingUiSpecialAttributesSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Components".to_string(), + location: Some(BookRoute::DescribingUiComponents { + section: DescribingUiComponentsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Props".to_string(), + location: Some(BookRoute::DescribingUiComponentProps { + section: DescribingUiComponentPropsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32, 3u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Component Children".to_string(), + location: Some(BookRoute::DescribingUiComponentChildren { + section: DescribingUiComponentChildrenSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32, 4u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Interactivity".to_string(), + location: Some(BookRoute::InteractivityIndex { + section: InteractivityIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Event Listeners".to_string(), + location: Some(BookRoute::InteractivityEventHandlers { + section: InteractivityEventHandlersSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Hooks & Component State".to_string(), + location: Some(BookRoute::InteractivityHooks { + section: InteractivityHooksSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "User Input".to_string(), + location: Some(BookRoute::InteractivityUserInput { + section: InteractivityUserInputSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 3u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Sharing State".to_string(), + location: Some(BookRoute::InteractivitySharingState { + section: InteractivitySharingStateSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 4u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Custom Hooks".to_string(), + location: Some(BookRoute::InteractivityCustomHooks { + section: InteractivityCustomHooksSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 5u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Dynamic Rendering".to_string(), + location: Some(BookRoute::InteractivityDynamicRendering { + section: InteractivityDynamicRenderingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 6u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Routing".to_string(), + location: Some(BookRoute::InteractivityRouter { + section: InteractivityRouterSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 7u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Async".to_string(), + location: Some(BookRoute::AsyncIndex { + section: AsyncIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![4u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "UseFuture".to_string(), + location: Some(BookRoute::AsyncUseFuture { + section: AsyncUseFutureSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![4u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "UseCoroutine".to_string(), + location: Some(BookRoute::AsyncUseCoroutine { + section: AsyncUseCoroutineSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![4u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Spawning Futures".to_string(), + location: Some(BookRoute::AsyncSpawn { + section: AsyncSpawnSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![4u32, 3u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Best Practices".to_string(), + location: Some(BookRoute::BestPracticesIndex { + section: BestPracticesIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Error Handling".to_string(), + location: Some(BookRoute::BestPracticesErrorHandling { + section: BestPracticesErrorHandlingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Antipatterns".to_string(), + location: Some(BookRoute::BestPracticesAntipatterns { + section: BestPracticesAntipatternsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 2u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Publishing".to_string(), + location: Some(BookRoute::PublishingIndex { + section: PublishingIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![6u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Desktop".to_string(), + location: Some(BookRoute::PublishingDesktop { + section: PublishingDesktopSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![6u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Web".to_string(), + location: Some(BookRoute::PublishingWeb { + section: PublishingWebSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![6u32, 2u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Separator, + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Custom Renderer".to_string(), + location: Some(BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![7u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Separator, + ], + suffix_chapters: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Roadmap".to_string(), + location: Some(BookRoute::Roadmap { + section: RoadmapSection::Empty, + }), + number: None, + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Contributing".to_string(), + location: Some(BookRoute::Contributing { + section: ContributingSection::Empty, + }), + number: None, + nested_items: vec![], + }), + ], + }, + pages: pages.into_iter().collect(), + page_id_mapping, + } + }); +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum IndexSection { + #[default] + Empty, + Introduction, + Features, + Multiplatform, + Stability, +} +impl std::str::FromStr for IndexSection { + type Err = IndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "introduction" => Ok(Self::Introduction), + "features" => Ok(Self::Features), + "multiplatform" => Ok(Self::Multiplatform), + "stability" => Ok(Self::Stability), + _ => Err(IndexSectionParseError), + } + } +} +impl std::fmt::Display for IndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Introduction => f.write_str("introduction"), + Self::Features => f.write_str("features"), + Self::Multiplatform => f.write_str("multiplatform"), + Self::Stability => f.write_str("stability"), + } + } +} +#[derive(Debug)] +pub struct IndexSectionParseError; +impl std::fmt::Display for IndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of IndexSectionintroduction, features, multiplatform, stability", + )?; + Ok(()) + } +} +impl std::error::Error for IndexSectionParseError {} +#[component(no_case_check)] +pub fn Index(section: IndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "introduction", + Link { + to: BookRoute::Index { + section: IndexSection::Introduction, + }, + class: "header", + "Introduction" + } + } + p { + img { + src: asset!( + "/assets/blog/release-03/dioxuslogo_full.png", ImageAssetOptions::new() + .with_webp() + ), + alt: "dioxuslogo", + title: "", + } + } + p { + "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." + } + CodeBlock { + contents: "
\nfn app(cx: Scope) -> Element {{\n    let mut count = use_state(cx, || 0);\n\n    cx.render(rsx!(\n        h1 {{ "High-Five counter: {{count}}" }}\n        button {{ onclick: move |_| count += 1, "Up high!" }}\n        button {{ onclick: move |_| count -= 1, "Down low!" }}\n    ))\n}}
\n", + } + p { + "Dioxus is heavily inspired by React. If you know React, getting started with Dioxus will be a breeze." + } + blockquote { + p { + "This guide assumes you already know some " + Link { to: "https://www.rust-lang.org/", "Rust" } + "! If not, we recommend reading " + Link { to: "https://doc.rust-lang.org/book/ch01-00-getting-started.html", + em { "the book" } + } + " to learn Rust first." + } + } + h2 { id: "features", + Link { + to: BookRoute::Index { + section: IndexSection::Features, + }, + class: "header", + "Features" + } + } + ul { + li { "Desktop apps running natively (no Electron!) in less than 10 lines of code." } + li { "Incredibly ergonomic and powerful state management." } + li { + "Comprehensive inline documentation – hover and guides for all HTML elements, listeners, and events." + } + li { "Extremely memory efficient – 0 global allocations for steady-state components." } + li { "Multi-channel asynchronous scheduler for first-class async support." } + li { + "And more! Read the " + Link { to: "https://dioxuslabs.com/blog/introducing-dioxus/", "full release post" } + "." + } + } + h3 { id: "multiplatform", + Link { + to: BookRoute::Index { + section: IndexSection::Multiplatform, + }, + class: "header", + "Multiplatform" + } + } + p { + "Dioxus is a " + em { "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 " + code { "html" } + " feature enabled, but this can be disabled depending on your target renderer." + } + p { "Right now, we have several 1st-party renderers:" } + ul { + li { "WebSys (for WASM): Great support" } + li { "Tao/Tokio (for Desktop apps): Good support" } + li { "Tao/Tokio (for Mobile apps): Poor support" } + li { "SSR (for generating static markup)" } + li { "TUI/Rink (for terminal-based apps): Experimental" } + } + h2 { id: "stability", + Link { + to: BookRoute::Index { + section: IndexSection::Stability, + }, + class: "header", + "Stability" + } + } + p { "Dioxus has not reached a stable release yet." } + p { + "Web: Since the web is a fairly mature platform, we expect there to be very little API churn for web-based features." + } + p { + "Desktop: APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart." + } + p { "SSR: We don't expect the SSR API to change drastically in the future." } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedIndexSection { + #[default] + Empty, + GettingStarted, + Prerequisites, + AnEditor, + Rust, + SetupGuides, +} +impl std::str::FromStr for GettingStartedIndexSection { + type Err = GettingStartedIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "getting-started" => Ok(Self::GettingStarted), + "prerequisites" => Ok(Self::Prerequisites), + "an-editor" => Ok(Self::AnEditor), + "rust" => Ok(Self::Rust), + "setup-guides" => Ok(Self::SetupGuides), + _ => Err(GettingStartedIndexSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::GettingStarted => f.write_str("getting-started"), + Self::Prerequisites => f.write_str("prerequisites"), + Self::AnEditor => f.write_str("an-editor"), + Self::Rust => f.write_str("rust"), + Self::SetupGuides => f.write_str("setup-guides"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedIndexSectionParseError; +impl std::fmt::Display for GettingStartedIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedIndexSectiongetting-started, prerequisites, an-editor, rust, setup-guides", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedIndexSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedIndex(section: GettingStartedIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "getting-started", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::GettingStarted, + }, + class: "header", + "Getting Started" + } + } + p { "This section will help you set up your Dioxus project!" } + h2 { id: "prerequisites", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Prerequisites, + }, + class: "header", + "Prerequisites" + } + } + h3 { id: "an-editor", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::AnEditor, + }, + class: "header", + "An Editor" + } + } + p { + "Dioxus integrates very well with the " + Link { to: "https://rust-analyzer.github.io", "Rust-Analyzer LSP plugin" } + " which will provide appropriate syntax highlighting, code navigation, folding, and more." + } + h3 { id: "rust", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Rust, + }, + class: "header", + "Rust" + } + } + p { + "Head over to " + Link { to: "http://rust-lang.org", "https://rust-lang.org" } + " and install the Rust compiler." + } + p { + "We strongly recommend going through the " + Link { to: "https://doc.rust-lang.org/book/ch01-00-getting-started.html", + "official Rust book" + } + " " + em { "completely" } + ". However, we hope that a Dioxus app can serve as a great first Rust project. With Dioxus, you'll learn about:" + } + ul { + li { "Error handling" } + li { "Structs, Functions, Enums" } + li { "Closures" } + li { "Macros" } + } + p { + "We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need deep knowledge of async, lifetimes, or smart pointers until you start building complex Dioxus apps." + } + h2 { id: "setup-guides", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::SetupGuides, + }, + class: "header", + "Setup Guides" + } + } + p { + "Dioxus supports multiple platforms. Choose the platform you want to target below to get platform-specific setup instructions:" + } + ul { + li { + Link { + to: BookRoute::GettingStartedWeb { + section: GettingStartedWebSection::Empty, + }, + "Web" + } + ": runs in the browser through WebAssembly" + } + li { + Link { + to: BookRoute::GettingStartedSsr { + section: GettingStartedSsrSection::Empty, + }, + "Server Side Rendering" + } + ": renders to HTML text on the server" + } + li { + Link { + to: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Empty, + }, + "Liveview" + } + ": runs on the server, renders in the browser using WebSockets" + } + li { + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Empty, + }, + "Desktop" + } + ": runs in a web view on desktop" + } + li { + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::Empty, + }, + "Mobile" + } + ": runs in a web view on mobile" + } + li { + Link { + to: BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::Empty, + }, + "Terminal UI" + } + ": renders text-based graphics in the terminal" + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedDesktopSection { + #[default] + Empty, + DesktopOverview, + Support, + GettingStarted, + PlatformSpecificDependencies, + Windows, + Linux, + Macos, + CreatingAProject, +} +impl std::str::FromStr for GettingStartedDesktopSection { + type Err = GettingStartedDesktopSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "desktop-overview" => Ok(Self::DesktopOverview), + "support" => Ok(Self::Support), + "getting-started" => Ok(Self::GettingStarted), + "platform-specific-dependencies" => Ok(Self::PlatformSpecificDependencies), + "windows" => Ok(Self::Windows), + "linux" => Ok(Self::Linux), + "macos" => Ok(Self::Macos), + "creating-a-project" => Ok(Self::CreatingAProject), + _ => Err(GettingStartedDesktopSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedDesktopSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::DesktopOverview => f.write_str("desktop-overview"), + Self::Support => f.write_str("support"), + Self::GettingStarted => f.write_str("getting-started"), + Self::PlatformSpecificDependencies => f.write_str("platform-specific-dependencies"), + Self::Windows => f.write_str("windows"), + Self::Linux => f.write_str("linux"), + Self::Macos => f.write_str("macos"), + Self::CreatingAProject => f.write_str("creating-a-project"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedDesktopSectionParseError; +impl std::fmt::Display for GettingStartedDesktopSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedDesktopSectiondesktop-overview, support, getting-started, platform-specific-dependencies, windows, linux, macos, creating-a-project", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedDesktopSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedDesktop(section: GettingStartedDesktopSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "desktop-overview", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::DesktopOverview, + }, + class: "header", + "Desktop Overview" + } + } + p { + "Build a standalone native desktop app that looks and feels the same across operating systems." + } + p { + "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." + } + p { "Examples:" } + ul { + li { + Link { to: "https://github.com/DioxusLabs/example-projects/blob/master/file-explorer", + "File Explorer" + } + } + li { + Link { to: "https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner", + "WiFi Scanner" + } + } + } + p { + Link { to: "https://github.com/DioxusLabs/example-projects/tree/master/file-explorer", + img { + src: "https://github.com/DioxusLabs/example-projects/raw/master/file-explorer/assets/image.png", + alt: "File ExplorerExample", + title: "", + } + } + } + h2 { id: "support", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Support, + }, + class: "header", + "Support" + } + } + p { + "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 " + em { "not" } + " available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs " + em { "are" } + " accessible, so streaming, WebSockets, filesystem, etc are all viable APIs. In the future, we plan to move to a custom web renderer-based DOM renderer with WGPU integrations." + } + p { + "Dioxus Desktop is built off " + Link { to: "https://tauri.app/", "Tauri" } + ". Right now there aren't any Dioxus abstractions over keyboard shortcuts, menubar, handling, etc, so you'll want to leverage Tauri – mostly " + Link { to: "http://github.com/tauri-apps/wry/", "Wry" } + " and " + Link { to: "http://github.com/tauri-apps/tao", "Tao" } + ") directly." + } + h1 { id: "getting-started", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::GettingStarted, + }, + class: "header", + "Getting started" + } + } + h2 { id: "platform-specific-dependencies", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::PlatformSpecificDependencies, + }, + class: "header", + "Platform-Specific Dependencies" + } + } + p { + "Dioxus desktop renders through a web view. Depending on your platform, you might need to install some dependancies." + } + h3 { id: "windows", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Windows, + }, + class: "header", + "Windows" + } + } + p { + "Windows Desktop apps depend on WebView2 – a library that should be installed in all modern Windows distributions. If you have Edge installed, then Dioxus will work fine. If you " + em { "don't" } + " have Webview2, " + Link { to: "https://developer.microsoft.com/en-us/microsoft-edge/webview2/", + "then you can install it through Microsoft" + } + ". MS provides 3 options:" + } + ol { + li { + "A tiny \"evergreen\" " + em { "bootstrapper" } + " that fetches an installer from Microsoft's CDN" + } + li { + "A tiny " + em { "installer" } + " that fetches Webview2 from Microsoft's CDN" + } + li { "A statically linked version of Webview2 in your final binary for offline users" } + } + p { "For development purposes, use Option 1." } + h3 { id: "linux", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Linux, + }, + class: "header", + "Linux" + } + } + p { + "Webview Linux apps require WebkitGtk. When distributing, this can be part of your dependency tree in your " + code { ".rpm" } + " or " + code { ".deb" } + ". However, likely, your users will already have WebkitGtk." + } + CodeBlock { contents: "
\nsudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev
\n" } + p { + "When using Debian/bullseye " + code { "libappindicator3-dev" } + " is no longer available but replaced by " + code { "libayatana-appindicator3-dev" } + "." + } + CodeBlock { contents: "
\n# on Debian/bullseye use:\nsudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
\n" } + p { + "If you run into issues, make sure you have all the basics installed, as outlined in the " + Link { to: "https://tauri.studio/v1/guides/getting-started/prerequisites#setting-up-linux", + "Tauri docs" + } + "." + } + h3 { id: "macos", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Macos, + }, + class: "header", + "MacOS" + } + } + p { + "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)." + } + h2 { id: "creating-a-project", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::CreatingAProject, + }, + class: "header", + "Creating a Project" + } + } + p { "Create a new crate:" } + CodeBlock { contents: "
\ncargo new --bin demo\ncd demo
\n" } + p { + "Add Dioxus and the desktop renderer as dependencies (this will edit your " + code { "Cargo.toml" } + "):" + } + CodeBlock { contents: "
\ncargo add dioxus\ncargo add dioxus-desktop
\n" } + p { + "Edit your " + code { "main.rs" } + ":" + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\n// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types\nuse dioxus::prelude::*;\n\nfn main() {{\n    // launch the dioxus app in a webview\n    dioxus_desktop::launch(App);\n}}\n\n// define a component that renders a div with the text "Hello, world!"\nfn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            "Hello, world!"\n        }}\n    }})\n}}
\n", + name: "hello_world_desktop.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedWebSection { + #[default] + Empty, + Web, + Support, + Tooling, + CreatingAProject, +} +impl std::str::FromStr for GettingStartedWebSection { + type Err = GettingStartedWebSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "web" => Ok(Self::Web), + "support" => Ok(Self::Support), + "tooling" => Ok(Self::Tooling), + "creating-a-project" => Ok(Self::CreatingAProject), + _ => Err(GettingStartedWebSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedWebSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Web => f.write_str("web"), + Self::Support => f.write_str("support"), + Self::Tooling => f.write_str("tooling"), + Self::CreatingAProject => f.write_str("creating-a-project"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedWebSectionParseError; +impl std::fmt::Display for GettingStartedWebSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedWebSectionweb, support, tooling, creating-a-project", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedWebSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedWeb(section: GettingStartedWebSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "web", + Link { + to: BookRoute::GettingStartedWeb { + section: GettingStartedWebSection::Web, + }, + class: "header", + "Web" + } + } + p { + "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 " + code { "dioxus" } + " and " + code { "dioxus-web" } + " crates." + } + p { + "A build of Dioxus for the web will be roughly equivalent to the size of a React build (70kb vs 65kb) but it will load significantly faster because " + Link { to: "https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/", + "WebAssembly can be compiled as it is streamed" + } + "." + } + p { "Examples:" } + ul { + li { + Link { to: "https://github.com/DioxusLabs/example-projects/tree/master/todomvc", + "TodoMVC" + } + } + li { + Link { to: "https://github.com/DioxusLabs/example-projects/tree/master/ecommerce-site", + "ECommerce" + } + } + } + p { + Link { to: "https://github.com/DioxusLabs/example-projects/blob/master/todomvc", + img { + src: "https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png", + alt: "TodoMVC example", + title: "", + } + } + } + blockquote { + p { + "Note: Because of the limitations of Wasm, " + Link { to: "https://rustwasm.github.io/docs/book/reference/which-crates-work-with-wasm.html", + "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)." + } + } + h2 { id: "support", + Link { + to: BookRoute::GettingStartedWeb { + section: GettingStartedWebSection::Support, + }, + class: "header", + "Support" + } + } + p { "The Web is the best-supported target platform for Dioxus." } + ul { + li { + "Because your app will be compiled to WASM you have access to browser APIs through " + Link { to: "https://rustwasm.github.io/docs/wasm-bindgen/introduction.html", + "wasm-bingen" + } + "." + } + li { + "Dioxus provides hydration to resume apps that are rendered on the server. See the " + Link { to: "https://github.com/DioxusLabs/dioxus/blob/master/packages/web/examples/hydrate.rs", + "hydration example" + } + " for more details." + } + } + h2 { id: "tooling", + Link { + to: BookRoute::GettingStartedWeb { + section: GettingStartedWebSection::Tooling, + }, + class: "header", + "Tooling" + } + } + p { + "To develop your Dioxus app for the web, you'll need a tool to build and serve your assets. We recommend using " + Link { to: "https://github.com/DioxusLabs/cli", "dioxus-cli" } + " which includes a build system, Wasm optimization, a dev server, and support hot reloading:" + } + CodeBlock { contents: "
\ncargo install dioxus-cli
\n" } + p { + "Make sure the " + code { "wasm32-unknown-unknown" } + " target for rust is installed:" + } + CodeBlock { contents: "
\nrustup target add wasm32-unknown-unknown
\n" } + h2 { id: "creating-a-project", + Link { + to: BookRoute::GettingStartedWeb { + section: GettingStartedWebSection::CreatingAProject, + }, + class: "header", + "Creating a Project" + } + } + p { "Create a new crate:" } + CodeBlock { contents: "
\ncargo new --bin demo\ncd demo
\n" } + p { + "Add Dioxus and the web renderer as dependencies (this will edit your " + code { "Cargo.toml" } + "):" + } + CodeBlock { contents: "
\ncargo add dioxus\ncargo add dioxus-web
\n" } + p { + "Edit your " + code { "main.rs" } + ":" + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\n// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types\nuse dioxus::prelude::*;\n\nfn main() {{\n    // launch the web app\n    dioxus_web::launch(App);\n}}\n\n// create a component that renders a div with the text "Hello, world!"\nfn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            "Hello, world!"\n        }}\n    }})\n}}
\n", + name: "hello_world_web.rs".to_string(), + } + p { "And to serve our app:" } + CodeBlock { contents: "
\ndioxus serve
\n" } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedHotReloadSection { + #[default] + Empty, + SettingUpHotReload, + Setup, + Usage, + Limitations, +} +impl std::str::FromStr for GettingStartedHotReloadSection { + type Err = GettingStartedHotReloadSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "setting-up-hot-reload" => Ok(Self::SettingUpHotReload), + "setup" => Ok(Self::Setup), + "usage" => Ok(Self::Usage), + "limitations" => Ok(Self::Limitations), + _ => Err(GettingStartedHotReloadSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedHotReloadSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::SettingUpHotReload => f.write_str("setting-up-hot-reload"), + Self::Setup => f.write_str("setup"), + Self::Usage => f.write_str("usage"), + Self::Limitations => f.write_str("limitations"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedHotReloadSectionParseError; +impl std::fmt::Display for GettingStartedHotReloadSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedHotReloadSectionsetting-up-hot-reload, setup, usage, limitations", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedHotReloadSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedHotReload( + section: GettingStartedHotReloadSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "setting-up-hot-reload", + Link { + to: BookRoute::GettingStartedHotReload { + section: GettingStartedHotReloadSection::SettingUpHotReload, + }, + class: "header", + "Setting Up Hot Reload" + } + } + ol { + li { + "Hot reloading allows much faster iteration times inside of rsx calls by interpreting them and streaming the edits." + } + li { + "It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program." + } + li { "Currently the cli only implements hot reloading for the web renderer." } + } + h1 { id: "setup", + Link { + to: BookRoute::GettingStartedHotReload { + section: GettingStartedHotReloadSection::Setup, + }, + class: "header", + "Setup" + } + } + p { + "Install " + Link { to: "https://github.com/DioxusLabs/cli", "dioxus-cli" } + "." + " " + "Hot reloading is automatically enabled when using the web renderer on debug builds." + } + h1 { id: "usage", + Link { + to: BookRoute::GettingStartedHotReload { + section: GettingStartedHotReloadSection::Usage, + }, + class: "header", + "Usage" + } + } + ol { + li { "run:" } + } + CodeBlock { contents: "
\ndioxus serve --hot-reload
\n" } + ol { + li { "change some code within a rsx macro" } + li { "open your localhost in a browser" } + li { "save and watch the style change without recompiling" } + } + h1 { id: "limitations", + Link { + to: BookRoute::GettingStartedHotReload { + section: GettingStartedHotReloadSection::Limitations, + }, + class: "header", + "Limitations" + } + } + ol { + li { + "The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will trigger a full recompile to capture the expression." + } + li { + "Components and Iterators can contain arbitrary rust code and will trigger a full recompile when changed." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedSsrSection { + #[default] + Empty, + ServerSideRendering, + MultithreadedSupport, + Setup, +} +impl std::str::FromStr for GettingStartedSsrSection { + type Err = GettingStartedSsrSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "server-side-rendering" => Ok(Self::ServerSideRendering), + "multithreaded-support" => Ok(Self::MultithreadedSupport), + "setup" => Ok(Self::Setup), + _ => Err(GettingStartedSsrSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedSsrSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ServerSideRendering => f.write_str("server-side-rendering"), + Self::MultithreadedSupport => f.write_str("multithreaded-support"), + Self::Setup => f.write_str("setup"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedSsrSectionParseError; +impl std::fmt::Display for GettingStartedSsrSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedSsrSectionserver-side-rendering, multithreaded-support, setup", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedSsrSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedSsr(section: GettingStartedSsrSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "server-side-rendering", + Link { + to: BookRoute::GettingStartedSsr { + section: GettingStartedSsrSection::ServerSideRendering, + }, + class: "header", + "Server-Side Rendering" + } + } + p { "The Dioxus VirtualDom can be rendered server-side." } + p { + Link { to: "https://github.com/dioxusLabs/docsite", "Example: Dioxus DocSite" } + } + h2 { id: "multithreaded-support", + Link { + to: BookRoute::GettingStartedSsr { + section: GettingStartedSsrSection::MultithreadedSupport, + }, + class: "header", + "Multithreaded Support" + } + } + p { + "The Dioxus VirtualDom, sadly, is not currently " + code { "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." + } + p { + "To solve this, you'll want to spawn a VirtualDom on its own thread and communicate with it via channels." + } + p { + "When working with web frameworks that require " + code { "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." + } + h2 { id: "setup", + Link { + to: BookRoute::GettingStartedSsr { + section: GettingStartedSsrSection::Setup, + }, + class: "header", + "Setup" + } + } + p { + "For this guide, we're going to show how to use Dioxus SSR with " + Link { to: "https://docs.rs/axum/latest/axum/", "Axum" } + "." + } + p { "Make sure you have Rust and Cargo installed, and then create a new project:" } + CodeBlock { contents: "
\ncargo new --bin demo\ncd app
\n" } + p { "Add Dioxus and the ssr renderer as dependencies:" } + CodeBlock { contents: "
\ncargo add dioxus\ncargo add dioxus-ssr
\n" } + p { + "Next, add all the Axum dependencies. This will be different if you're using a different Web Framework" + } + CodeBlock { contents: "
\ncargo add tokio --features full\ncargo add axum
\n" } + p { "Your dependencies should look roughly like this:" } + CodeBlock { + contents: "
\n[dependencies]\naxum = "0.4.5"\ndioxus = {{ version = "*" }}\ndioxus-ssr = {{ version = "*" }}\ntokio = {{ version = "1.15.0", features = ["full"] }}
\n", + } + p { "Now, set up your Axum app to respond on an endpoint." } + CodeBlock { + contents: "
\nuse axum::{{response::Html, routing::get, Router}};\nuse dioxus::prelude::*;\n\n#[tokio::main]\nasync fn main() {{\n    let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));\n    println!("listening on http://{{}}", addr);\n\n    axum::Server::bind(&addr)\n        .serve(\n            Router::new()\n                .route("/", get(app_endpoint))\n                .into_make_service(),\n        )\n        .await\n        .unwrap();\n}}
\n", + } + p { + "And then add our endpoint. We can either render " + code { "rsx!" } + " directly:" + } + CodeBlock { contents: "
\nasync fn app_endpoint() -> Html<String> {{\n    // render the rsx! macro to HTML\n    Html(dioxus_ssr::render_lazy(rsx! {{\n        div {{ "hello world!" }}\n    }}))\n}}
\n" } + p { "Or we can render VirtualDoms." } + CodeBlock { + contents: "
\nasync fn app_endpoint() -> Html<String> {{\n    // create a component that renders a div with the text "hello world"\n    fn app(cx: Scope) -> Element {{\n        cx.render(rsx!(div {{ "hello world" }}))\n    }}\n    // create a VirtualDom with the app component\n    let mut app = VirtualDom::new(app);\n    // rebuild the VirtualDom before rendering\n    let _ = app.rebuild();\n\n    // render the VirtualDom to HTML\n    Html(dioxus_ssr::render_vdom(&app))\n}}
\n", + } + p { "And that's it!" } + blockquote { + p { + "You might notice that you cannot hold the VirtualDom across an await point. Dioxus is currently not ThreadSafe, so it " + em { "must" } + " remain on the thread it started. We are working on loosening this requirement." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedLiveviewSection { + #[default] + Empty, + Liveview, + Support, + Setup, +} +impl std::str::FromStr for GettingStartedLiveviewSection { + type Err = GettingStartedLiveviewSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "liveview" => Ok(Self::Liveview), + "support" => Ok(Self::Support), + "setup" => Ok(Self::Setup), + _ => Err(GettingStartedLiveviewSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedLiveviewSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Liveview => f.write_str("liveview"), + Self::Support => f.write_str("support"), + Self::Setup => f.write_str("setup"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedLiveviewSectionParseError; +impl std::fmt::Display for GettingStartedLiveviewSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedLiveviewSectionliveview, support, setup", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedLiveviewSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedLiveview(section: GettingStartedLiveviewSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "liveview", + Link { + to: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Liveview, + }, + class: "header", + "Liveview" + } + } + p { + "Liveview allows apps to " + em { "run" } + " on the server and " + em { "render" } + " in the browser. It uses WebSockets to communicate between the server and the browser." + } + p { "Examples:" } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/axum.rs", + code { "Axum Example" } + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/salvo.rs", + code { "Salvo Example" } + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/warp.rs", + code { "Warp Example" } + } + } + } + h2 { id: "support", + Link { + to: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Support, + }, + class: "header", + "Support" + } + } + p { + "Liveview is currently limited in capability when compared to the Web platform. Liveview apps run on the server in 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." + } + h2 { id: "setup", + Link { + to: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Setup, + }, + class: "header", + "Setup" + } + } + p { + "For this guide, we're going to show how to use Dioxus Liveview with " + Link { to: "https://docs.rs/axum/latest/axum/", "Axum" } + "." + } + p { "Make sure you have Rust and Cargo installed, and then create a new project:" } + CodeBlock { contents: "
\ncargo new --bin demo\ncd app
\n" } + p { "Add Dioxus and the liveview renderer with the Axum feature as dependencies:" } + CodeBlock { contents: "
\ncargo add dioxus\ncargo add dioxus-liveview --features axum
\n" } + p { + "Next, add all the Axum dependencies. This will be different if you're using a different Web Framework" + } + CodeBlock { contents: "
\ncargo add tokio --features full\ncargo add axum
\n" } + p { "Your dependencies should look roughly like this:" } + CodeBlock { + contents: "
\n[dependencies]\naxum = "0.4.5"\ndioxus = {{ version = "*" }}\ndioxus-liveview = {{ version = "*", features = ["axum"] }}\ntokio = {{ version = "1.15.0", features = ["full"] }}
\n", + } + p { "Now, set up your Axum app to respond on an endpoint." } + CodeBlock { + contents: "
\n#[tokio::main]\nasync fn main() {{\n    let addr: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into();\n\n    let view = dioxus_liveview::LiveViewPool::new();\n\n    let app = Router::new()\n        // The root route contains the glue code to connect to the WebSocket\n        .route(\n            "/",\n            get(move || async move {{\n                Html(format!(\n                    r#"\n                <!DOCTYPE html>\n                <html>\n                <head> <title>Dioxus LiveView with Axum</title>  </head>\n                <body> <div id="main"></div> </body>\n                {{glue}}\n                </html>\n                "#,\n                    // Create the glue code to connect to the WebSocket on the "/ws" route\n                    glue = dioxus_liveview::interpreter_glue(&format!("ws://{{addr}}/ws"))\n                ))\n            }}),\n        )\n        // The WebSocket route is what Dioxus uses to communicate with the browser\n        .route(\n            "/ws",\n            get(move |ws: WebSocketUpgrade| async move {{\n                ws.on_upgrade(move |socket| async move {{\n                    // When the WebSocket is upgraded, launch the LiveView with the app component\n                    _ = view.launch(dioxus_liveview::axum_socket(socket), app).await;\n                }})\n            }}),\n        );\n\n    println!("Listening on http://{{}}", addr);\n\n    axum::Server::bind(&addr.to_string().parse().unwrap())\n        .serve(app.into_make_service())\n        .await\n        .unwrap();\n}}
\n", + name: "hello_world_liveview.rs".to_string(), + } + p { "And then add our app component:" } + CodeBlock { + contents: "
\nfn app(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            "Hello, world!"\n        }}\n    }})\n}}
\n", + name: "hello_world_liveview.rs".to_string(), + } + p { "And that's it!" } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedTuiSection { + #[default] + Empty, + TerminalUi, + Support, + GettingSetUp, +} +impl std::str::FromStr for GettingStartedTuiSection { + type Err = GettingStartedTuiSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "terminal-ui" => Ok(Self::TerminalUi), + "support" => Ok(Self::Support), + "getting-set-up" => Ok(Self::GettingSetUp), + _ => Err(GettingStartedTuiSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedTuiSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::TerminalUi => f.write_str("terminal-ui"), + Self::Support => f.write_str("support"), + Self::GettingSetUp => f.write_str("getting-set-up"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedTuiSectionParseError; +impl std::fmt::Display for GettingStartedTuiSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedTuiSectionterminal-ui, support, getting-set-up", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedTuiSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedTui(section: GettingStartedTuiSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "terminal-ui", + Link { + to: BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::TerminalUi, + }, + class: "header", + "Terminal UI" + } + } + p { "You can build a text-based interface that will run in the terminal using Dioxus." } + p { + img { + src: "https://github.com/DioxusLabs/rink/raw/master/examples/example.png", + alt: "Hello World screenshot", + title: "", + } + } + blockquote { + p { + "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." + } + } + h2 { id: "support", + Link { + to: BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::Support, + }, + class: "header", + "Support" + } + } + p { + "TUI support is currently quite experimental. But, if you're willing to venture into the realm of the unknown, this guide will get you started." + } + ul { + li { "It uses flexbox for the layout" } + li { "It only supports a subset of the attributes and elements" } + li { + "Regular widgets will not work in the tui render, but the tui renderer has its own widget components that start with a capital letter. See the " + Link { to: "https://github.com/DioxusLabs/dioxus/blob/master/packages/tui/examples/tui_widgets.rs", + "widgets example" + } + } + li { "1px is one character line height. Your regular CSS px does not translate" } + li { "If your app panics, your terminal is wrecked. This will be fixed eventually" } + } + h2 { id: "getting-set-up", + Link { + to: BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::GettingSetUp, + }, + class: "header", + "Getting Set up" + } + } + p { "Start by making a new package and adding Dioxus and the TUI renderer as dependancies." } + CodeBlock { contents: "
\ncargo new --bin demo\ncd demo\ncargo add dioxus\ncargo add dioxus-tui
\n" } + p { + "Then, edit your " + code { "main.rs" } + " with the basic template." + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\n// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types\nuse dioxus::prelude::*;\n\nfn main() {{\n    // launch the app in the terminal\n    dioxus_tui::launch(App);\n}}\n\n// create a component that renders a div with the text "Hello, world!"\nfn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            "Hello, world!"\n        }}\n    }})\n}}
\n", + name: "hello_world_tui.rs".to_string(), + } + p { "To run our app:" } + CodeBlock { contents: "
\ncargo run
\n" } + p { + "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." + } + CodeBlock { + contents: "
\n// todo remove deprecated\n#![allow(non_snake_case, deprecated)]\n\nuse dioxus::events::{{KeyCode, KeyboardEvent}};\nuse dioxus::prelude::*;\nuse dioxus_tui::TuiContext;\n\nfn main() {{\n    dioxus_tui::launch_cfg(\n        App,\n        dioxus_tui::Config::new()\n            .without_ctrl_c_quit()\n            // Some older terminals only support 16 colors or ANSI colors\n            // If your terminal is one of these, change this to BaseColors or ANSI\n            .with_rendering_mode(dioxus_tui::RenderingMode::Rgb),\n    );\n}}\n\nfn App(cx: Scope) -> Element {{\n    let tui_ctx: TuiContext = cx.consume_context().unwrap();\n\n    cx.render(rsx! {{\n        div {{\n            width: "100%",\n            height: "10px",\n            background_color: "red",\n            justify_content: "center",\n            align_items: "center",\n            onkeydown: move |k: KeyboardEvent| if let KeyCode::Q = k.key_code {{\n                tui_ctx.quit();\n            }},\n\n            "Hello world!"\n        }}\n    }})\n}}
\n", + name: "hello_world_tui_no_ctrl_c.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedMobileSection { + #[default] + Empty, + MobileApp, + Support, + GettingSetUp, +} +impl std::str::FromStr for GettingStartedMobileSection { + type Err = GettingStartedMobileSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "mobile-app" => Ok(Self::MobileApp), + "support" => Ok(Self::Support), + "getting-set-up" => Ok(Self::GettingSetUp), + _ => Err(GettingStartedMobileSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedMobileSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::MobileApp => f.write_str("mobile-app"), + Self::Support => f.write_str("support"), + Self::GettingSetUp => f.write_str("getting-set-up"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedMobileSectionParseError; +impl std::fmt::Display for GettingStartedMobileSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedMobileSectionmobile-app, support, getting-set-up", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedMobileSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedMobile(section: GettingStartedMobileSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "mobile-app", + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::MobileApp, + }, + class: "header", + "Mobile App" + } + } + p { "Build a mobile app with Dioxus!" } + p { + "Example: " + Link { to: "https://github.com/DioxusLabs/example-projects/blob/master/ios_demo", + "Todo App" + } + } + h2 { id: "support", + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::Support, + }, + class: "header", + "Support" + } + } + p { + "Mobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with either the platform's WebView or experimentally through " + Link { to: "https://github.com/DioxusLabs/blitz", "WGPU" } + ". WebView doesn't support animations, transparency, and native widgets." + } + p { + "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." + } + p { + "This guide is primarily targeted at iOS apps, however, you can follow it while using the " + code { "android" } + " guide in " + code { "cargo-mobile" } + "." + } + h2 { id: "getting-set-up", + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::GettingSetUp, + }, + class: "header", + "Getting Set up" + } + } + p { + "Getting set up with mobile can be quite challenging. The tooling here isn't great (yet) and might take some hacking around to get things working. macOS M1 is broadly unexplored and might not work for you." + } + p { + "We're going to be using " + code { "cargo-mobile" } + " to build for mobile. First, install it:" + } + CodeBlock { contents: "
\ncargo install --git https://github.com/BrainiumLLC/cargo-mobile
\n" } + p { + "And then initialize your app for the right platform. Use the " + code { "winit" } + " template for now. Right now, there's no \"Dioxus\" template in cargo-mobile." + } + CodeBlock { contents: "
\ncargo mobile init
\n" } + p { + "We're going to completely clear out the " + code { "dependencies" } + " it generates for us, swapping out " + code { "winit" } + " with " + code { "dioxus-mobile" } + "." + } + CodeBlock { + contents: "
\n\n[package]\nname = "dioxus-ios-demo"\nversion = "0.1.0"\nauthors = []\nedition = "2018"\n\n\n# leave the `lib` declaration\n[lib]\ncrate-type = ["staticlib", "cdylib", "rlib"]\n\n\n# leave the binary it generates for us\n[[bin]]\nname = "dioxus-ios-demo-desktop"\npath = "gen/bin/desktop.rs"\n\n# clear all the dependencies\n[dependencies]\nmobile-entry-point = "0.1.0"\ndioxus = {{ version = "*"}}\ndioxus-desktop = {{ version = "*" }}\nsimple_logger = "*"
\n", + } + p { + "Edit your " + code { "lib.rs" } + ":" + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\nfn main() {{\n    dioxus_desktop::launch(app);\n}}\n\nfn app(cx: Scope) -> Element {{\n    cx.render(rsx!{{\n        div {{\n            "hello world!"\n        }}\n    }})\n}}
\n", + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum DescribingUiIndexSection { + #[default] + Empty, + DescribingTheUi, + RsxFeatures, + Attributes, + CustomAttributes, + Interpolation, + Children, + Fragments, + Expressions, + Loops, + IfStatements, +} +impl std::str::FromStr for DescribingUiIndexSection { + type Err = DescribingUiIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "describing-the-ui" => Ok(Self::DescribingTheUi), + "rsx-features" => Ok(Self::RsxFeatures), + "attributes" => Ok(Self::Attributes), + "custom-attributes" => Ok(Self::CustomAttributes), + "interpolation" => Ok(Self::Interpolation), + "children" => Ok(Self::Children), + "fragments" => Ok(Self::Fragments), + "expressions" => Ok(Self::Expressions), + "loops" => Ok(Self::Loops), + "if-statements" => Ok(Self::IfStatements), + _ => Err(DescribingUiIndexSectionParseError), + } + } +} +impl std::fmt::Display for DescribingUiIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::DescribingTheUi => f.write_str("describing-the-ui"), + Self::RsxFeatures => f.write_str("rsx-features"), + Self::Attributes => f.write_str("attributes"), + Self::CustomAttributes => f.write_str("custom-attributes"), + Self::Interpolation => f.write_str("interpolation"), + Self::Children => f.write_str("children"), + Self::Fragments => f.write_str("fragments"), + Self::Expressions => f.write_str("expressions"), + Self::Loops => f.write_str("loops"), + Self::IfStatements => f.write_str("if-statements"), + } + } +} +#[derive(Debug)] +pub struct DescribingUiIndexSectionParseError; +impl std::fmt::Display for DescribingUiIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of DescribingUiIndexSectiondescribing-the-ui, rsx-features, attributes, custom-attributes, interpolation, children, fragments, expressions, loops, if-statements", + )?; + Ok(()) + } +} +impl std::error::Error for DescribingUiIndexSectionParseError {} +#[component(no_case_check)] +pub fn DescribingUiIndex(section: DescribingUiIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "describing-the-ui", + Link { + to: BookRoute::DescribingUiIndex { + section: DescribingUiIndexSection::DescribingTheUi, + }, + class: "header", + "Describing the UI" + } + } + p { + "Dioxus is a " + em { "declarative" } + " framework. This means that instead of telling Dioxus what to do (e.g. to \"create an element\" or \"set the color to red\") we simply " + em { "declare" } + " what we want the UI to look like using RSX." + } + p { "You have already seen a simple example of RSX syntax in the \"hello world\" application:" } + CodeBlock { + contents: "
\n// define a component that renders a div with the text "Hello, world!"\nfn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            "Hello, world!"\n        }}\n    }})\n}}
\n", + name: "hello_world_desktop.rs".to_string(), + } + p { + "Here, we use the " + code { "rsx!" } + " macro to " + em { "declare" } + " that we want a " + code { "div" } + " element, containing the text " + code { "\"Hello, world!\"" } + ". Dioxus takes the RSX and constructs a UI from it." + } + h2 { id: "rsx-features", + Link { + to: BookRoute::DescribingUiIndex { + section: DescribingUiIndexSection::RsxFeatures, + }, + class: "header", + "RSX Features" + } + } + p { + "RSX is very similar to HTML in that it describes elements with attributes and children. Here's an empty " + code { "div" } + " element in RSX, as well as the resulting HTML:" + } + CodeBlock { + contents: "
\ncx.render(rsx!(div {{\n// attributes / listeners\n// children\n}}))
\n", + name: "rsx_overview.rs".to_string(), + } + CodeBlock { contents: "
\n<div></div>
\n" } + h3 { id: "attributes", + Link { + to: BookRoute::DescribingUiIndex { + section: DescribingUiIndexSection::Attributes, + }, + class: "header", + "Attributes" + } + } + p { + "Attributes (and " + Link { + to: BookRoute::InteractivityIndex { + section: InteractivityIndexSection::Empty, + }, + "listeners" + } + ") modify the behavior or appearance of the element they are attached to. They are specified inside the " + code { "{{}}" } + " brackets, using the " + code { "name: value" } + " syntax. You can provide the value as a literal in the RSX:" + } + CodeBlock { + contents: "
\ncx.render(rsx!(a {{\nhref: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",\nclass: "primary_button",\ncolor: "red",\n}}))
\n", + name: "rsx_overview.rs".to_string(), + } + CodeBlock { contents: "
\n<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" class="primary_button" autofocus="true" style="color: red"></a>
\n" } + blockquote { + p { + "Note: All attributes defined in " + code { "dioxus-html" } + " follow the snake_case naming convention. They transform their " + code { "snake_case" } + " names to HTML's " + code { "camelCase" } + " attributes." + } + } + blockquote { + p { + "Note: Styles can be used directly outside of the " + code { "style:" } + " attribute. In the above example, " + code { "color: \"red\"" } + " is turned into " + code { "style=\"color: red\"" } + "." + } + } + h4 { id: "custom-attributes", + Link { + to: BookRoute::DescribingUiIndex { + section: DescribingUiIndexSection::CustomAttributes, + }, + class: "header", + "Custom Attributes" + } + } + p { + "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:" + } + CodeBlock { + contents: "
\ncx.render(rsx!(b {{\n    "customAttribute": "value",\n}}))
\n", + name: "rsx_overview.rs".to_string(), + } + CodeBlock { contents: "
\n<b customAttribute="value">\n</b>
\n" } + h3 { id: "interpolation", + Link { + to: BookRoute::DescribingUiIndex { + section: DescribingUiIndexSection::Interpolation, + }, + class: "header", + "Interpolation" + } + } + p { + "Similarly to how you can " + Link { to: "https://doc.rust-lang.org/rust-by-example/hello/print/fmt.html", + "format" + } + " Rust strings, you can also interpolate in RSX text. Use " + code { "{{variable}}" } + " to Display the value of a variable in a string, or " + code { "{{variable:?}}" } + " to use the Debug representation:" + } + CodeBlock { + contents: "
\nlet coordinates = (42, 0);\nlet country = "es";\ncx.render(rsx!(div {{\nclass: "country-{{country}}",\n"position": "{{coordinates:?}}",\n// arbitrary expressions are allowed,\n// as long as they don't contain `{{}}`\ndiv {{\n    "{{country.to_uppercase()}}"\n}},\ndiv {{\n    "{{7*6}}"\n}},\n// {{}} can be escaped with {{{{}}}}\ndiv {{\n    "{{{{}}}}"\n}},\n}}))
\n", + name: "rsx_overview.rs".to_string(), + } + CodeBlock { + contents: "
\n<div class="country-es" position="(42, 0)">\n    <div>ES</div>\n    <div>42</div>\n    <div>{{}}</div>\n</div>
\n", + } + h3 { id: "children", + Link { + to: BookRoute::DescribingUiIndex { + section: DescribingUiIndexSection::Children, + }, + class: "header", + "Children" + } + } + p { + "To add children to an element, put them inside the " + code { "{{}}" } + " brackets after all attributes and listeners in the element. They can be other elements, text, or " + Link { + to: BookRoute::DescribingUiComponents { + section: DescribingUiComponentsSection::Empty, + }, + "components" + } + ". For example, you could have an " + code { "ol" } + " (ordered list) element, containing 3 " + code { "li" } + " (list item) elements, each of which contains some text:" + } + CodeBlock { + contents: "
\ncx.render(rsx!(ol {{\nli {{"First Item"}}\nli {{"Second Item"}}\nli {{"Third Item"}}\n}}))
\n", + name: "rsx_overview.rs".to_string(), + } + CodeBlock { + contents: "
\n<ol>\n    <li>First Item</li>\n    <li>Second Item</li>\n    <li>Third Item</li>\n</ol>
\n", + } + h3 { id: "fragments", + Link { + to: BookRoute::DescribingUiIndex { + section: DescribingUiIndexSection::Fragments, + }, + class: "header", + "Fragments" + } + } + p { + "You can render multiple elements at the top level of " + code { "rsx!" } + " and they will be automatically grouped." + } + CodeBlock { + contents: "
\ncx.render(rsx!(\np {{"First Item"}},\np {{"Second Item"}},\n))
\n", + name: "rsx_overview.rs".to_string(), + } + CodeBlock { contents: "
\n<p>First Item</p>\n<p>Second Item</p>
\n" } + h3 { id: "expressions", + Link { + to: BookRoute::DescribingUiIndex { + section: DescribingUiIndexSection::Expressions, + }, + class: "header", + "Expressions" + } + } + p { + "You can include arbitrary Rust expressions as children within RSX that implements " + Link { to: "https://docs.rs/dioxus-core/0.3/dioxus_core/trait.IntoDynNode.html", + "IntoDynNode" + } + ". This is useful for displaying data from an " + Link { to: "https://doc.rust-lang.org/stable/book/ch13-02-iterators.html#processing-a-series-of-items-with-iterators", + "iterator" + } + ":" + } + CodeBlock { + contents: "
\nlet text = "Dioxus";\ncx.render(rsx!(span {{\ntext.to_uppercase(),\n// create a list of text from 0 to 9\n(0..10).map(|i| rsx!{{ i.to_string() }})\n}}))
\n", + name: "rsx_overview.rs".to_string(), + } + CodeBlock { contents: "
\n<span>DIOXUS0123456789</span>
\n" } + h3 { id: "loops", + Link { + to: BookRoute::DescribingUiIndex { + section: DescribingUiIndexSection::Loops, + }, + class: "header", + "Loops" + } + } + p { "In addition to iterators you can also use for loops directly within RSX:" } + CodeBlock { + contents: "
\ncx.render(rsx!{{\n// use a for loop where the body itself is RSX\ndiv {{\n    // create a list of text from 0 to 9\n    for i in 0..3 {{\n        // NOTE: the body of the loop is RSX not a rust statement\n        div {{\n            "{{i}}"\n        }}\n    }}\n}}\n// iterator equivalent\ndiv {{\n    (0..3).map(|i| rsx!{{ div {{ "{{i}}" }} }})\n}}\n}})
\n", + name: "rsx_overview.rs".to_string(), + } + CodeBlock { + contents: "
\n<div>0</div>\n<div>1</div>\n<div>2</div>\n<div>0</div>\n<div>1</div>\n<div>2</div>
\n", + } + h3 { id: "if-statements", + Link { + to: BookRoute::DescribingUiIndex { + section: DescribingUiIndexSection::IfStatements, + }, + class: "header", + "If statements" + } + } + p { "You can also use if statements without an else branch within RSX:" } + CodeBlock { + contents: "
\ncx.render(rsx!{{\n// use if statements without an else\nif true {{\n    rsx!(div {{ "true" }})\n}}\n}})
\n", + name: "rsx_overview.rs".to_string(), + } + CodeBlock { contents: "
\n<div>true</div>
\n" } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum DescribingUiSpecialAttributesSection { + #[default] + Empty, + SpecialAttributes, + TheHtmlEscapeHatch, + BooleanAttributes, +} +impl std::str::FromStr for DescribingUiSpecialAttributesSection { + type Err = DescribingUiSpecialAttributesSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "special-attributes" => Ok(Self::SpecialAttributes), + "the-html-escape-hatch" => Ok(Self::TheHtmlEscapeHatch), + "boolean-attributes" => Ok(Self::BooleanAttributes), + _ => Err(DescribingUiSpecialAttributesSectionParseError), + } + } +} +impl std::fmt::Display for DescribingUiSpecialAttributesSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::SpecialAttributes => f.write_str("special-attributes"), + Self::TheHtmlEscapeHatch => f.write_str("the-html-escape-hatch"), + Self::BooleanAttributes => f.write_str("boolean-attributes"), + } + } +} +#[derive(Debug)] +pub struct DescribingUiSpecialAttributesSectionParseError; +impl std::fmt::Display for DescribingUiSpecialAttributesSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of DescribingUiSpecialAttributesSectionspecial-attributes, the-html-escape-hatch, boolean-attributes", + )?; + Ok(()) + } +} +impl std::error::Error for DescribingUiSpecialAttributesSectionParseError {} +#[component(no_case_check)] +pub fn DescribingUiSpecialAttributes( + section: DescribingUiSpecialAttributesSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "special-attributes", + Link { + to: BookRoute::DescribingUiSpecialAttributes { + section: DescribingUiSpecialAttributesSection::SpecialAttributes, + }, + class: "header", + "Special Attributes" + } + } + p { "While most attributes are simply passed on to the HTML, some have special behaviors." } + h2 { id: "the-html-escape-hatch", + Link { + to: BookRoute::DescribingUiSpecialAttributes { + section: DescribingUiSpecialAttributesSection::TheHtmlEscapeHatch, + }, + class: "header", + "The HTML Escape Hatch" + } + } + p { + "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 " + code { "dangerous_inner_html" } + "." + } + p { + "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 " + Link { to: "https://dioxuslabs.com", "Dioxus homepage" } + ":" + } + CodeBlock { + contents: "
\n// this should come from a trusted source\nlet contents = "live <b>dangerously</b>";\n\ncx.render(rsx! {{\ndiv {{\n    dangerous_inner_html: "{{contents}}",\n}}\n}})
\n", + name: "dangerous_inner_html.rs".to_string(), + } + blockquote { + p { + "Note! This attribute is called \"dangerous_inner_html\" because it is " + strong { "dangerous" } + " to pass it data you don't trust. If you're not careful, you can easily expose " + Link { to: "https://en.wikipedia.org/wiki/Cross-site_scripting", + "cross-site scripting (XSS)" + } + " attacks to your users." + } + p { + "If you're handling untrusted input, make sure to sanitize your HTML before passing it into " + code { "dangerous_inner_html" } + " – or just pass it to a Text Element to escape any HTML tags." + } + } + h2 { id: "boolean-attributes", + Link { + to: BookRoute::DescribingUiSpecialAttributes { + section: DescribingUiSpecialAttributesSection::BooleanAttributes, + }, + class: "header", + "Boolean Attributes" + } + } + p { + "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 " + code { "\"false\"" } + " will cause them to be removed from the target element." + } + p { + "So this RSX wouldn't actually render the " + code { "hidden" } + " attribute:" + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\ndiv {{\n    hidden: "false",\n    "hello"\n}}\n}})
\n", + name: "boolean_attribute.rs".to_string(), + } + CodeBlock { contents: "
\n<div>hello</div>
\n" } + p { + "Not all attributes work like this however. " + em { "Only the following attributes" } + " have this behavior:" + } + ul { + li { + code { "allowfullscreen" } + } + li { + code { "allowpaymentrequest" } + } + li { + code { "async" } + } + li { + code { "autofocus" } + } + li { + code { "autoplay" } + } + li { + code { "checked" } + } + li { + code { "controls" } + } + li { + code { "default" } + } + li { + code { "defer" } + } + li { + code { "disabled" } + } + li { + code { "formnovalidate" } + } + li { + code { "hidden" } + } + li { + code { "ismap" } + } + li { + code { "itemscope" } + } + li { + code { "loop" } + } + li { + code { "multiple" } + } + li { + code { "muted" } + } + li { + code { "nomodule" } + } + li { + code { "novalidate" } + } + li { + code { "open" } + } + li { + code { "playsinline" } + } + li { + code { "readonly" } + } + li { + code { "required" } + } + li { + code { "reversed" } + } + li { + code { "selected" } + } + li { + code { "truespeed" } + } + } + p { + "For any other attributes, a value of " + code { "\"false\"" } + " will be sent directly to the DOM." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum DescribingUiComponentsSection { + #[default] + Empty, + Components, +} +impl std::str::FromStr for DescribingUiComponentsSection { + type Err = DescribingUiComponentsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "components" => Ok(Self::Components), + _ => Err(DescribingUiComponentsSectionParseError), + } + } +} +impl std::fmt::Display for DescribingUiComponentsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Components => f.write_str("components"), + } + } +} +#[derive(Debug)] +pub struct DescribingUiComponentsSectionParseError; +impl std::fmt::Display for DescribingUiComponentsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of DescribingUiComponentsSectioncomponents", + )?; + Ok(()) + } +} +impl std::error::Error for DescribingUiComponentsSectionParseError {} +#[component(no_case_check)] +pub fn DescribingUiComponents(section: DescribingUiComponentsSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "components", + Link { + to: BookRoute::DescribingUiComponents { + section: DescribingUiComponentsSection::Components, + }, + class: "header", + "Components" + } + } + p { + "Just like you wouldn't want to write a complex program in a single, long, " + code { "main" } + " function, you shouldn't build a complex UI in a single " + code { "App" } + " function. Instead, you should break down the functionality of an app in logical parts called components." + } + p { + "A component is a Rust function, named in UpperCamelCase, that takes a " + code { "Scope" } + " parameter and returns an " + code { "Element" } + " describing the UI it wants to render. In fact, our " + code { "App" } + " function is a component!" + } + CodeBlock { + contents: "
\n// define a component that renders a div with the text "Hello, world!"\nfn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            "Hello, world!"\n        }}\n    }})\n}}
\n", + name: "hello_world_desktop.rs".to_string(), + } + blockquote { + p { + "You'll probably want to add " + code { "#![allow(non_snake_case)]" } + " to the top of your crate to avoid warnings about UpperCamelCase component names" + } + } + p { + "A Component is responsible for some rendering task – typically, rendering an isolated part of the user interface. For example, you could have an " + code { "About" } + " component that renders a short description of Dioxus Labs:" + } + CodeBlock { + contents: "
\npub fn About(cx: Scope) -> Element {{\n    cx.render(rsx!(p {{\n        b {{"Dioxus Labs"}}\n        " An Open Source project dedicated to making Rust UI wonderful."\n    }}))\n}}
\n", + name: "components.rs".to_string(), + } + p { + "Then, you can render your component in another component, similarly to how elements are rendered:" + } + CodeBlock { + contents: "
\nfn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        About {{}},\n        About {{}},\n    }})\n}}
\n", + name: "components.rs".to_string(), + } + p { + img { + src: asset!( + "/assets/blog/release-03/screenshot_about_component.png", + ImageAssetOptions::new().with_webp() + ), + alt: "Screenshot containing the About component twice", + title: "", + } + } + blockquote { + p { + "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!" + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum DescribingUiComponentPropsSection { + #[default] + Empty, + ComponentProps, + Deriveprops, + OwnedProps, + BorrowedProps, + PropOptions, + OptionalProps, + ExplicitlyRequiredOptionS, + DefaultProps, + AutomaticConversionWithInto, + TheInlinePropsMacro, +} +impl std::str::FromStr for DescribingUiComponentPropsSection { + type Err = DescribingUiComponentPropsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "component-props" => Ok(Self::ComponentProps), + "deriveprops" => Ok(Self::Deriveprops), + "owned-props" => Ok(Self::OwnedProps), + "borrowed-props" => Ok(Self::BorrowedProps), + "prop-options" => Ok(Self::PropOptions), + "optional-props" => Ok(Self::OptionalProps), + "explicitly-required-option-s" => Ok(Self::ExplicitlyRequiredOptionS), + "default-props" => Ok(Self::DefaultProps), + "automatic-conversion-with-into" => Ok(Self::AutomaticConversionWithInto), + "the-inline-props-macro" => Ok(Self::TheInlinePropsMacro), + _ => Err(DescribingUiComponentPropsSectionParseError), + } + } +} +impl std::fmt::Display for DescribingUiComponentPropsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ComponentProps => f.write_str("component-props"), + Self::Deriveprops => f.write_str("deriveprops"), + Self::OwnedProps => f.write_str("owned-props"), + Self::BorrowedProps => f.write_str("borrowed-props"), + Self::PropOptions => f.write_str("prop-options"), + Self::OptionalProps => f.write_str("optional-props"), + Self::ExplicitlyRequiredOptionS => f.write_str("explicitly-required-option-s"), + Self::DefaultProps => f.write_str("default-props"), + Self::AutomaticConversionWithInto => f.write_str("automatic-conversion-with-into"), + Self::TheInlinePropsMacro => f.write_str("the-inline-props-macro"), + } + } +} +#[derive(Debug)] +pub struct DescribingUiComponentPropsSectionParseError; +impl std::fmt::Display for DescribingUiComponentPropsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of DescribingUiComponentPropsSectioncomponent-props, deriveprops, owned-props, borrowed-props, prop-options, optional-props, explicitly-required-option-s, default-props, automatic-conversion-with-into, the-inline-props-macro", + )?; + Ok(()) + } +} +impl std::error::Error for DescribingUiComponentPropsSectionParseError {} +#[component(no_case_check)] +pub fn DescribingUiComponentProps( + section: DescribingUiComponentPropsSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "component-props", + Link { + to: BookRoute::DescribingUiComponentProps { + section: DescribingUiComponentPropsSection::ComponentProps, + }, + class: "header", + "Component Props" + } + } + p { + "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." + } + h2 { id: "deriveprops", + Link { + to: BookRoute::DescribingUiComponentProps { + section: DescribingUiComponentPropsSection::Deriveprops, + }, + class: "header", + "derive(Props)" + } + } + p { + "Component props are a single struct annotated with " + code { "#[derive(Props)]" } + ". For a component to accept props, the type of its argument must be " + code { "Scope" } + ". Then, you can access the value of the props using " + code { "cx.props" } + "." + } + p { "There are 2 flavors of Props structs:" } + ul { + li { + "Owned props:" + ul { + li { "Don't have an associated lifetime" } + li { + "Implement " + code { "PartialEq" } + ", allow for memoization (if the props don't change, Dioxus won't re-render the component)" + } + } + } + li { + "Borrowed props:" + ul { + li { + Link { to: "https://doc.rust-lang.org/beta/rust-by-example/scope/borrow.html", + "Borrow" + } + " from a parent component" + } + li { "Cannot be memoized due to lifetime constraints" } + } + } + } + h3 { id: "owned-props", + Link { + to: BookRoute::DescribingUiComponentProps { + section: DescribingUiComponentPropsSection::OwnedProps, + }, + class: "header", + "Owned Props" + } + } + p { "Owned Props are very simple – they don't borrow anything. Example:" } + CodeBlock { + contents: "
\n// Remember: Owned props must implement `PartialEq`!\n#[derive(PartialEq, Props)]\nstruct LikesProps {{\n    score: i32,\n}}\n\nfn Likes(cx: Scope<LikesProps>) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            "This post has ",\n            b {{ "{{cx.props.score}}" }},\n            " likes"\n        }}\n    }})\n}}
\n", + name: "component_owned_props.rs".to_string(), + } + p { + "You can then pass prop values to the component the same way you would pass attributes to an element:" + } + CodeBlock { + contents: "
\nfn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        Likes {{\n            score: 42,\n        }},\n    }})\n}}
\n", + name: "component_owned_props.rs".to_string(), + } + p { + img { + src: asset!( + "/assets/blog/release-03/component_owned_props_screenshot.png", + ImageAssetOptions::new().with_webp() + ), + alt: "Screenshot: Likes component", + title: "", + } + } + h3 { id: "borrowed-props", + Link { + to: BookRoute::DescribingUiComponentProps { + section: DescribingUiComponentPropsSection::BorrowedProps, + }, + class: "header", + "Borrowed Props" + } + } + p { + "Owned props work 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 " + code { "App" } + " Component to a " + code { "TitleCard" } + " subcomponent? A naive solution might be to " + Link { to: "https://doc.rust-lang.org/std/clone/trait.Clone.html", + code { ".clone()" } + } + " the String, creating a copy of it for the subcomponent – but this would be inefficient, especially for larger Strings." + } + p { + "Rust allows for something more efficient – borrowing the String as a " + code { "&str" } + " – this is what Borrowed Props are for!" + } + CodeBlock { + contents: "
\n#[derive(Props)]\nstruct TitleCardProps<'a> {{\n    title: &'a str,\n}}\n\nfn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element {{\n    cx.render(rsx! {{\n        h1 {{ "{{cx.props.title}}" }}\n    }})\n}}
\n", + name: "component_borrowed_props.rs".to_string(), + } + p { "We can then use the component like this:" } + CodeBlock { + contents: "
\nfn App(cx: Scope) -> Element {{\n    let hello = "Hello Dioxus!";\n\n    cx.render(rsx!(TitleCard {{ title: hello }}))\n}}
\n", + name: "component_borrowed_props.rs".to_string(), + } + p { + img { + src: asset!( + "/assets/blog/release-03/component_borrowed_props_screenshot.png", + ImageAssetOptions::new().with_webp() + ), + alt: "Screenshot: TitleCard component", + title: "", + } + } + p { + "Borrowed props can be very useful, but they do not allow for memorization so they will " + em { "always" } + " rerun when the parent scope is rerendered. Because of this Borrowed Props should be reserved for components that are cheap to rerun or places where cloning data is an issue. Using Borrowed Props everywhere will result in large parts of your app rerunning every interaction." + } + h2 { id: "prop-options", + Link { + to: BookRoute::DescribingUiComponentProps { + section: DescribingUiComponentPropsSection::PropOptions, + }, + class: "header", + "Prop Options" + } + } + p { + "The " + code { "#[derive(Props)]" } + " macro has some features that let you customize the behavior of props." + } + h3 { id: "optional-props", + Link { + to: BookRoute::DescribingUiComponentProps { + section: DescribingUiComponentPropsSection::OptionalProps, + }, + class: "header", + "Optional Props" + } + } + p { + "You can create optional fields by using the " + code { "Option<…>" } + " type for a field:" + } + CodeBlock { + contents: "
\n#[derive(Props)]\nstruct OptionalProps<'a> {{\n    title: &'a str,\n    subtitle: Option<&'a str>,\n}}\n\nfn Title<'a>(cx: Scope<'a, OptionalProps>) -> Element<'a> {{\n    cx.render(rsx!(h1{{\n        "{{cx.props.title}}: ",\n        cx.props.subtitle.unwrap_or("No subtitle provided"),\n    }}))\n}}
\n", + name: "component_props_options.rs".to_string(), + } + p { "Then, you can choose to either provide them or not:" } + CodeBlock { + contents: "
\nTitle {{\ntitle: "Some Title",\n}},\nTitle {{\ntitle: "Some Title",\nsubtitle: "Some Subtitle",\n}},\n// Providing an Option explicitly won't compile though:\n// Title {{\n//     title: "Some Title",\n//     subtitle: None,\n// }},
\n", + name: "component_props_options.rs".to_string(), + } + h3 { id: "explicitly-required-option-s", + Link { + to: BookRoute::DescribingUiComponentProps { + section: DescribingUiComponentPropsSection::ExplicitlyRequiredOptionS, + }, + class: "header", + "Explicitly Required Option s" + } + } + p { + "If you want to explicitly require an " + code { "Option" } + ", and not an optional prop, you can annotate it with " + code { "#[props(!optional)]" } + ":" + } + CodeBlock { + contents: "
\n#[derive(Props)]\nstruct ExplicitOptionProps<'a> {{\n    title: &'a str,\n    #[props(!optional)]\n    subtitle: Option<&'a str>,\n}}\n\nfn ExplicitOption<'a>(cx: Scope<'a, ExplicitOptionProps>) -> Element<'a> {{\n    cx.render(rsx!(h1 {{\n        "{{cx.props.title}}: ",\n        cx.props.subtitle.unwrap_or("No subtitle provided"),\n    }}))\n}}
\n", + name: "component_props_options.rs".to_string(), + } + p { + "Then, you have to explicitly pass either " + code { "Some(\"str\")" } + " or " + code { "None" } + ":" + } + CodeBlock { + contents: "
\nExplicitOption {{\ntitle: "Some Title",\nsubtitle: None,\n}},\nExplicitOption {{\ntitle: "Some Title",\nsubtitle: Some("Some Title"),\n}},\n// This won't compile:\n// ExplicitOption {{\n//     title: "Some Title",\n// }},
\n", + name: "component_props_options.rs".to_string(), + } + h3 { id: "default-props", + Link { + to: BookRoute::DescribingUiComponentProps { + section: DescribingUiComponentPropsSection::DefaultProps, + }, + class: "header", + "Default Props" + } + } + p { + "You can use " + code { "#[props(default = 42)]" } + " to make a field optional and specify its default value:" + } + CodeBlock { + contents: "
\n#[derive(PartialEq, Props)]\nstruct DefaultProps {{\n    // default to 42 when not provided\n    #[props(default = 42)]\n    number: i64,\n}}\n\nfn DefaultComponent(cx: Scope<DefaultProps>) -> Element {{\n    cx.render(rsx!(h1 {{ "{{cx.props.number}}" }}))\n}}
\n", + name: "component_props_options.rs".to_string(), + } + p { "Then, similarly to optional props, you don't have to provide it:" } + CodeBlock { + contents: "
\nDefaultComponent {{\nnumber: 5,\n}},\nDefaultComponent {{}},
\n", + name: "component_props_options.rs".to_string(), + } + h3 { id: "automatic-conversion-with-into", + Link { + to: BookRoute::DescribingUiComponentProps { + section: DescribingUiComponentPropsSection::AutomaticConversionWithInto, + }, + class: "header", + "Automatic Conversion with .into" + } + } + p { + "It is common for Rust functions to accept " + code { "impl Into" } + " rather than just " + code { "SomeType" } + " to support a wider range of parameters. If you want similar functionality with props, you can use " + code { "#[props(into)]" } + ". For example, you could add it on a " + code { "String" } + " prop – and " + code { "&str" } + " will also be automatically accepted, as it can be converted into " + code { "String" } + ":" + } + CodeBlock { + contents: "
\n#[derive(PartialEq, Props)]\nstruct IntoProps {{\n    #[props(into)]\n    string: String,\n}}\n\nfn IntoComponent(cx: Scope<IntoProps>) -> Element {{\n    cx.render(rsx!(h1 {{ "{{cx.props.string}}" }}))\n}}
\n", + name: "component_props_options.rs".to_string(), + } + p { "Then, you can use it so:" } + CodeBlock { + contents: "
\nIntoComponent {{\nstring: "some &str",\n}},
\n", + name: "component_props_options.rs".to_string(), + } + h2 { id: "the-inline-props-macro", + Link { + to: BookRoute::DescribingUiComponentProps { + section: DescribingUiComponentPropsSection::TheInlinePropsMacro, + }, + class: "header", + "The inline_props macro" + } + } + p { + "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 " + code { "cx.props.whatever" } + ", we could just use " + code { "whatever" } + " directly!" + } + p { + code { "inline_props" } + " allows you to do just that. Instead of typing the \"full\" version:" + } + CodeBlock { + contents: "
\n#[derive(Props, PartialEq)]\nstruct TitleCardProps {{\n    title: String,\n}}\n\nfn TitleCard(cx: Scope<TitleCardProps>) -> Element {{\n    cx.render(rsx!{{\n        h1 {{ "{{cx.props.title}}" }}\n    }})\n}}
\n", + } + p { + "...you can define a function that accepts props as arguments. Then, just annotate it with " + code { "#[inline_props]" } + ", and the macro will turn it into a regular Component for you:" + } + CodeBlock { contents: "
\n#[inline_props]\nfn TitleCard(cx: Scope, title: String) -> Element {{\n    cx.render(rsx!{{\n        h1 {{ "{{title}}" }}\n    }})\n}}
\n" } + blockquote { + p { + "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." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum DescribingUiComponentChildrenSection { + #[default] + Empty, + ComponentChildren, + TheChildrenField, +} +impl std::str::FromStr for DescribingUiComponentChildrenSection { + type Err = DescribingUiComponentChildrenSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "component-children" => Ok(Self::ComponentChildren), + "the-children-field" => Ok(Self::TheChildrenField), + _ => Err(DescribingUiComponentChildrenSectionParseError), + } + } +} +impl std::fmt::Display for DescribingUiComponentChildrenSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ComponentChildren => f.write_str("component-children"), + Self::TheChildrenField => f.write_str("the-children-field"), + } + } +} +#[derive(Debug)] +pub struct DescribingUiComponentChildrenSectionParseError; +impl std::fmt::Display for DescribingUiComponentChildrenSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of DescribingUiComponentChildrenSectioncomponent-children, the-children-field", + )?; + Ok(()) + } +} +impl std::error::Error for DescribingUiComponentChildrenSectionParseError {} +#[component(no_case_check)] +pub fn DescribingUiComponentChildren( + section: DescribingUiComponentChildrenSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "component-children", + Link { + to: BookRoute::DescribingUiComponentChildren { + section: DescribingUiComponentChildrenSection::ComponentChildren, + }, + class: "header", + "Component Children" + } + } + p { + "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 " + code { "Element" } + ":" + } + CodeBlock { + contents: "
\n#[derive(Props)]\nstruct ClickableProps<'a> {{\n    href: &'a str,\n    body: Element<'a>,\n}}\n\nfn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {{\n    cx.render(rsx!(\n        a {{\n            href: "{{cx.props.href}}",\n            class: "fancy-button",\n            &cx.props.body\n        }}\n    ))\n}}
\n", + name: "component_element_props.rs".to_string(), + } + p { + "Then, when rendering the component, you can pass in the output of " + code { "cx.render(rsx!(...))" } + ":" + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n    Clickable {{\n        href: "https://www.youtube.com/watch?v=C-M2hs3sXGo",\n        body: cx.render(rsx!("How to " i {{"not"}} " be seen")),\n    }}\n}})
\n", + name: "component_element_props.rs".to_string(), + } + blockquote { + p { + "Note: Since " + code { "Element<'a>" } + " is a borrowed prop, there will be no memoization." + } + } + blockquote { + p { + "Warning: While it may compile, do not include the same " + code { "Element" } + " more than once in the RSX. The resulting behavior is unspecified." + } + } + h2 { id: "the-children-field", + Link { + to: BookRoute::DescribingUiComponentChildren { + section: DescribingUiComponentChildrenSection::TheChildrenField, + }, + class: "header", + "The children field" + } + } + p { + "Rather than passing the RSX through a regular prop, you may wish to accept children similarly to how elements can have children. The \"magic\" " + code { "children" } + " prop lets you achieve this:" + } + CodeBlock { + contents: "
\n#[derive(Props)]\nstruct ClickableProps<'a> {{\n    href: &'a str,\n    children: Element<'a>,\n}}\n\nfn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {{\n    cx.render(rsx!(\n        a {{\n            href: "{{cx.props.href}}",\n            class: "fancy-button",\n            &cx.props.children\n        }}\n    ))\n}}
\n", + name: "component_children.rs".to_string(), + } + p { + "This makes using the component much simpler: simply put the RSX inside the " + code { "{{}}" } + " brackets – and there is no need for a " + code { "render" } + " call or another macro!" + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n    Clickable {{\n        href: "https://www.youtube.com/watch?v=C-M2hs3sXGo",\n        "How to " i {{"not"}} " be seen"\n    }}\n}})
\n", + name: "component_children.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum InteractivityIndexSection { + #[default] + Empty, + Interactivity, +} +impl std::str::FromStr for InteractivityIndexSection { + type Err = InteractivityIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "interactivity" => Ok(Self::Interactivity), + _ => Err(InteractivityIndexSectionParseError), + } + } +} +impl std::fmt::Display for InteractivityIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Interactivity => f.write_str("interactivity"), + } + } +} +#[derive(Debug)] +pub struct InteractivityIndexSectionParseError; +impl std::fmt::Display for InteractivityIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of InteractivityIndexSectioninteractivity", + )?; + Ok(()) + } +} +impl std::error::Error for InteractivityIndexSectionParseError {} +#[component(no_case_check)] +pub fn InteractivityIndex(section: InteractivityIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "interactivity", + Link { + to: BookRoute::InteractivityIndex { + section: InteractivityIndexSection::Interactivity, + }, + class: "header", + "Interactivity" + } + } + p { + "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." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum InteractivityEventHandlersSection { + #[default] + Empty, + EventHandlers, + TheEventObject, + EventPropagation, + PreventDefault, + HandlerProps, +} +impl std::str::FromStr for InteractivityEventHandlersSection { + type Err = InteractivityEventHandlersSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "event-handlers" => Ok(Self::EventHandlers), + "the-event-object" => Ok(Self::TheEventObject), + "event-propagation" => Ok(Self::EventPropagation), + "prevent-default" => Ok(Self::PreventDefault), + "handler-props" => Ok(Self::HandlerProps), + _ => Err(InteractivityEventHandlersSectionParseError), + } + } +} +impl std::fmt::Display for InteractivityEventHandlersSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::EventHandlers => f.write_str("event-handlers"), + Self::TheEventObject => f.write_str("the-event-object"), + Self::EventPropagation => f.write_str("event-propagation"), + Self::PreventDefault => f.write_str("prevent-default"), + Self::HandlerProps => f.write_str("handler-props"), + } + } +} +#[derive(Debug)] +pub struct InteractivityEventHandlersSectionParseError; +impl std::fmt::Display for InteractivityEventHandlersSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of InteractivityEventHandlersSectionevent-handlers, the-event-object, event-propagation, prevent-default, handler-props", + )?; + Ok(()) + } +} +impl std::error::Error for InteractivityEventHandlersSectionParseError {} +#[component(no_case_check)] +pub fn InteractivityEventHandlers( + section: InteractivityEventHandlersSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "event-handlers", + Link { + to: BookRoute::InteractivityEventHandlers { + section: InteractivityEventHandlersSection::EventHandlers, + }, + class: "header", + "Event Handlers" + } + } + p { + "Event handlers are used to respond to user actions. For example, an event handler could be triggered when the user clicks, scrolls, moves the mouse, or types a character." + } + p { + "Event handlers are attached to elements. For example, we usually don't care about all the clicks that happen within an app, only those on a particular button." + } + p { + "Event handlers are similar to regular attributes, but their name usually starts with " + code { "on" } + "- and they accept closures as values. The closure will be called whenever the event it listens for is triggered and will be passed that event." + } + p { + "For example, to handle clicks on an element, we can specify an " + code { "onclick" } + " handler:" + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\nbutton {{\n    onclick: move |event| println!("Clicked! Event: {{event:?}}"),\n    "click me!"\n}}\n}})
\n", + name: "event_click.rs".to_string(), + } + h2 { id: "the-event-object", + Link { + to: BookRoute::InteractivityEventHandlers { + section: InteractivityEventHandlersSection::TheEventObject, + }, + class: "header", + "The Event object" + } + } + p { + "Event handlers receive an " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/struct.Event.html", + code { "Event" } + } + " object containing information about the event. Different types of events contain different types of data. For example, mouse-related events contain " + Link { to: "https://docs.rs/dioxus/latest/dioxus/events/struct.MouseData.html", + code { "MouseData" } + } + ", which tells you things like where the mouse was clicked and what mouse buttons were used." + } + p { "In the example above, this event data was logged to the terminal:" } + CodeBlock { + contents: "
\nClicked! Event: UiEvent {{ bubble_state: Cell {{ value: true }}, data: MouseData {{ coordinates: Coordinates {{ screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }}, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) }} }}\nClicked! Event: UiEvent {{ bubble_state: Cell {{ value: true }}, data: MouseData {{ coordinates: Coordinates {{ screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }}, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) }} }}
\n", + } + p { + "To learn what the different event types for HTML provide, read the " + Link { to: "https://docs.rs/dioxus-html/latest/dioxus_html/events/index.html", + "events module docs" + } + "." + } + h3 { id: "event-propagation", + Link { + to: BookRoute::InteractivityEventHandlers { + section: InteractivityEventHandlersSection::EventPropagation, + }, + class: "header", + "Event propagation" + } + } + p { + "Some events will trigger first on the element the event originated at upward. For example, a click event on a " + code { "button" } + " inside a " + code { "div" } + " would first trigger the button's event listener and then the div's event listener." + } + blockquote { + p { + "For more information about event propigation see " + Link { to: "https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling", + "the mdn docs on event bubling" + } + } + } + p { + "If you want to prevent this behavior, you can call " + code { "stop_propogation()" } + " on the event:" + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\ndiv {{\n    onclick: move |_event| {{}},\n    "outer",\n    button {{\n        onclick: move |event| {{\n            // now, outer won't be triggered\n            event.stop_propagation();\n        }},\n        "inner"\n    }}\n}}\n}})
\n", + name: "event_nested.rs".to_string(), + } + h2 { id: "prevent-default", + Link { + to: BookRoute::InteractivityEventHandlers { + section: InteractivityEventHandlersSection::PreventDefault, + }, + class: "header", + "Prevent Default" + } + } + p { + "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." + } + p { + "In some instances, might want to avoid this default behavior. For this, you can add the " + code { "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:" + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\ninput {{\n    prevent_default: "oninput",\n    prevent_default: "onclick",\n}}\n}})
\n", + name: "event_prevent_default.rs".to_string(), + } + p { "Any event handlers will still be called." } + blockquote { + p { + "Normally, in React or JavaScript, you'd call \"preventDefault\" on the event in the callback. Dioxus does " + em { "not" } + " currently support this behavior. Note: this means you cannot conditionally prevent default behavior based on the data in the event." + } + } + h2 { id: "handler-props", + Link { + to: BookRoute::InteractivityEventHandlers { + section: InteractivityEventHandlersSection::HandlerProps, + }, + class: "header", + "Handler Props" + } + } + p { + "Sometimes, you might want to make a component that accepts an event handler. A simple example would be a " + code { "FancyButton" } + " component, which accepts an " + code { "on_click" } + " handler:" + } + CodeBlock { + contents: "
\n#[derive(Props)]\npub struct FancyButtonProps<'a> {{\n    on_click: EventHandler<'a, MouseEvent>,\n}}\n\npub fn FancyButton<'a>(cx: Scope<'a, FancyButtonProps<'a>>) -> Element<'a> {{\n    cx.render(rsx!(button {{\n        class: "fancy-button",\n        onclick: move |evt| cx.props.on_click.call(evt),\n        "click me pls."\n    }}))\n}}
\n", + name: "event_handler_prop.rs".to_string(), + } + p { "Then, you can use it like any other handler:" } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n    FancyButton {{\n        on_click: move |event| println!("Clicked! {{event:?}}")\n    }}\n}})
\n", + name: "event_handler_prop.rs".to_string(), + } + blockquote { + p { + "Note: just like any other attribute, you can name the handlers anything you want! Though they must start with " + code { "on" } + ", for the prop to be automatically turned into an " + code { "EventHandler" } + " at the call site." + } + p { + "You can also put custom data in the event, rather than e.g. " + code { "MouseData" } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum InteractivityHooksSection { + #[default] + Empty, + HooksAndComponentState, + UseStateHook, + RulesOfHooks, + NoHooksInConditionals, + NoHooksInClosures, + NoHooksInLoops, + UseRefHook, +} +impl std::str::FromStr for InteractivityHooksSection { + type Err = InteractivityHooksSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "hooks-and-component-state" => Ok(Self::HooksAndComponentState), + "use-state-hook" => Ok(Self::UseStateHook), + "rules-of-hooks" => Ok(Self::RulesOfHooks), + "no-hooks-in-conditionals" => Ok(Self::NoHooksInConditionals), + "no-hooks-in-closures" => Ok(Self::NoHooksInClosures), + "no-hooks-in-loops" => Ok(Self::NoHooksInLoops), + "use-ref-hook" => Ok(Self::UseRefHook), + _ => Err(InteractivityHooksSectionParseError), + } + } +} +impl std::fmt::Display for InteractivityHooksSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::HooksAndComponentState => f.write_str("hooks-and-component-state"), + Self::UseStateHook => f.write_str("use-state-hook"), + Self::RulesOfHooks => f.write_str("rules-of-hooks"), + Self::NoHooksInConditionals => f.write_str("no-hooks-in-conditionals"), + Self::NoHooksInClosures => f.write_str("no-hooks-in-closures"), + Self::NoHooksInLoops => f.write_str("no-hooks-in-loops"), + Self::UseRefHook => f.write_str("use-ref-hook"), + } + } +} +#[derive(Debug)] +pub struct InteractivityHooksSectionParseError; +impl std::fmt::Display for InteractivityHooksSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of InteractivityHooksSectionhooks-and-component-state, use-state-hook, rules-of-hooks, no-hooks-in-conditionals, no-hooks-in-closures, no-hooks-in-loops, use-ref-hook", + )?; + Ok(()) + } +} +impl std::error::Error for InteractivityHooksSectionParseError {} +#[component(no_case_check)] +pub fn InteractivityHooks(section: InteractivityHooksSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "hooks-and-component-state", + Link { + to: BookRoute::InteractivityHooks { + section: InteractivityHooksSection::HooksAndComponentState, + }, + class: "header", + "Hooks and Component State" + } + } + p { + "So far our components have had no state like a normal rust functions. 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 opened a drop-down, and render different things accordingly." + } + p { + "Hooks allow us to create state in our components. Hooks are Rust functions that take a reference to " + code { "ScopeState" } + " (in a component, you can pass " + code { "cx" } + "), and provide you with functionality and state." + } + h2 { id: "use-state-hook", + Link { + to: BookRoute::InteractivityHooks { + section: InteractivityHooksSection::UseStateHook, + }, + class: "header", + "use_state Hook" + } + } + p { + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_state.html", + code { "use_state" } + } + " is one of the simplest hooks." + } + ul { + li { "You provide a closure that determines the initial value" } + li { + code { "use_state" } + " gives you the current value, and a way to update it by setting it to something else" + } + li { + "When the value updates, " + code { "use_state" } + " makes the component re-render, and provides you with the new value" + } + } + p { + "For example, you might have seen the counter example, in which state (a number) is tracked using the " + code { "use_state" } + " hook:" + } + CodeBlock { + contents: "
\nfn App(cx: Scope) -> Element {{\n    // count will be initialized to 0 the first time the component is rendered\n    let mut count = use_state(cx, || 0);\n\n    cx.render(rsx!(\n        h1 {{ "High-Five counter: {{count}}" }}\n        button {{\n            onclick: move |_| {{\n                // changing the count will cause the component to re-render\n                count += 1\n            }},\n            "Up high!"\n        }}\n        button {{\n            onclick: move |_| {{\n                // changing the count will cause the component to re-render\n                count -= 1\n            }},\n            "Down low!"\n        }}\n    ))\n}}
\n", + name: "hooks_counter.rs".to_string(), + } + p { + img { + src: asset!("/assets/blog/release-03/counter.png", ImageAssetOptions::new().with_webp()), + alt: "Screenshot: counter app", + title: "", + } + } + p { + "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!" + } + blockquote { + p { + code { "use_state" } + " returns your value wrapped in a smart pointer of type " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseState.html", + code { "UseState" } + } + ". This is why you can both read the value and update it, even within an event handler." + } + } + p { "You can use multiple hooks in the same component if you want:" } + CodeBlock { + contents: "
\nfn App(cx: Scope) -> Element {{\n    let mut count_a = use_state(cx, || 0);\n    let mut count_b = use_state(cx, || 0);\n\n    cx.render(rsx!(\n        h1 {{ "Counter_a: {{count_a}}" }}\n        button {{ onclick: move |_| count_a += 1, "a++" }}\n        button {{ onclick: move |_| count_a -= 1, "a--" }}\n        h1 {{ "Counter_b: {{count_b}}" }}\n        button {{ onclick: move |_| count_b += 1, "b++" }}\n        button {{ onclick: move |_| count_b -= 1, "b--" }}\n    ))\n}}
\n", + name: "hooks_counter_two_state.rs".to_string(), + } + p { + img { + src: asset!( + "/assets/blog/release-03/counter_two_state.png", ImageAssetOptions::new() + .with_webp() + ), + alt: "Screenshot: app with two counters", + title: "", + } + } + h2 { id: "rules-of-hooks", + Link { + to: BookRoute::InteractivityHooks { + section: InteractivityHooksSection::RulesOfHooks, + }, + class: "header", + "Rules of Hooks" + } + } + p { + "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 " + code { "ScopeState" } + ", which is why you must pass " + code { "&cx" } + " to them." + } + p { + "But how can Dioxus differentiate between multiple hooks in the same component? As you saw in the second example, both " + code { "use_state" } + " functions were called with the same parameters, so how come they can return different things when the counters are different?" + } + CodeBlock { + contents: "
\nlet mut count_a = use_state(cx, || 0);\nlet mut count_b = use_state(cx, || 0);
\n", + name: "hooks_counter_two_state.rs".to_string(), + } + p { + "This is only possible because the two hooks are always called in the same order, so Dioxus knows which is which. Because the order you call hooks matters, you must follow certain rules when using hooks:" + } + ol { + li { "Hooks may be only used in components or other hooks (we'll get to that later)" } + li { + "On every call to the component function" + ol { + li { "The same hooks must be called" } + li { "In the same order" } + } + } + li { + "Hooks name's should start with " + code { "use_" } + " so you don't accidentally confuse them with regular functions" + } + } + p { "These rules mean that there are certain things you can't do with hooks:" } + h3 { id: "no-hooks-in-conditionals", + Link { + to: BookRoute::InteractivityHooks { + section: InteractivityHooksSection::NoHooksInConditionals, + }, + class: "header", + "No Hooks in Conditionals" + } + } + CodeBlock { + contents: "
\n// ❌ don't call hooks in conditionals!\n// We must ensure that the same hooks will be called every time\n// But `if` statements only run if the conditional is true!\n// So we might violate rule 2.\nif you_are_happy && you_know_it {{\nlet something = use_state(cx, || "hands");\nprintln!("clap your {{something}}")\n}}\n\n// ✅ instead, *always* call use_state\n// You can put other stuff in the conditional though\nlet something = use_state(cx, || "hands");\nif you_are_happy && you_know_it {{\nprintln!("clap your {{something}}")\n}}
\n", + name: "hooks_bad.rs".to_string(), + } + h3 { id: "no-hooks-in-closures", + Link { + to: BookRoute::InteractivityHooks { + section: InteractivityHooksSection::NoHooksInClosures, + }, + class: "header", + "No Hooks in Closures" + } + } + CodeBlock { + contents: "
\n// ❌ don't call hooks inside closures!\n// We can't guarantee that the closure, if used, will be called in the same order every time\nlet _a = || {{\nlet b = use_state(cx, || 0);\nb.get()\n}};\n\n// ✅ instead, move hook `b` outside\nlet b = use_state(cx, || 0);\nlet _a = || b.get();
\n", + name: "hooks_bad.rs".to_string(), + } + h3 { id: "no-hooks-in-loops", + Link { + to: BookRoute::InteractivityHooks { + section: InteractivityHooksSection::NoHooksInLoops, + }, + class: "header", + "No Hooks in Loops" + } + } + CodeBlock { + contents: "
\n// `names` is a Vec<&str>\n\n// ❌ Do not use hooks in loops!\n// In this case, if the length of the Vec changes, we break rule 2\nfor _name in &names {{\nlet is_selected = use_state(cx, || false);\nprintln!("selected: {{is_selected}}");\n}}\n\n// ✅ Instead, use a hashmap with use_ref\nlet selection_map = use_ref(cx, HashMap::<&str, bool>::new);\n\nfor name in &names {{\nlet is_selected = selection_map.read()[name];\nprintln!("selected: {{is_selected}}");\n}}
\n", + name: "hooks_bad.rs".to_string(), + } + h2 { id: "use-ref-hook", + Link { + to: BookRoute::InteractivityHooks { + section: InteractivityHooksSection::UseRefHook, + }, + class: "header", + "use_ref Hook" + } + } + p { + code { "use_state" } + " is great for tracking simple values. However, you may notice in the " + Link { to: "https://docs.rs/dioxus/latest/dioxus/hooks/struct.UseState.html", + code { "UseState" } + " API" + } + " that the only way to modify its value is to replace it with something else (e.g., by calling " + code { "set" } + ", or through one of the " + code { "+=" } + ", " + code { "-=" } + " 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?" + } + p { + "For example, suppose we want to maintain a " + code { "Vec" } + " of values. If we stored it with " + code { "use_state" } + ", the only way to add a new value to the list would be to create a new " + code { "Vec" } + " with the additional value, and put it in the state. This is expensive! We want to modify the existing " + code { "Vec" } + " instead." + } + p { + "Thankfully, there is another hook for that, " + code { "use_ref" } + "! It is similar to " + code { "use_state" } + ", but it lets you get a mutable reference to the contained data." + } + p { + "Here's a simple example that keeps a list of events in a " + code { "use_ref" } + ". We can acquire write access to the state with " + code { ".with_mut()" } + ", and then just " + code { ".push" } + " a new value to the state:" + } + CodeBlock { + contents: "
\nfn App(cx: Scope) -> Element {{\n    let list = use_ref(cx, Vec::new);\n\n    cx.render(rsx!(\n        p {{ "Current list: {{list.read():?}}" }}\n        button {{\n            onclick: move |event| {{\n                list.with_mut(|list| list.push(event));\n            }},\n            "Click me!"\n        }}\n    ))\n}}
\n", + name: "hooks_use_ref.rs".to_string(), + } + blockquote { + p { + "The return values of " + code { "use_state" } + " and " + code { "use_ref" } + " ( " + code { "UseState" } + " and " + code { "UseRef" } + ", respectively) are in some ways similar to " + Link { to: "https://doc.rust-lang.org/std/cell/", + code { "Cell" } + } + " and " + Link { to: "https://doc.rust-lang.org/std/cell/struct.RefCell.html", + code { "RefCell" } + } + " – they provide interior mutability. However, these Dioxus wrappers also ensure that the component gets re-rendered whenever you change the state." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum InteractivityUserInputSection { + #[default] + Empty, + UserInput, + ControlledInputs, + UncontrolledInputs, +} +impl std::str::FromStr for InteractivityUserInputSection { + type Err = InteractivityUserInputSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "user-input" => Ok(Self::UserInput), + "controlled-inputs" => Ok(Self::ControlledInputs), + "uncontrolled-inputs" => Ok(Self::UncontrolledInputs), + _ => Err(InteractivityUserInputSectionParseError), + } + } +} +impl std::fmt::Display for InteractivityUserInputSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::UserInput => f.write_str("user-input"), + Self::ControlledInputs => f.write_str("controlled-inputs"), + Self::UncontrolledInputs => f.write_str("uncontrolled-inputs"), + } + } +} +#[derive(Debug)] +pub struct InteractivityUserInputSectionParseError; +impl std::fmt::Display for InteractivityUserInputSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of InteractivityUserInputSectionuser-input, controlled-inputs, uncontrolled-inputs", + )?; + Ok(()) + } +} +impl std::error::Error for InteractivityUserInputSectionParseError {} +#[component(no_case_check)] +pub fn InteractivityUserInput(section: InteractivityUserInputSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "user-input", + Link { + to: BookRoute::InteractivityUserInput { + section: InteractivityUserInputSection::UserInput, + }, + class: "header", + "User Input" + } + } + p { + "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." + } + h2 { id: "controlled-inputs", + Link { + to: BookRoute::InteractivityUserInput { + section: InteractivityUserInputSection::ControlledInputs, + }, + class: "header", + "Controlled Inputs" + } + } + p { + "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:" + } + CodeBlock { + contents: "
\nfn App(cx: Scope) -> Element {{\n    let name = use_state(cx, || "bob".to_string());\n\n    cx.render(rsx! {{\n        input {{\n            // we tell the component what to render\n            value: "{{name}}",\n            // and what to do when the value changes\n            oninput: move |evt| name.set(evt.value.clone()),\n        }}\n    }})\n}}
\n", + name: "input_controlled.rs".to_string(), + } + p { "Notice the flexibility – you can:" } + ul { + li { "Also display the same contents in another element, and they will be in sync" } + li { "Transform the input every time it is modified (e.g. to make sure it is upper case)" } + li { "Validate the input every time it changes" } + li { + "Have custom logic happening when the input changes (e.g. network request for autocompletion)" + } + li { + "Programmatically change the value (e.g. a \"randomize\" button that fills the input with nonsense)" + } + } + h2 { id: "uncontrolled-inputs", + Link { + to: BookRoute::InteractivityUserInput { + section: InteractivityUserInputSection::UncontrolledInputs, + }, + class: "header", + "Uncontrolled Inputs" + } + } + p { + "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 will be editable anyway (this is built into the browser). This approach can be more performant, but less flexible. For example, it's harder to keep the input in sync with another element." + } + p { + "Since you don't necessarily have the current value of the uncontrolled input in state, you can access it either by listening to " + code { "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. " + code { "oninput" } + " or " + code { "onsubmit" } + "):" + } + CodeBlock { + contents: "
\nfn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        form {{\n            onsubmit: move |event| {{\n                println!("Submitted! {{event:?}}")\n            }},\n            input {{ name: "name", }},\n            input {{ name: "age", }},\n            input {{ name: "date", }},\n            input {{ r#type: "submit", }},\n        }}\n    }})\n}}
\n", + name: "input_uncontrolled.rs".to_string(), + } + CodeBlock { contents: "
\nSubmitted! UiEvent {{ data: FormData {{ value: "", values: {{"age": "very old", "date": "1966", "name": "Fred"}} }} }}
\n" } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum InteractivitySharingStateSection { + #[default] + Empty, + SharingState, + LiftingState, + UsingContext, +} +impl std::str::FromStr for InteractivitySharingStateSection { + type Err = InteractivitySharingStateSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "sharing-state" => Ok(Self::SharingState), + "lifting-state" => Ok(Self::LiftingState), + "using-context" => Ok(Self::UsingContext), + _ => Err(InteractivitySharingStateSectionParseError), + } + } +} +impl std::fmt::Display for InteractivitySharingStateSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::SharingState => f.write_str("sharing-state"), + Self::LiftingState => f.write_str("lifting-state"), + Self::UsingContext => f.write_str("using-context"), + } + } +} +#[derive(Debug)] +pub struct InteractivitySharingStateSectionParseError; +impl std::fmt::Display for InteractivitySharingStateSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of InteractivitySharingStateSectionsharing-state, lifting-state, using-context", + )?; + Ok(()) + } +} +impl std::error::Error for InteractivitySharingStateSectionParseError {} +#[component(no_case_check)] +pub fn InteractivitySharingState( + section: InteractivitySharingStateSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "sharing-state", + Link { + to: BookRoute::InteractivitySharingState { + section: InteractivitySharingStateSection::SharingState, + }, + class: "header", + "Sharing State" + } + } + p { + "Often, multiple components need to access the same state. Depending on your needs, there are several ways to implement this." + } + h2 { id: "lifting-state", + Link { + to: BookRoute::InteractivitySharingState { + section: InteractivitySharingStateSection::LiftingState, + }, + class: "header", + "Lifting State" + } + } + p { + "One approach to share state between components is to \"lift\" it up to the nearest common ancestor. This means putting the " + code { "use_state" } + " hook in a parent component, and passing the needed values down as props." + } + p { + "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)." + } + blockquote { + p { + "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, maintainable, and performant (this is even more important for larger, complex apps)." + } + } + p { + "We start with a " + code { "Meme" } + " component, responsible for rendering a meme with a given caption:" + } + CodeBlock { + contents: "
\n#[inline_props]\nfn Meme<'a>(cx: Scope<'a>, caption: &'a str) -> Element<'a> {{\n    let container_style = r#"\n        position: relative;\n        width: fit-content;\n    "#;\n\n    let caption_container_style = r#"\n        position: absolute;\n        bottom: 0;\n        left: 0;\n        right: 0;\n        padding: 16px 8px;\n    "#;\n\n    let caption_style = r"\n        font-size: 32px;\n        margin: 0;\n        color: white;\n        text-align: center;\n    ";\n\n    cx.render(rsx!(\n        div {{\n            style: "{{container_style}}",\n            img {{\n                src: "https://i.imgflip.com/2zh47r.jpg",\n                height: "500px",\n            }},\n            div {{\n                style: "{{caption_container_style}}",\n                p {{\n                    style: "{{caption_style}}",\n                    "{{caption}}"\n                }}\n            }}\n        }}\n    ))\n}}
\n", + name: "meme_editor.rs".to_string(), + } + blockquote { + p { + "Note that the " + code { "Meme" } + " component is unaware where the caption is coming from – it could be stored in " + code { "use_state" } + ", " + code { "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!" + } + } + p { + "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 " + code { "Meme" } + " component? Instead, it should accept the current caption as a prop, as well as an event handler to delegate input events to:" + } + CodeBlock { + contents: "
\n#[inline_props]\nfn CaptionEditor<'a>(\n    cx: Scope<'a>,\n    caption: &'a str,\n    on_input: EventHandler<'a, FormEvent>,\n) -> Element<'a> {{\n    let input_style = r"\n        border: none;\n        background: cornflowerblue;\n        padding: 8px 16px;\n        margin: 0;\n        border-radius: 4px;\n        color: white;\n    ";\n\n    cx.render(rsx!(input {{\n        style: "{{input_style}}",\n        value: "{{caption}}",\n        oninput: move |event| on_input.call(event),\n    }}))\n}}
\n", + name: "meme_editor.rs".to_string(), + } + p { + "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." + } + CodeBlock { + contents: "
\nfn MemeEditor(cx: Scope) -> Element {{\n    let container_style = r"\n        display: flex;\n        flex-direction: column;\n        gap: 16px;\n        margin: 0 auto;\n        width: fit-content;\n    ";\n\n    let caption = use_state(cx, || "me waiting for my rust code to compile".to_string());\n\n    cx.render(rsx! {{\n        div {{\n            style: "{{container_style}}",\n            h1 {{ "Meme Editor" }},\n            Meme {{\n                caption: caption,\n            }},\n            CaptionEditor {{\n                caption: caption,\n                on_input: move |event: FormEvent| {{caption.set(event.value.clone());}},\n            }},\n        }}\n    }})\n}}
\n", + name: "meme_editor.rs".to_string(), + } + p { + img { + src: asset!( + "/assets/blog/release-03/meme_editor_screenshot.png", ImageAssetOptions::new() + .with_webp() + ), + alt: "Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: \"me waiting for a language feature\"", + title: "", + } + } + h2 { id: "using-context", + Link { + to: BookRoute::InteractivitySharingState { + section: InteractivitySharingStateSection::UsingContext, + }, + class: "header", + "Using Context" + } + } + p { + "Sometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient." + } + p { + "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." + } + blockquote { + p { + "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!" + } + } + p { + "Now, we could write another " + code { "use_state" } + " in the top component, and pass " + code { "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!" + } + p { + "Dioxus offers a better solution than this \"prop drilling\" – providing context. The " + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html", + code { "use_context_provider" } + } + " hook is similar to " + code { "use_ref" } + ", but it makes it available through " + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html", + code { "use_context" } + } + " for all children components." + } + p { "First, we have to create a struct for our dark mode configuration:" } + CodeBlock { + contents: "
\nstruct DarkMode(bool);
\n", + name: "meme_editor_dark_mode.rs".to_string(), + } + p { + "Now, in a top-level component (like " + code { "App" } + "), we can provide the " + code { "DarkMode" } + " context to all children components:" + } + CodeBlock { + contents: "
\nuse_shared_state_provider(cx, || DarkMode(false));
\n", + name: "meme_editor_dark_mode.rs".to_string(), + } + p { + "As a result, any child component of " + code { "App" } + " (direct or not), can access the " + code { "DarkMode" } + " context." + } + CodeBlock { + contents: "
\nlet dark_mode_context = use_shared_state::<DarkMode>(cx);
\n", + name: "meme_editor_dark_mode.rs".to_string(), + } + blockquote { + p { + code { "use_context" } + " returns " + code { "Option>" } + " here. If the context has been provided, the value is " + code { "Some(UseSharedState)" } + ", which you can call " + code { ".read" } + " or " + code { ".write" } + " on, similarly to " + code { "UseRef" } + ". Otherwise, the value is " + code { "None" } + "." + } + } + p { + "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):" + } + CodeBlock { + contents: "
\npub fn DarkModeToggle(cx: Scope) -> Element {{\n    let dark_mode = use_shared_state::<DarkMode>(cx).unwrap();\n\n    let style = if dark_mode.read().0 {{\n        "color:white"\n    }} else {{\n        ""\n    }};\n\n    cx.render(rsx!(label {{\n        style: "{{style}}",\n        "Dark Mode",\n        input {{\n            r#type: "checkbox",\n            oninput: move |event| {{\n                let is_enabled = event.value == "true";\n                dark_mode.write().0 = is_enabled;\n            }},\n        }},\n    }}))\n}}
\n", + name: "meme_editor_dark_mode.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum InteractivityCustomHooksSection { + #[default] + Empty, + CustomHooks, + ComposingHooks, + CustomHookLogic, +} +impl std::str::FromStr for InteractivityCustomHooksSection { + type Err = InteractivityCustomHooksSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "custom-hooks" => Ok(Self::CustomHooks), + "composing-hooks" => Ok(Self::ComposingHooks), + "custom-hook-logic" => Ok(Self::CustomHookLogic), + _ => Err(InteractivityCustomHooksSectionParseError), + } + } +} +impl std::fmt::Display for InteractivityCustomHooksSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::CustomHooks => f.write_str("custom-hooks"), + Self::ComposingHooks => f.write_str("composing-hooks"), + Self::CustomHookLogic => f.write_str("custom-hook-logic"), + } + } +} +#[derive(Debug)] +pub struct InteractivityCustomHooksSectionParseError; +impl std::fmt::Display for InteractivityCustomHooksSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of InteractivityCustomHooksSectioncustom-hooks, composing-hooks, custom-hook-logic", + )?; + Ok(()) + } +} +impl std::error::Error for InteractivityCustomHooksSectionParseError {} +#[component(no_case_check)] +pub fn InteractivityCustomHooks( + section: InteractivityCustomHooksSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "custom-hooks", + Link { + to: BookRoute::InteractivityCustomHooks { + section: InteractivityCustomHooksSection::CustomHooks, + }, + class: "header", + "Custom Hooks" + } + } + p { + "Hooks are a great way to encapsulate business logic. If none of the existing hooks work for your problem, you can write your own." + } + h2 { id: "composing-hooks", + Link { + to: BookRoute::InteractivityCustomHooks { + section: InteractivityCustomHooksSection::ComposingHooks, + }, + class: "header", + "Composing Hooks" + } + } + p { + "To avoid repetition, you can encapsulate business logic based on existing hooks to create a new hook." + } + p { + "For example, if many components need to access an " + code { "AppSettings" } + " struct, you can create a \"shortcut\" hook:" + } + CodeBlock { + contents: "
\nfn use_settings(cx: &ScopeState) -> UseSharedState<AppSettings> {{\n    use_shared_state::<AppSettings>(cx).expect("App settings not provided")\n}}
\n", + name: "hooks_composed.rs".to_string(), + } + h2 { id: "custom-hook-logic", + Link { + to: BookRoute::InteractivityCustomHooks { + section: InteractivityCustomHooksSection::CustomHookLogic, + }, + class: "header", + "Custom Hook Logic" + } + } + p { + "You can use " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.Scope.html#method.use_hook", + code { "cx.use_hook" } + } + " to build your own hooks. In fact, this is what all the standard hooks are built on!" + } + p { + code { "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." + } + blockquote { + p { + "Note: You can implement " + Link { to: "https://doc.rust-lang.org/std/ops/trait.Drop.html", + code { "Drop" } + } + " for your hook value – it will be dropped then the component is unmounted (no longer in the UI)" + } + } + p { + "Inside the initialization closure, you will typically make calls to other " + code { "cx" } + " methods. For example:" + } + ul { + li { + "The " + code { "use_state" } + " hook tracks state in the hook value, and uses " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.Scope.html#method.schedule_update", + code { "cx.schedule_update" } + } + " to make Dioxus re-render the component whenever it changes." + } + li { + "The " + code { "use_context" } + " hook calls " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.Scope.html#method.consume_context", + code { "cx.consume_context" } + } + " (which would be expensive to call on every render) to get some context from the scope" + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum InteractivityDynamicRenderingSection { + #[default] + Empty, + DynamicRendering, + ConditionalRendering, + ImprovingTheIfElseExample, + InspectingElementProps, + RenderingNothing, + RenderingLists, + InlineForLoops, + TheKeyAttribute, +} +impl std::str::FromStr for InteractivityDynamicRenderingSection { + type Err = InteractivityDynamicRenderingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "dynamic-rendering" => Ok(Self::DynamicRendering), + "conditional-rendering" => Ok(Self::ConditionalRendering), + "improving-the-if-else-example" => Ok(Self::ImprovingTheIfElseExample), + "inspecting-element-props" => Ok(Self::InspectingElementProps), + "rendering-nothing" => Ok(Self::RenderingNothing), + "rendering-lists" => Ok(Self::RenderingLists), + "inline-for-loops" => Ok(Self::InlineForLoops), + "the-key-attribute" => Ok(Self::TheKeyAttribute), + _ => Err(InteractivityDynamicRenderingSectionParseError), + } + } +} +impl std::fmt::Display for InteractivityDynamicRenderingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::DynamicRendering => f.write_str("dynamic-rendering"), + Self::ConditionalRendering => f.write_str("conditional-rendering"), + Self::ImprovingTheIfElseExample => f.write_str("improving-the-if-else-example"), + Self::InspectingElementProps => f.write_str("inspecting-element-props"), + Self::RenderingNothing => f.write_str("rendering-nothing"), + Self::RenderingLists => f.write_str("rendering-lists"), + Self::InlineForLoops => f.write_str("inline-for-loops"), + Self::TheKeyAttribute => f.write_str("the-key-attribute"), + } + } +} +#[derive(Debug)] +pub struct InteractivityDynamicRenderingSectionParseError; +impl std::fmt::Display for InteractivityDynamicRenderingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of InteractivityDynamicRenderingSectiondynamic-rendering, conditional-rendering, improving-the-if-else-example, inspecting-element-props, rendering-nothing, rendering-lists, inline-for-loops, the-key-attribute", + )?; + Ok(()) + } +} +impl std::error::Error for InteractivityDynamicRenderingSectionParseError {} +#[component(no_case_check)] +pub fn InteractivityDynamicRendering( + section: InteractivityDynamicRenderingSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "dynamic-rendering", + Link { + to: BookRoute::InteractivityDynamicRendering { + section: InteractivityDynamicRenderingSection::DynamicRendering, + }, + class: "header", + "Dynamic Rendering" + } + } + p { + "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!" + } + h2 { id: "conditional-rendering", + Link { + to: BookRoute::InteractivityDynamicRendering { + section: InteractivityDynamicRenderingSection::ConditionalRendering, + }, + class: "header", + "Conditional Rendering" + } + } + p { + "To render different elements based on a condition, you could use an " + code { "if-else" } + " statement:" + } + CodeBlock { + contents: "
\nif *is_logged_in {{\ncx.render(rsx! {{\n    "Welcome!"\n    button {{\n        onclick: move |_| on_log_out.call(()),\n        "Log Out",\n    }}\n}})\n}} else {{\ncx.render(rsx! {{\n    button {{\n        onclick: move |_| on_log_in.call(()),\n        "Log In",\n    }}\n}})\n}}
\n", + name: "conditional_rendering.rs".to_string(), + } + blockquote { + p { + "You could also use " + code { "match" } + " statements, or any Rust function to conditionally render different things." + } + } + h3 { id: "improving-the-if-else-example", + Link { + to: BookRoute::InteractivityDynamicRendering { + section: InteractivityDynamicRenderingSection::ImprovingTheIfElseExample, + }, + class: "header", + "Improving the if-else Example" + } + } + p { + "You may have noticed some repeated code in the " + code { "if-else" } + " example above. Repeating code like this is both bad for maintainability and performance. Dioxus will skip diffing static elements like the button, but when switching between multiple " + code { "rsx" } + " calls it cannot perform this optimization. For this example either approach is fine, but for components with large parts that are reused between conditionals, it can be more of an issue." + } + p { + "We can improve this example by splitting up the dynamic parts and inserting them where they are needed." + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n// We only render the welcome message if we are logged in\n// You can use if statements in the middle of a render block to conditionally render elements\nif *is_logged_in {{\n    // Notice the body of this if statment is rsx code, not an expression\n    "Welcome!"\n}}\nbutton {{\n    // depending on the value of `is_logged_in`, we will call a different event handler\n    onclick: move |_| if *is_logged_in {{\n        on_log_in.call(())\n    }}\n    else{{\n        on_log_out.call(())\n    }},\n    if *is_logged_in {{\n        // if we are logged in, the button should say "Log Out"\n        "Log Out"\n    }} else {{\n        // if we are not logged in, the button should say "Log In"\n        "Log In"\n    }}\n}}\n}})
\n", + name: "conditional_rendering.rs".to_string(), + } + h3 { id: "inspecting-element-props", + Link { + to: BookRoute::InteractivityDynamicRendering { + section: InteractivityDynamicRenderingSection::InspectingElementProps, + }, + class: "header", + "Inspecting Element props" + } + } + p { + "Since " + code { "Element" } + " is a " + code { "Option" } + ", components accepting " + code { "Element" } + " as a prop can inspect its contents, and render different things based on that. Example:" + } + CodeBlock { + contents: "
\nfn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {{\n    match cx.props.children {{\n        Some(VNode {{ dynamic_nodes, .. }}) => {{\n            todo!("render some stuff")\n        }}\n        _ => {{\n            todo!("render some other stuff")\n        }}\n    }}\n}}
\n", + name: "component_children_inspect.rs".to_string(), + } + p { + "You can't mutate the " + code { "Element" } + ", but if you need a modified version of it, you can construct a new one based on its attributes/children/etc." + } + h2 { id: "rendering-nothing", + Link { + to: BookRoute::InteractivityDynamicRendering { + section: InteractivityDynamicRenderingSection::RenderingNothing, + }, + class: "header", + "Rendering Nothing" + } + } + p { + "To render nothing, you can return " + code { "None" } + " from a component. This is useful if you want to conditionally hide something:" + } + CodeBlock { + contents: "
\nif *is_logged_in {{\nreturn None;\n}}\n\ncx.render(rsx! {{\na {{\n    "You must be logged in to comment"\n}}\n}})
\n", + name: "conditional_rendering.rs".to_string(), + } + p { + "This works because the " + code { "Element" } + " type is just an alias for " + code { "Option" } + } + blockquote { + p { + "Again, you may use a different method to conditionally return " + code { "None" } + ". For example the boolean's " + Link { to: "https://doc.rust-lang.org/std/primitive.bool.html#method.then", + code { "then()" } + } + " function could be used." + } + } + h2 { id: "rendering-lists", + Link { + to: BookRoute::InteractivityDynamicRendering { + section: InteractivityDynamicRenderingSection::RenderingLists, + }, + class: "header", + "Rendering Lists" + } + } + p { + "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." + } + p { + "For this, Dioxus accepts iterators that produce " + code { "Element" } + "s. So we need to:" + } + ul { + li { + "Get an iterator over all of our items (e.g., if you have a " + code { "Vec" } + " of comments, iterate over it with " + code { "iter()" } + ")" + } + li { + code { ".map" } + " the iterator to convert each item into a " + code { "LazyNode" } + " using " + code { "rsx!(...)" } + ul { + li { + "Add a unique " + code { "key" } + " attribute to each iterator item" + } + } + } + li { "Include this iterator in the final RSX (or use it inline)" } + } + p { + "Example: suppose you have a list of comments you want to render. Then, you can render them like this:" + } + CodeBlock { + contents: "
\nlet comment_field = use_state(cx, String::new);\nlet mut next_id = use_state(cx, || 0);\nlet comments = use_ref(cx, Vec::<Comment>::new);\n\nlet comments_lock = comments.read();\nlet comments_rendered = comments_lock.iter().map(|comment| {{\nrsx!(CommentComponent {{\n    key: "{{comment.id}}",\n    comment: comment.clone(),\n}})\n}});\n\ncx.render(rsx!(\nform {{\n    onsubmit: move |_| {{\n        comments.write().push(Comment {{\n            content: comment_field.get().clone(),\n            id: *next_id.get(),\n        }});\n        next_id += 1;\n\n        comment_field.set(String::new());\n    }},\n    input {{\n        value: "{{comment_field}}",\n        oninput: |event| comment_field.set(event.value.clone()),\n    }}\n    input {{\n        r#type: "submit",\n    }}\n}},\ncomments_rendered,\n))
\n", + name: "rendering_lists.rs".to_string(), + } + h3 { id: "inline-for-loops", + Link { + to: BookRoute::InteractivityDynamicRendering { + section: InteractivityDynamicRenderingSection::InlineForLoops, + }, + class: "header", + "Inline for loops" + } + } + p { + "Because of how common it is to render a list of items, Dioxus provides a shorthand for this. Instead of using " + code { ".iter, " } + ".map " + code { ", and " } + "rsx " + code { ", you can use a " } + "for" + " " + "`" + " loop with a body of rsx code:" + } + CodeBlock { + contents: "
\nlet comment_field = use_state(cx, String::new);\nlet mut next_id = use_state(cx, || 0);\nlet comments = use_ref(cx, Vec::<Comment>::new);\n\ncx.render(rsx!(\nform {{\n    onsubmit: move |_| {{\n        comments.write().push(Comment {{\n            content: comment_field.get().clone(),\n            id: *next_id.get(),\n        }});\n        next_id += 1;\n\n        comment_field.set(String::new());\n    }},\n    input {{\n        value: "{{comment_field}}",\n        oninput: |event| comment_field.set(event.value.clone()),\n    }}\n    input {{\n        r#type: "submit",\n    }}\n}},\nfor comment in &*comments.read() {{\n    // Notice the body of this for loop is rsx code, not an expression\n    CommentComponent {{\n        key: "{{comment.id}}",\n        comment: comment.clone(),\n    }}\n}}\n))
\n", + name: "rendering_lists.rs".to_string(), + } + h3 { id: "the-key-attribute", + Link { + to: BookRoute::InteractivityDynamicRendering { + section: InteractivityDynamicRenderingSection::TheKeyAttribute, + }, + class: "header", + "The key Attribute" + } + } + p { + "Every time you re-render your list, Dioxus needs to keep track of which items go where to determine what updates need to be made to the UI." + } + p { + "For example, suppose the " + code { "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!" + } + p { + "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 matter where you get the key from, as long as it meets the requirements:" + } + ul { + li { "Keys must be unique in a list" } + li { "The same item should always get associated with the same key" } + li { + "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" + } + } + p { + "You might be tempted to use an item's index in the list as its key. 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." + } + blockquote { + p { + "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." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum InteractivityRouterSection { + #[default] + Empty, + Router, + WhatIsIt, + UsingTheRouter, + Links, + MoreReading, +} +impl std::str::FromStr for InteractivityRouterSection { + type Err = InteractivityRouterSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "router" => Ok(Self::Router), + "what-is-it" => Ok(Self::WhatIsIt), + "using-the-router" => Ok(Self::UsingTheRouter), + "links" => Ok(Self::Links), + "more-reading" => Ok(Self::MoreReading), + _ => Err(InteractivityRouterSectionParseError), + } + } +} +impl std::fmt::Display for InteractivityRouterSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Router => f.write_str("router"), + Self::WhatIsIt => f.write_str("what-is-it"), + Self::UsingTheRouter => f.write_str("using-the-router"), + Self::Links => f.write_str("links"), + Self::MoreReading => f.write_str("more-reading"), + } + } +} +#[derive(Debug)] +pub struct InteractivityRouterSectionParseError; +impl std::fmt::Display for InteractivityRouterSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of InteractivityRouterSectionrouter, what-is-it, using-the-router, links, more-reading", + )?; + Ok(()) + } +} +impl std::error::Error for InteractivityRouterSectionParseError {} +#[component(no_case_check)] +pub fn InteractivityRouter(section: InteractivityRouterSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "router", + Link { + to: BookRoute::InteractivityRouter { + section: InteractivityRouterSection::Router, + }, + class: "header", + "Router" + } + } + p { + "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. For a desktop app, these scenes might be different views in your app." + } + p { + "To unify these platforms, Dioxus provides a first-party solution for scene management called Dioxus Router." + } + h2 { id: "what-is-it", + Link { + to: BookRoute::InteractivityRouter { + section: InteractivityRouterSection::WhatIsIt, + }, + class: "header", + "What is it?" + } + } + p { + "For an app like the Dioxus landing page (https://dioxuslabs.com), we want to have several different scenes:" + } + ul { + li { "Homepage" } + li { "Blog" } + } + p { + "Each of these scenes is independent – we don't want to render both the homepage and blog at the same time." + } + p { + "The Dioxus router makes it easy to create these scenes. To make sure we're using the router, add the " + code { "dioxus-router" } + " package to your " + code { "Cargo.toml" } + "." + } + CodeBlock { contents: "
\ncargo add dioxus-router
\n" } + h2 { id: "using-the-router", + Link { + to: BookRoute::InteractivityRouter { + section: InteractivityRouterSection::UsingTheRouter, + }, + class: "header", + "Using the router" + } + } + p { + "Unlike other routers in the Rust ecosystem, our router is built declaratively. This makes it possible to compose our app layout simply by arranging components." + } + CodeBlock { + contents: "
\nrsx!{{\n    // All of our routes will be rendered inside this Router component\n    Router {{\n        // if the current location is "/home", render the Home component\n        Route {{ to: "/home", Home {{}} }}\n        // if the current location is "/blog", render the Blog component\n        Route {{ to: "/blog", Blog {{}} }}\n    }}\n}}
\n", + } + p { + "Whenever we visit this app, we will get either the Home component or the Blog component rendered depending on which route we enter at. If neither of these routes match the current location, then nothing will render." + } + p { "We can fix this one of two ways:" } + ul { + li { "A fallback 404 page" } + } + CodeBlock { contents: "
\nrsx!{{\n    Router {{\n        Route {{ to: "/home", Home {{}} }}\n        Route {{ to: "/blog", Blog {{}} }}\n        //  if the current location doesn't match any of the above routes, render the NotFound component\n        Route {{ to: "", NotFound {{}} }}\n    }}\n}}
\n" } + ul { + li { "Redirect 404 to home" } + } + CodeBlock { + contents: "
\nrsx!{{\n    Router {{\n        Route {{ to: "/home", Home {{}} }}\n        Route {{ to: "/blog", Blog {{}} }}\n        //  if the current location doesn't match any of the above routes, redirect to "/home"\n        Redirect {{ from: "", to: "/home" }}\n    }}\n}}
\n", + } + h2 { id: "links", + Link { + to: BookRoute::InteractivityRouter { + section: InteractivityRouterSection::Links, + }, + class: "header", + "Links" + } + } + p { + "For our app to navigate these routes, we can provide clickable elements called Links. These simply wrap " + code { "" } + " elements that, when clicked, navigate the app to the given location." + } + CodeBlock { contents: "
\nrsx!{{\n    Link {{\n        to: "/home",\n        "Go home!"\n    }}\n}}
\n" } + h2 { id: "more-reading", + Link { + to: BookRoute::InteractivityRouter { + section: InteractivityRouterSection::MoreReading, + }, + class: "header", + "More reading" + } + } + p { + "This page is just a very brief overview of the router. For more information, check out " + Link { to: "https://dioxuslabs.com/router/guide/", "the router book" } + " or some of " + Link { to: "https://github.com/DioxusLabs/dioxus/blob/master/examples/router.rs", + "the router examples" + } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum AsyncIndexSection { + #[default] + Empty, + WorkingWithAsync, + TheRuntime, +} +impl std::str::FromStr for AsyncIndexSection { + type Err = AsyncIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "working-with-async" => Ok(Self::WorkingWithAsync), + "the-runtime" => Ok(Self::TheRuntime), + _ => Err(AsyncIndexSectionParseError), + } + } +} +impl std::fmt::Display for AsyncIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::WorkingWithAsync => f.write_str("working-with-async"), + Self::TheRuntime => f.write_str("the-runtime"), + } + } +} +#[derive(Debug)] +pub struct AsyncIndexSectionParseError; +impl std::fmt::Display for AsyncIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of AsyncIndexSectionworking-with-async, the-runtime", + )?; + Ok(()) + } +} +impl std::error::Error for AsyncIndexSectionParseError {} +#[component(no_case_check)] +pub fn AsyncIndex(section: AsyncIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "working-with-async", + Link { + to: BookRoute::AsyncIndex { + section: AsyncIndexSection::WorkingWithAsync, + }, + class: "header", + "Working with Async" + } + } + p { + "Often, apps need to interact with file systems, network interfaces, hardware, or timers. This chapter provides an overview of using async code in Dioxus." + } + h2 { id: "the-runtime", + Link { + to: BookRoute::AsyncIndex { + section: AsyncIndexSection::TheRuntime, + }, + class: "header", + "The Runtime" + } + } + p { + "By default, Dioxus-Desktop ships with the " + code { "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." + } + p { + "Dioxus is not currently thread-safe, so any async code you write does " + em { "not" } + " need to be " + code { "Send/Sync" } + ". That means that you can use non-thread-safe structures like " + code { "Cell" } + ", " + code { "Rc" } + ", and " + code { "RefCell" } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum AsyncUseFutureSection { + #[default] + Empty, + Usefuture, + RestartingTheFuture, + Dependencies, +} +impl std::str::FromStr for AsyncUseFutureSection { + type Err = AsyncUseFutureSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "usefuture" => Ok(Self::Usefuture), + "restarting-the-future" => Ok(Self::RestartingTheFuture), + "dependencies" => Ok(Self::Dependencies), + _ => Err(AsyncUseFutureSectionParseError), + } + } +} +impl std::fmt::Display for AsyncUseFutureSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Usefuture => f.write_str("usefuture"), + Self::RestartingTheFuture => f.write_str("restarting-the-future"), + Self::Dependencies => f.write_str("dependencies"), + } + } +} +#[derive(Debug)] +pub struct AsyncUseFutureSectionParseError; +impl std::fmt::Display for AsyncUseFutureSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of AsyncUseFutureSectionusefuture, restarting-the-future, dependencies", + )?; + Ok(()) + } +} +impl std::error::Error for AsyncUseFutureSectionParseError {} +#[component(no_case_check)] +pub fn AsyncUseFuture(section: AsyncUseFutureSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "usefuture", + Link { + to: BookRoute::AsyncUseFuture { + section: AsyncUseFutureSection::Usefuture, + }, + class: "header", + "UseFuture" + } + } + p { + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_future.html", + code { "use_future" } + } + " lets you run an async closure, and provides you with its result." + } + p { + "For example, we can make an API request (using " + Link { to: "https://docs.rs/reqwest/latest/reqwest/index.html", "reqwest" } + ") inside " + code { "use_future" } + ":" + } + CodeBlock { + contents: "
\nlet future = use_future(cx, (), |_| async move {{\n    reqwest::get("https://dog.ceo/api/breeds/image/random")\n        .await\n        .unwrap()\n        .json::<ApiResponse>()\n        .await\n}});
\n", + name: "use_future.rs".to_string(), + } + p { + "The code inside " + code { "use_future" } + " will be submitted to the Dioxus scheduler once the component has rendered." + } + p { + "We can use " + code { ".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 " + code { "None" } + ". However, once the future is finished, the component will be re-rendered and the value will now be " + code { "Some(...)" } + ", containing the return value of the closure." + } + p { "We can then render that result:" } + CodeBlock { + contents: "
\ncx.render(match future.value() {{\n    Some(Ok(response)) => rsx! {{\n        button {{\n            onclick: move |_| future.restart(),\n            "Click to fetch another doggo"\n        }}\n        div {{\n            img {{\n                max_width: "500px",\n                max_height: "500px",\n                src: "{{response.image_url}}",\n            }}\n        }}\n    }},\n    Some(Err(_)) => rsx! {{ div {{ "Loading dogs failed" }} }},\n    None => rsx! {{ div {{ "Loading dogs..." }} }},\n}})
\n", + name: "use_future.rs".to_string(), + } + h2 { id: "restarting-the-future", + Link { + to: BookRoute::AsyncUseFuture { + section: AsyncUseFutureSection::RestartingTheFuture, + }, + class: "header", + "Restarting the Future" + } + } + p { + "The " + code { "UseFuture" } + " handle provides a " + code { "restart" } + " method. It can be used to execute the future again, producing a new value." + } + h2 { id: "dependencies", + Link { + to: BookRoute::AsyncUseFuture { + section: AsyncUseFutureSection::Dependencies, + }, + class: "header", + "Dependencies" + } + } + p { + "Often, you will need to run the future again every time some value (e.g. a prop) changes. Rather than calling " + code { "restart" } + " 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:" + } + CodeBlock { + contents: "
\nlet future = use_future(cx, (breed,), |(breed,)| async move {{\n    reqwest::get(format!("https://dog.ceo/api/breed/{{breed}}/images/random"))\n        .await\n        .unwrap()\n        .json::<ApiResponse>()\n        .await\n}});
\n", + name: "use_future.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum AsyncUseCoroutineSection { + #[default] + Empty, + Coroutines, + UseCoroutine, + YieldingValues, + SendingValues, + AutomaticInjectionIntoTheContextApi, +} +impl std::str::FromStr for AsyncUseCoroutineSection { + type Err = AsyncUseCoroutineSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "coroutines" => Ok(Self::Coroutines), + "use-coroutine" => Ok(Self::UseCoroutine), + "yielding-values" => Ok(Self::YieldingValues), + "sending-values" => Ok(Self::SendingValues), + "automatic-injection-into-the-context-api" => { + Ok(Self::AutomaticInjectionIntoTheContextApi) + } + _ => Err(AsyncUseCoroutineSectionParseError), + } + } +} +impl std::fmt::Display for AsyncUseCoroutineSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Coroutines => f.write_str("coroutines"), + Self::UseCoroutine => f.write_str("use-coroutine"), + Self::YieldingValues => f.write_str("yielding-values"), + Self::SendingValues => f.write_str("sending-values"), + Self::AutomaticInjectionIntoTheContextApi => { + f.write_str("automatic-injection-into-the-context-api") + } + } + } +} +#[derive(Debug)] +pub struct AsyncUseCoroutineSectionParseError; +impl std::fmt::Display for AsyncUseCoroutineSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of AsyncUseCoroutineSectioncoroutines, use-coroutine, yielding-values, sending-values, automatic-injection-into-the-context-api", + )?; + Ok(()) + } +} +impl std::error::Error for AsyncUseCoroutineSectionParseError {} +#[component(no_case_check)] +pub fn AsyncUseCoroutine(section: AsyncUseCoroutineSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "coroutines", + Link { + to: BookRoute::AsyncUseCoroutine { + section: AsyncUseCoroutineSection::Coroutines, + }, + class: "header", + "Coroutines" + } + } + p { + "Another tool in your async toolbox are coroutines. Coroutines are futures that can be manually stopped, started, paused, and resumed." + } + p { + "Like regular futures, code in a coroutine will run until the next " + code { "await" } + " point before yielding. This low-level control over asynchronous tasks is quite powerful, allowing for infinitely looping tasks like WebSocket polling, background timers, and other periodic actions." + } + h2 { id: "use-coroutine", + Link { + to: BookRoute::AsyncUseCoroutine { + section: AsyncUseCoroutineSection::UseCoroutine, + }, + class: "header", + "use_coroutine" + } + } + p { + "The " + code { "use_coroutine" } + " hook allows you to create a coroutine. Most coroutines we write will be polling loops using async/await." + } + CodeBlock { + contents: "
\nfn app(cx: Scope) -> Element {{\n    let ws: &UseCoroutine<()> = use_coroutine(cx, |rx| async move {{\n        // Connect to some sort of service\n        let mut conn = connect_to_ws_server().await;\n\n        // Wait for data on the service\n        while let Some(msg) = conn.next().await {{\n            // handle messages\n        }}\n    }});\n}}
\n", + } + p { "For many services, a simple async loop will handle the majority of use cases." } + p { + "However, if we want to temporarily disable the coroutine, we can \"pause\" it using the " + code { "pause" } + " method, and \"resume\" it using the " + code { "resume" } + " method:" + } + CodeBlock { + contents: "
\nlet sync: &UseCoroutine<()> = use_coroutine(cx, |rx| async move {{\n    // code for syncing\n}});\n\nif sync.is_running() {{\n    cx.render(rsx!{{\n        button {{\n            onclick: move |_| sync.pause(),\n            "Disable syncing"\n        }}\n    }})\n}} else {{\n    cx.render(rsx!{{\n        button {{\n            onclick: move |_| sync.resume(),\n            "Enable syncing"\n        }}\n    }})\n}}
\n", + } + p { + "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." + } + h2 { id: "yielding-values", + Link { + to: BookRoute::AsyncUseCoroutine { + section: AsyncUseCoroutineSection::YieldingValues, + }, + class: "header", + "Yielding Values" + } + } + p { + "To yield values from a coroutine, simply bring in a " + code { "UseState" } + " handle and set the value whenever your coroutine completes its work." + } + p { + "The future must be " + code { "'static" } + " – so any values captured by the task cannot carry any references to " + code { "cx" } + ", such as a " + code { "UseState" } + "." + } + p { + "You can use " + Link { to: "https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned", + "to_owned" + } + " to create a clone of the hook handle which can be moved into the async closure." + } + CodeBlock { + contents: "
\nlet sync_status = use_state(cx, || Status::Launching);\nlet sync_task = use_coroutine(cx, |rx: UnboundedReceiver<SyncAction>| {{\n    let sync_status = sync_status.to_owned();\n    async move {{\n        loop {{\n            delay_ms(1000).await;\n            sync_status.set(Status::Working);\n        }}\n    }}\n}})
\n", + } + p { + "To make this a bit less verbose, Dioxus exports the " + code { "to_owned!" } + " macro which will create a binding as shown above, which can be quite helpful when dealing with many values." + } + CodeBlock { + contents: "
\nlet sync_status = use_state(cx, || Status::Launching);\nlet load_status = use_state(cx, || Status::Launching);\nlet sync_task = use_coroutine(cx, |rx: UnboundedReceiver<SyncAction>| {{\n    to_owned![sync_status, load_status];\n    async move {{\n        // ...\n    }}\n}})
\n", + } + h2 { id: "sending-values", + Link { + to: BookRoute::AsyncUseCoroutine { + section: AsyncUseCoroutineSection::SendingValues, + }, + class: "header", + "Sending Values" + } + } + p { + "You might've noticed the " + code { "use_coroutine" } + " closure takes an argument called " + code { "rx" } + ". What is that? Well, a common pattern in complex apps is to handle a bunch of async code at once. With libraries like Redux Toolkit, managing multiple promises at once can be challenging and a common source of bugs." + } + p { + "With Coroutines, we can centralize our async logic. The " + code { "rx" } + " parameter is an Channel that allows code external to the coroutine to send data " + em { "into" } + " the coroutine. Instead of looping on an external service, we can loop on the channel itself, processing messages from within our app without needing to spawn a new future. To send data into the coroutine, we would call \"send\" on the handle." + } + CodeBlock { + contents: "
\nenum ProfileUpdate {{\n    SetUsername(String),\n    SetAge(i32)\n}}\n\nlet profile = use_coroutine(cx, |mut rx: UnboundedReciver<ProfileUpdate>| async move {{\n    let mut server = connect_to_server().await;\n\n    while let Ok(msg) = rx.next().await {{\n        match msg {{\n            ProfileUpdate::SetUsername(name) => server.update_username(name).await,\n            ProfileUpdate::SetAge(age) => server.update_age(age).await,\n        }}\n    }}\n}});\n\n\ncx.render(rsx!{{\n    button {{\n        onclick: move |_| profile.send(ProfileUpdate::SetUsername("Bob".to_string())),\n        "Update username"\n    }}\n}})
\n", + } + p { + "For sufficiently complex apps, we could build a bunch of different useful \"services\" that loop on channels to update the app." + } + CodeBlock { + contents: "
\nlet profile = use_coroutine(cx, profile_service);\nlet editor = use_coroutine(cx, editor_service);\nlet sync = use_coroutine(cx, sync_service);\n\nasync fn profile_service(rx: UnboundedReceiver<ProfileCommand>) {{\n    // do stuff\n}}\n\nasync fn sync_service(rx: UnboundedReceiver<SyncCommand>) {{\n    // do stuff\n}}\n\nasync fn editor_service(rx: UnboundedReceiver<EditorCommand>) {{\n    // do stuff\n}}
\n", + } + p { + "We can combine coroutines with " + Link { to: "https://docs.rs/fermi/latest/fermi/index.html", "Fermi" } + " to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state " + em { "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 " + em { "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." + } + CodeBlock { + contents: "
\nstatic USERNAME: Atom<String> = |_| "default".to_string();\n\nfn app(cx: Scope) -> Element {{\n    let atoms = use_atom_root(cx);\n\n    use_coroutine(cx, |rx| sync_service(rx, atoms.clone()));\n\n    cx.render(rsx!{{\n        Banner {{}}\n    }})\n}}\n\nfn Banner(cx: Scope) -> Element {{\n    let username = use_read(cx, USERNAME);\n\n    cx.render(rsx!{{\n        h1 {{ "Welcome back, {{username}}" }}\n    }})\n}}
\n", + } + p { + "Now, in our sync service, we can structure our state however we want. We only need to update the view values when ready." + } + CodeBlock { + contents: "
\nenum SyncAction {{\n    SetUsername(String),\n}}\n\nasync fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {{\n    let username = atoms.write(USERNAME);\n    let errors = atoms.write(ERRORS);\n\n    while let Ok(msg) = rx.next().await {{\n        match msg {{\n            SyncAction::SetUsername(name) => {{\n                if set_name_on_server(&name).await.is_ok() {{\n                    username.set(name);\n                }} else {{\n                    errors.make_mut().push("SetUsernameFailed");\n                }}\n            }}\n        }}\n    }}\n}}
\n", + } + h2 { id: "automatic-injection-into-the-context-api", + Link { + to: BookRoute::AsyncUseCoroutine { + section: AsyncUseCoroutineSection::AutomaticInjectionIntoTheContextApi, + }, + class: "header", + "Automatic injection into the Context API" + } + } + p { + "Coroutine handles are automatically injected through the context API. You can use the " + code { "use_coroutine_handle" } + " hook with the message type as a generic to fetch a handle." + } + CodeBlock { contents: "
\nfn Child(cx: Scope) -> Element {{\n    let sync_task = use_coroutine_handle::<SyncAction>(cx);\n\n    sync_task.send(SyncAction::SetUsername);\n}}
\n" } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum AsyncSpawnSection { + #[default] + Empty, + SpawningFutures, + SpawningTokioTasks, +} +impl std::str::FromStr for AsyncSpawnSection { + type Err = AsyncSpawnSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "spawning-futures" => Ok(Self::SpawningFutures), + "spawning-tokio-tasks" => Ok(Self::SpawningTokioTasks), + _ => Err(AsyncSpawnSectionParseError), + } + } +} +impl std::fmt::Display for AsyncSpawnSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::SpawningFutures => f.write_str("spawning-futures"), + Self::SpawningTokioTasks => f.write_str("spawning-tokio-tasks"), + } + } +} +#[derive(Debug)] +pub struct AsyncSpawnSectionParseError; +impl std::fmt::Display for AsyncSpawnSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of AsyncSpawnSectionspawning-futures, spawning-tokio-tasks", + )?; + Ok(()) + } +} +impl std::error::Error for AsyncSpawnSectionParseError {} +#[component(no_case_check)] +pub fn AsyncSpawn(section: AsyncSpawnSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "spawning-futures", + Link { + to: BookRoute::AsyncSpawn { + section: AsyncSpawnSection::SpawningFutures, + }, + class: "header", + "Spawning Futures" + } + } + p { + "The " + code { "use_future" } + " and " + code { "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 " + code { "cx.spawn" } + ":" + } + CodeBlock { + contents: "
\nlet logged_in = use_state(cx, || false);\n\nlet log_in = move |_| {{\n    cx.spawn({{\n        let logged_in = logged_in.to_owned();\n\n        async move {{\n            let resp = reqwest::Client::new()\n                .post("http://example.com/login")\n                .send()\n                .await;\n\n            match resp {{\n                Ok(_data) => {{\n                    println!("Login successful!");\n                    logged_in.set(true);\n                }}\n                Err(_err) => {{\n                    println!(\n                        "Login failed - you need a login server running on localhost:8080."\n                    )\n                }}\n            }}\n        }}\n    }});\n}};\n\ncx.render(rsx! {{\n    button {{\n        onclick: log_in,\n        "Login",\n    }}\n}})
\n", + name: "spawn.rs".to_string(), + } + blockquote { + p { + "Note: " + code { "spawn" } + " will always spawn a " + em { "new" } + " future. You most likely don't want to call it on every render." + } + } + p { + "Calling " + code { "spawn" } + " will give you a " + code { "JoinHandle" } + " which lets you cancel or pause the future." + } + h2 { id: "spawning-tokio-tasks", + Link { + to: BookRoute::AsyncSpawn { + section: AsyncSpawnSection::SpawningTokioTasks, + }, + class: "header", + "Spawning Tokio Tasks" + } + } + p { + "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:" + } + CodeBlock { + contents: "
\ncx.spawn(async {{\n    let _ = tokio::spawn(async {{}}).await;\n\n    let _ = tokio::task::spawn_local(async {{\n        // some !Send work\n    }})\n    .await;\n}});
\n", + name: "spawn.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum BestPracticesIndexSection { + #[default] + Empty, + BestPractices, + ReusableComponents, + MinimizeStateDependencies, +} +impl std::str::FromStr for BestPracticesIndexSection { + type Err = BestPracticesIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "best-practices" => Ok(Self::BestPractices), + "reusable-components" => Ok(Self::ReusableComponents), + "minimize-state-dependencies" => Ok(Self::MinimizeStateDependencies), + _ => Err(BestPracticesIndexSectionParseError), + } + } +} +impl std::fmt::Display for BestPracticesIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::BestPractices => f.write_str("best-practices"), + Self::ReusableComponents => f.write_str("reusable-components"), + Self::MinimizeStateDependencies => f.write_str("minimize-state-dependencies"), + } + } +} +#[derive(Debug)] +pub struct BestPracticesIndexSectionParseError; +impl std::fmt::Display for BestPracticesIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of BestPracticesIndexSectionbest-practices, reusable-components, minimize-state-dependencies", + )?; + Ok(()) + } +} +impl std::error::Error for BestPracticesIndexSectionParseError {} +#[component(no_case_check)] +pub fn BestPracticesIndex(section: BestPracticesIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "best-practices", + Link { + to: BookRoute::BestPracticesIndex { + section: BestPracticesIndexSection::BestPractices, + }, + class: "header", + "Best Practices" + } + } + h2 { id: "reusable-components", + Link { + to: BookRoute::BestPracticesIndex { + section: BestPracticesIndexSection::ReusableComponents, + }, + class: "header", + "Reusable Components" + } + } + p { + "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." + } + p { "Organize your components in modules to keep the codebase easy to navigate!" } + h2 { id: "minimize-state-dependencies", + Link { + to: BookRoute::BestPracticesIndex { + section: BestPracticesIndexSection::MinimizeStateDependencies, + }, + class: "header", + "Minimize State Dependencies" + } + } + p { + "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:" + } + ul { + li { "Keep state local to a component if possible" } + li { "When sharing state through props, only pass down the specific data necessary" } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum BestPracticesErrorHandlingSection { + #[default] + Empty, + ErrorHandling, + TheSimplestReturningNone, + EarlyReturnOnResult, + MatchResults, + PassingErrorStatesThroughComponents, + GoingGlobal, +} +impl std::str::FromStr for BestPracticesErrorHandlingSection { + type Err = BestPracticesErrorHandlingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "error-handling" => Ok(Self::ErrorHandling), + "the-simplest--returning-none" => Ok(Self::TheSimplestReturningNone), + "early-return-on-result" => Ok(Self::EarlyReturnOnResult), + "match-results" => Ok(Self::MatchResults), + "passing-error-states-through-components" => { + Ok(Self::PassingErrorStatesThroughComponents) + } + "going-global" => Ok(Self::GoingGlobal), + _ => Err(BestPracticesErrorHandlingSectionParseError), + } + } +} +impl std::fmt::Display for BestPracticesErrorHandlingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ErrorHandling => f.write_str("error-handling"), + Self::TheSimplestReturningNone => f.write_str("the-simplest--returning-none"), + Self::EarlyReturnOnResult => f.write_str("early-return-on-result"), + Self::MatchResults => f.write_str("match-results"), + Self::PassingErrorStatesThroughComponents => { + f.write_str("passing-error-states-through-components") + } + Self::GoingGlobal => f.write_str("going-global"), + } + } +} +#[derive(Debug)] +pub struct BestPracticesErrorHandlingSectionParseError; +impl std::fmt::Display for BestPracticesErrorHandlingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of BestPracticesErrorHandlingSectionerror-handling, the-simplest--returning-none, early-return-on-result, match-results, passing-error-states-through-components, going-global", + )?; + Ok(()) + } +} +impl std::error::Error for BestPracticesErrorHandlingSectionParseError {} +#[component(no_case_check)] +pub fn BestPracticesErrorHandling( + section: BestPracticesErrorHandlingSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "error-handling", + Link { + to: BookRoute::BestPracticesErrorHandling { + section: BestPracticesErrorHandlingSection::ErrorHandling, + }, + class: "header", + "Error handling" + } + } + p { + "A selling point of Rust for web development is the reliability of always knowing where errors can occur and being forced to handle them" + } + p { + "However, we haven't talked about error handling at all in this guide! In this chapter, we'll cover some strategies in handling errors to ensure your app never crashes." + } + h2 { id: "the-simplest--returning-none", + Link { + to: BookRoute::BestPracticesErrorHandling { + section: BestPracticesErrorHandlingSection::TheSimplestReturningNone, + }, + class: "header", + "The simplest – returning None" + } + } + p { + "Astute observers might have noticed that " + code { "Element" } + " is actually a type alias for " + code { "Option" } + ". You don't need to know what a " + code { "VNode" } + " is, but it's important to recognize that we could actually return nothing at all:" + } + CodeBlock { contents: "
\nfn App(cx: Scope) -> Element {{\n    None\n}}
\n" } + p { + "This lets us add in some syntactic sugar for operations we think " + em { "shouldn't" } + " fail, but we're still not confident enough to \"unwrap\" on." + } + blockquote { + p { + "The nature of " + code { "Option" } + " might change in the future as the " + code { "try" } + " trait gets upgraded." + } + } + CodeBlock { + contents: "
\nfn App(cx: Scope) -> Element {{\n    // immediately return "None"\n    let name = cx.use_hook(|_| Some("hi"))?;\n}}
\n", + } + h2 { id: "early-return-on-result", + Link { + to: BookRoute::BestPracticesErrorHandling { + section: BestPracticesErrorHandlingSection::EarlyReturnOnResult, + }, + class: "header", + "Early return on result" + } + } + p { + "Because Rust can't accept both Options and Results with the existing try infrastructure, you'll need to manually handle Results. This can be done by converting them into Options or by explicitly handling them." + } + CodeBlock { + contents: "
\nfn App(cx: Scope) -> Element {{\n    // Convert Result to Option\n    let name = cx.use_hook(|_| "1.234").parse().ok()?;\n\n\n    // Early return\n    let count = cx.use_hook(|_| "1.234");\n    let val = match count.parse() {{\n        Ok(val) => val\n        Err(err) => return cx.render(rsx!{{ "Parsing failed" }})\n    }};\n}}
\n", + } + p { + "Notice that while hooks in Dioxus do not like being called in conditionals or loops, they " + em { "are" } + " okay with early returns. Returning an error state early is a completely valid way of handling errors." + } + h2 { id: "match-results", + Link { + to: BookRoute::BestPracticesErrorHandling { + section: BestPracticesErrorHandlingSection::MatchResults, + }, + class: "header", + "Match results" + } + } + p { + "The next \"best\" way of handling errors in Dioxus is to match on the error locally. This is the most robust way of handling errors, though it doesn't scale to architectures beyond a single component." + } + p { "To do this, we simply have an error state built into our component:" } + CodeBlock { contents: "
\nlet err = use_state(cx, || None);
\n" } + p { + "Whenever we perform an action that generates an error, we'll set that error state. We can then match on the error in a number of ways (early return, return Element, etc)." + } + CodeBlock { + contents: "
\nfn Commandline(cx: Scope) -> Element {{\n    let error = use_state(cx, || None);\n\n    cx.render(match *error {{\n        Some(error) => rsx!(\n            h1 {{ "An error occured" }}\n        )\n        None => rsx!(\n            input {{\n                oninput: move |_| error.set(Some("bad thing happened!")),\n            }}\n        )\n    }})\n}}
\n", + } + h2 { id: "passing-error-states-through-components", + Link { + to: BookRoute::BestPracticesErrorHandling { + section: BestPracticesErrorHandlingSection::PassingErrorStatesThroughComponents, + }, + class: "header", + "Passing error states through components" + } + } + p { + "If you're dealing with a handful of components with minimal nesting, you can just pass the error handle into child components." + } + CodeBlock { + contents: "
\nfn Commandline(cx: Scope) -> Element {{\n    let error = use_state(cx, || None);\n\n    if let Some(error) = **error {{\n        return cx.render(rsx!{{ "An error occured" }});\n    }}\n\n    cx.render(rsx!{{\n        Child {{ error: error.clone() }}\n        Child {{ error: error.clone() }}\n        Child {{ error: error.clone() }}\n        Child {{ error: error.clone() }}\n    }})\n}}
\n", + } + p { + "Much like before, our child components can manually set the error during their own actions. The advantage to this pattern is that we can easily isolate error states to a few components at a time, making our app more predictable and robust." + } + h2 { id: "going-global", + Link { + to: BookRoute::BestPracticesErrorHandling { + section: BestPracticesErrorHandlingSection::GoingGlobal, + }, + class: "header", + "Going global" + } + } + p { + "A strategy for handling cascaded errors in larger apps is through signaling an error using global state. This particular pattern involves creating an \"error\" context, and then setting it wherever relevant. This particular method is not as \"sophisticated\" as React's error boundary, but it is more fitting for Rust." + } + p { + "To get started, consider using a built-in hook like " + code { "use_context" } + " and " + code { "use_context_provider" } + " or Fermi. Of course, it's pretty easy to roll your own hook too." + } + p { + "At the \"top\" of our architecture, we're going to want to explicitly declare a value that could be an error." + } + CodeBlock { contents: "
\nenum InputError {{\n    None,\n    TooLong,\n    TooShort,\n}}\n\nstatic INPUT_ERROR: Atom<InputError> = |_| InputError::None;
\n" } + p { + "Then, in our top level component, we want to explicitly handle the possible error state for this part of the tree." + } + CodeBlock { + contents: "
\nfn TopLevel(cx: Scope) -> Element {{\n    let error = use_read(cx, INPUT_ERROR);\n\n    match error {{\n        TooLong => return cx.render(rsx!{{ "FAILED: Too long!" }}),\n        TooShort => return cx.render(rsx!{{ "FAILED: Too Short!" }}),\n        _ => {{}}\n    }}\n}}
\n", + } + p { + "Now, whenever a downstream component has an error in its actions, it can simply just set its own error state:" + } + CodeBlock { + contents: "
\nfn Commandline(cx: Scope) -> Element {{\n    let set_error = use_set(cx, INPUT_ERROR);\n\n    cx.render(rsx!{{\n        input {{\n            oninput: move |evt| {{\n                if evt.value.len() > 20 {{\n                    set_error(InputError::TooLong);\n                }}\n            }}\n        }}\n    }})\n}}
\n", + } + p { + "This approach to error handling is best in apps that have \"well defined\" error states. Consider using a crate like " + code { "thiserror" } + " or " + code { "anyhow" } + " to simplify the generation of the error types." + } + p { + "This pattern is widely popular in many contexts and is particularly helpful whenever your code generates a non-recoverable error. You can gracefully capture these \"global\" error states without panicking or mucking up state." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum BestPracticesAntipatternsSection { + #[default] + Empty, + Antipatterns, + UnnecessarilyNestedFragments, + IncorrectIteratorKeys, + AvoidInteriorMutabilityInProps, + AvoidUpdatingStateDuringRender, +} +impl std::str::FromStr for BestPracticesAntipatternsSection { + type Err = BestPracticesAntipatternsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "antipatterns" => Ok(Self::Antipatterns), + "unnecessarily-nested-fragments" => Ok(Self::UnnecessarilyNestedFragments), + "incorrect-iterator-keys" => Ok(Self::IncorrectIteratorKeys), + "avoid-interior-mutability-in-props" => Ok(Self::AvoidInteriorMutabilityInProps), + "avoid-updating-state-during-render" => Ok(Self::AvoidUpdatingStateDuringRender), + _ => Err(BestPracticesAntipatternsSectionParseError), + } + } +} +impl std::fmt::Display for BestPracticesAntipatternsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Antipatterns => f.write_str("antipatterns"), + Self::UnnecessarilyNestedFragments => f.write_str("unnecessarily-nested-fragments"), + Self::IncorrectIteratorKeys => f.write_str("incorrect-iterator-keys"), + Self::AvoidInteriorMutabilityInProps => { + f.write_str("avoid-interior-mutability-in-props") + } + Self::AvoidUpdatingStateDuringRender => { + f.write_str("avoid-updating-state-during-render") + } + } + } +} +#[derive(Debug)] +pub struct BestPracticesAntipatternsSectionParseError; +impl std::fmt::Display for BestPracticesAntipatternsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of BestPracticesAntipatternsSectionantipatterns, unnecessarily-nested-fragments, incorrect-iterator-keys, avoid-interior-mutability-in-props, avoid-updating-state-during-render", + )?; + Ok(()) + } +} +impl std::error::Error for BestPracticesAntipatternsSectionParseError {} +#[component(no_case_check)] +pub fn BestPracticesAntipatterns( + section: BestPracticesAntipatternsSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "antipatterns", + Link { + to: BookRoute::BestPracticesAntipatterns { + section: BestPracticesAntipatternsSection::Antipatterns, + }, + class: "header", + "Antipatterns" + } + } + p { + "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 for performance or code re-usability reasons." + } + h2 { id: "unnecessarily-nested-fragments", + Link { + to: BookRoute::BestPracticesAntipatterns { + section: BestPracticesAntipatternsSection::UnnecessarilyNestedFragments, + }, + class: "header", + "Unnecessarily Nested Fragments" + } + } + p { + "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." + } + p { + "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." + } + CodeBlock { + contents: "
\n// ❌ Don't unnecessarily nest fragments\nlet _ = cx.render(rsx!(\n    Fragment {{\n        Fragment {{\n            Fragment {{\n                Fragment {{\n                    Fragment {{\n                        div {{ "Finally have a real node!" }}\n                    }}\n                }}\n            }}\n        }}\n    }}\n));\n\n// ✅ Render shallow structures\ncx.render(rsx!(\n    div {{ "Finally have a real node!" }}\n))
\n", + name: "anti_patterns.rs".to_string(), + } + h2 { id: "incorrect-iterator-keys", + Link { + to: BookRoute::BestPracticesAntipatterns { + section: BestPracticesAntipatternsSection::IncorrectIteratorKeys, + }, + class: "header", + "Incorrect Iterator Keys" + } + } + p { + "As described in the " + Link { + to: BookRoute::InteractivityDynamicRendering { + section: InteractivityDynamicRenderingSection::TheKeyAttribute, + }, + "dynamic 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 will never change." + } + CodeBlock { + contents: "
\nlet data: &HashMap<_, _> = &cx.props.data;\n\n// ❌ No keys\ncx.render(rsx! {{\n    ul {{\n        data.values().map(|value| rsx!(\n            li {{ "List item: {{value}}" }}\n        ))\n    }}\n}});\n\n// ❌ Using index as keys\ncx.render(rsx! {{\n    ul {{\n        cx.props.data.values().enumerate().map(|(index, value)| rsx!(\n            li {{ key: "{{index}}", "List item: {{value}}" }}\n        ))\n    }}\n}});\n\n// ✅ Using unique IDs as keys:\ncx.render(rsx! {{\n    ul {{\n        cx.props.data.iter().map(|(key, value)| rsx!(\n            li {{ key: "{{key}}", "List item: {{value}}" }}\n        ))\n    }}\n}})
\n", + name: "anti_patterns.rs".to_string(), + } + h2 { id: "avoid-interior-mutability-in-props", + Link { + to: BookRoute::BestPracticesAntipatterns { + section: BestPracticesAntipatternsSection::AvoidInteriorMutabilityInProps, + }, + class: "header", + "Avoid Interior Mutability in Props" + } + } + p { + "While it is technically acceptable to have a " + code { "Mutex" } + " or a " + code { "RwLock" } + " in the props, they will be difficult to use." + } + p { + "Suppose you have a struct " + code { "User" } + " containing the field " + code { "username: String" } + ". If you pass a " + code { "Mutex" } + " prop to a " + code { "UserComponent" } + " component, that component may wish to pass the username as a " + code { "&str" } + " prop to a child component. However, it cannot pass that borrowed field down, since it only would live as long as the " + code { "Mutex" } + "'s lock, which belongs to the " + code { "UserComponent" } + " function. Therefore, the component will be forced to clone the " + code { "username" } + " field." + } + h2 { id: "avoid-updating-state-during-render", + Link { + to: BookRoute::BestPracticesAntipatterns { + section: BestPracticesAntipatternsSection::AvoidUpdatingStateDuringRender, + }, + class: "header", + "Avoid Updating State During Render" + } + } + p { + "Every time you update the state, Dioxus needs to re-render the component – this is inefficient! Consider refactoring your code to avoid this." + } + p { + "Also, if you unconditionally update the state during render, it will be re-rendered in an infinite loop." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum PublishingIndexSection { + #[default] + Empty, + Publishing, +} +impl std::str::FromStr for PublishingIndexSection { + type Err = PublishingIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "publishing" => Ok(Self::Publishing), + _ => Err(PublishingIndexSectionParseError), + } + } +} +impl std::fmt::Display for PublishingIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Publishing => f.write_str("publishing"), + } + } +} +#[derive(Debug)] +pub struct PublishingIndexSectionParseError; +impl std::fmt::Display for PublishingIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of PublishingIndexSectionpublishing")?; + Ok(()) + } +} +impl std::error::Error for PublishingIndexSectionParseError {} +#[component(no_case_check)] +pub fn PublishingIndex(section: PublishingIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "publishing", + Link { + to: BookRoute::PublishingIndex { + section: PublishingIndexSection::Publishing, + }, + class: "header", + "Publishing" + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum PublishingDesktopSection { + #[default] + Empty, + Publishing, + InstallCargoBundle, + SettingUpYourProject, + Building, +} +impl std::str::FromStr for PublishingDesktopSection { + type Err = PublishingDesktopSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "publishing" => Ok(Self::Publishing), + "install-cargo-bundle" => Ok(Self::InstallCargoBundle), + "setting-up-your-project" => Ok(Self::SettingUpYourProject), + "building" => Ok(Self::Building), + _ => Err(PublishingDesktopSectionParseError), + } + } +} +impl std::fmt::Display for PublishingDesktopSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Publishing => f.write_str("publishing"), + Self::InstallCargoBundle => f.write_str("install-cargo-bundle"), + Self::SettingUpYourProject => f.write_str("setting-up-your-project"), + Self::Building => f.write_str("building"), + } + } +} +#[derive(Debug)] +pub struct PublishingDesktopSectionParseError; +impl std::fmt::Display for PublishingDesktopSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of PublishingDesktopSectionpublishing, install-cargo-bundle, setting-up-your-project, building", + )?; + Ok(()) + } +} +impl std::error::Error for PublishingDesktopSectionParseError {} +#[component(no_case_check)] +pub fn PublishingDesktop(section: PublishingDesktopSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "publishing", + Link { + to: BookRoute::PublishingDesktop { + section: PublishingDesktopSection::Publishing, + }, + class: "header", + "Publishing" + } + } + p { + "Congrats! You've made your first Dioxus app that actually does some pretty cool stuff. This app uses your operating system's WebView library, so it's portable to be distributed for other platforms." + } + p { "In this section, we'll cover how to bundle your app for macOS, Windows, and Linux." } + h2 { id: "install-cargo-bundle", + Link { + to: BookRoute::PublishingDesktop { + section: PublishingDesktopSection::InstallCargoBundle, + }, + class: "header", + "Install cargo-bundle" + } + } + p { + "The first thing we'll do is install " + Link { to: "https://github.com/burtonageo/cargo-bundle", + code { "cargo-bundle" } + } + ". This extension to cargo will make it very easy to package our app for the various platforms." + } + p { + "According to the " + code { "cargo-bundle" } + " github page," + } + p { + em { + "\"cargo-bundle is a tool used to generate installers or app bundles for GUI executables built with cargo. It can create .app bundles for Mac OS X and iOS, .deb packages for Linux, and .msi installers for Windows (note however that iOS and Windows support is still experimental). Support for creating .rpm packages (for Linux) and .apk packages (for Android) is still pending.\"" + } + } + p { "To install, simply run" } + p { + code { "cargo install cargo-bundle" } + } + h2 { id: "setting-up-your-project", + Link { + to: BookRoute::PublishingDesktop { + section: PublishingDesktopSection::SettingUpYourProject, + }, + class: "header", + "Setting up your project" + } + } + p { + "To get a project setup for bundling, we need to add some flags to our " + code { "Cargo.toml" } + " file." + } + CodeBlock { + contents: "
\n[package]\nname = "example"\n# ...other fields...\n\n[package.metadata.bundle]\nname = "DogSearch"\nidentifier = "com.dogs.dogsearch"\nversion = "1.0.0"\ncopyright = "Copyright (c) Jane Doe 2016. All rights reserved."\ncategory = "Developer Tool"\nshort_description = "Easily search for Dog photos"\nlong_description = """\nThis app makes it quick and easy to browse photos of dogs from over 200 bree\n"""
\n", + } + h2 { id: "building", + Link { + to: BookRoute::PublishingDesktop { + section: PublishingDesktopSection::Building, + }, + class: "header", + "Building" + } + } + p { + "Following cargo-bundle's instructions, we simply " + code { "cargo-bundle --release" } + " to produce a final app with all the optimizations and assets builtin." + } + p { + "Once you've ran " + code { "cargo-bundle --release" } + ", your app should be accessible in" + } + p { + code { "target/release/bundle//" } + "." + } + p { "For example, a macOS app would look like this:" } + p { + img { + src: asset!("/assets/static/publish.png", ImageAssetOptions::new().with_webp()), + alt: "Published App", + title: "", + } + } + p { + "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." + } + blockquote { + p { + "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." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum PublishingWebSection { + #[default] + Empty, + PublishingWithGithubPages, +} +impl std::str::FromStr for PublishingWebSection { + type Err = PublishingWebSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "publishing-with-github-pages" => Ok(Self::PublishingWithGithubPages), + _ => Err(PublishingWebSectionParseError), + } + } +} +impl std::fmt::Display for PublishingWebSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::PublishingWithGithubPages => f.write_str("publishing-with-github-pages"), + } + } +} +#[derive(Debug)] +pub struct PublishingWebSectionParseError; +impl std::fmt::Display for PublishingWebSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of PublishingWebSectionpublishing-with-github-pages", + )?; + Ok(()) + } +} +impl std::error::Error for PublishingWebSectionParseError {} +#[component(no_case_check)] +pub fn PublishingWeb(section: PublishingWebSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h2 { id: "publishing-with-github-pages", + Link { + to: BookRoute::PublishingWeb { + section: PublishingWebSection::PublishingWithGithubPages, + }, + class: "header", + "Publishing with Github Pages" + } + } + p { "To build our app and publish it to Github:" } + ul { + li { "Make sure GitHub Pages is set up for your repo" } + li { + "Build your app with " + code { "trunk build --release" } + " (include " + code { "--public-url " } + " to update asset prefixes if using a project site)" + } + li { + "Move your generated HTML/CSS/JS/Wasm from " + code { "dist" } + " into the folder configured for Github Pages" + } + li { "Add and commit with git" } + li { "Push to GitHub" } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CustomRendererIndexSection { + #[default] + Empty, + CustomRenderer, + TheSpecifics, + Templates, + Mutations, + AnExample, + EventLoop, + CustomRawElements, + NativeCore, + Realdom, + Example, + Layout, + Conclusion, +} +impl std::str::FromStr for CustomRendererIndexSection { + type Err = CustomRendererIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "custom-renderer" => Ok(Self::CustomRenderer), + "the-specifics" => Ok(Self::TheSpecifics), + "templates" => Ok(Self::Templates), + "mutations" => Ok(Self::Mutations), + "an-example" => Ok(Self::AnExample), + "event-loop" => Ok(Self::EventLoop), + "custom-raw-elements" => Ok(Self::CustomRawElements), + "native-core" => Ok(Self::NativeCore), + "realdom" => Ok(Self::Realdom), + "example" => Ok(Self::Example), + "layout" => Ok(Self::Layout), + "conclusion" => Ok(Self::Conclusion), + _ => Err(CustomRendererIndexSectionParseError), + } + } +} +impl std::fmt::Display for CustomRendererIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::CustomRenderer => f.write_str("custom-renderer"), + Self::TheSpecifics => f.write_str("the-specifics"), + Self::Templates => f.write_str("templates"), + Self::Mutations => f.write_str("mutations"), + Self::AnExample => f.write_str("an-example"), + Self::EventLoop => f.write_str("event-loop"), + Self::CustomRawElements => f.write_str("custom-raw-elements"), + Self::NativeCore => f.write_str("native-core"), + Self::Realdom => f.write_str("realdom"), + Self::Example => f.write_str("example"), + Self::Layout => f.write_str("layout"), + Self::Conclusion => f.write_str("conclusion"), + } + } +} +#[derive(Debug)] +pub struct CustomRendererIndexSectionParseError; +impl std::fmt::Display for CustomRendererIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CustomRendererIndexSectioncustom-renderer, the-specifics, templates, mutations, an-example, event-loop, custom-raw-elements, native-core, realdom, example, layout, conclusion", + )?; + Ok(()) + } +} +impl std::error::Error for CustomRendererIndexSectionParseError {} +#[component(no_case_check)] +pub fn CustomRendererIndex(section: CustomRendererIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "custom-renderer", + Link { + to: BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::CustomRenderer, + }, + class: "header", + "Custom Renderer" + } + } + p { + "Dioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer." + } + p { + "Great news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require processing " + code { "DomEdits" } + " and sending " + code { "UserEvents" } + "." + } + h2 { id: "the-specifics", + Link { + to: BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::TheSpecifics, + }, + class: "header", + "The specifics:" + } + } + p { "Implementing the renderer is fairly straightforward. The renderer needs to:" } + ol { + li { "Handle the stream of edits generated by updates to the virtual DOM" } + li { "Register listeners and pass events into the virtual DOM's event system" } + } + p { + "Essentially, your renderer needs to process edits and generate events to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen." + } + p { + "Internally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves." + } + p { + "For reference, check out the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/interpreter", + "javascript interpreter" + } + " or " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/tui", + "tui renderer" + } + " as a starting point for your custom renderer." + } + h2 { id: "templates", + Link { + to: BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::Templates, + }, + class: "header", + "Templates" + } + } + p { + "Dioxus is built around the concept of " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html", + "Templates" + } + ". Templates describe a UI tree known at compile time with dynamic parts filled at runtime. This is useful internally to make skip diffing static nodes, but it is also useful for the renderer to reuse parts of the UI tree. This can be useful for things like a list of items. Each item could contain some static parts and some dynamic parts. The renderer can use the template to create a static part of the UI once, clone it for each element in the list, and then fill in the dynamic parts." + } + h2 { id: "mutations", + Link { + to: BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::Mutations, + }, + class: "header", + "Mutations" + } + } + p { + "The " + code { "Mutation" } + " type is a serialized enum that represents an operation that should be applied to update the UI. The variants roughly follow this set:" + } + CodeBlock { + contents: "
\nenum Mutation {{\n    AppendChildren,\n    AssignId,\n    CreatePlaceholder,\n    CreateTextNode,\n    HydrateText,\n    LoadTemplate,\n    ReplaceWith,\n    ReplacePlaceholder,\n    InsertAfter,\n    InsertBefore,\n    SetAttribute,\n    SetText,\n    NewEventListener,\n    RemoveEventListener,\n    Remove,\n    PushRoot,\n}}
\n", + } + p { + "The Dioxus diffing mechanism operates as a " + Link { to: "https://en.wikipedia.org/wiki/Stack_machine", "stack machine" } + " where the \"push_root\" method pushes a new \"real\" DOM node onto the stack and \"append_child\" and \"replace_with\" both remove nodes from the stack." + } + h3 { id: "an-example", + Link { + to: BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::AnExample, + }, + class: "header", + "An Example" + } + } + p { + "For the sake of understanding, let's consider this example – a very simple UI declaration:" + } + CodeBlock { contents: "
\nrsx!( h1 {{"count {{x}}"}} )
\n" } + p { + "To get things started, Dioxus must first navigate to the container of this h1 tag. To \"navigate\" here, the internal diffing algorithm generates the DomEdit " + code { "PushRoot" } + " where the ID of the root is the container." + } + p { + "When the renderer receives this instruction, it pushes the actual Node onto its own stack. The real renderer's stack will look like this:" + } + CodeBlock { contents: "
\ninstructions: [\n    PushRoot(Container)\n]\nstack: [\n    ContainerNode,\n]
\n" } + p { + "Next, Dioxus will encounter the h1 node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the DomEdit " + code { "CreateElement" } + ". When the renderer receives this instruction, it will create an unmounted node and push it into its own stack:" + } + CodeBlock { contents: "
\ninstructions: [\n    PushRoot(Container),\n    CreateElement(h1),\n]\nstack: [\n    ContainerNode,\n    h1,\n]
\n" } + p { + "Next, Dioxus sees the text node, and generates the " + code { "CreateTextNode" } + " DomEdit:" + } + CodeBlock { contents: "
\ninstructions: [\n    PushRoot(Container),\n    CreateElement(h1),\n    CreateTextNode("hello world")\n]\nstack: [\n    ContainerNode,\n    h1,\n    "hello world"\n]
\n" } + p { + "Remember, the text node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the text node to the h1 element. It depends on the situation, but in this case, we use " + code { "AppendChildren" } + ". This pops the text node off the stack, leaving the h1 element as the next element in line." + } + CodeBlock { contents: "
\ninstructions: [\n    PushRoot(Container),\n    CreateElement(h1),\n    CreateTextNode("hello world"),\n    AppendChildren(1)\n]\nstack: [\n    ContainerNode,\n    h1\n]
\n" } + p { + "We call " + code { "AppendChildren" } + " again, popping off the h1 node and attaching it to the parent:" + } + CodeBlock { contents: "
\ninstructions: [\n    PushRoot(Container),\n    CreateElement(h1),\n    CreateTextNode("hello world"),\n    AppendChildren(1),\n    AppendChildren(1)\n]\nstack: [\n    ContainerNode,\n]
\n" } + p { "Finally, the container is popped since we don't need it anymore." } + CodeBlock { contents: "
\ninstructions: [\n    PushRoot(Container),\n    CreateElement(h1),\n    CreateTextNode("hello world"),\n    AppendChildren(1),\n    AppendChildren(1),\n    PopRoot\n]\nstack: []
\n" } + p { "Over time, our stack looked like this:" } + CodeBlock { contents: "
\n[]\n[Container]\n[Container, h1]\n[Container, h1, "hello world"]\n[Container, h1]\n[Container]\n[]
\n" } + p { + "Notice how our stack is empty once UI has been mounted. Conveniently, this approach completely separates the Virtual DOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits make Dioxus independent of platform specifics." + } + p { + "Dioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing." + } + p { + "It's important to note that there " + em { "is" } + " one layer of connectedness between Dioxus and the renderer. Dioxus saves and loads elements (the PushRoot edit) with an ID. Inside the VirtualDOM, this is just tracked as a u64." + } + p { + "Whenever a " + code { "CreateElement" } + " edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when PushRoot(ID) is generated. Dioxus reclaims the IDs of elements when removed. To stay in sync with Dioxus you can use a sparse Vec (Vec" + " " + "<" + " " + "Option" + p { class: "inline-html-block", dangerous_inner_html: "" } + ">) with possibly unoccupied items. You can use the ids as indexes into the Vec for elements, and grow the Vec when an id does not exist." + } + p { + "This little demo serves to show exactly how a Renderer would need to process an edit stream to build UIs. A set of serialized DomEditss for various demos is available for you to test your custom renderer against." + } + h2 { id: "event-loop", + Link { + to: BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::EventLoop, + }, + class: "header", + "Event loop" + } + } + p { + "Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important that your custom renderer can handle those too." + } + p { + "The code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is:" + } + CodeBlock { + contents: "
\npub async fn run(&mut self) -> dioxus_core::error::Result<()> {{\n    // Push the body element onto the WebsysDom's stack machine\n    let mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom());\n    websys_dom.stack.push(root_node);\n\n    // Rebuild or hydrate the virtualdom\n    let mutations = self.internal_dom.rebuild();\n    websys_dom.apply_mutations(mutations);\n\n    // Wait for updates from the real dom and progress the virtual dom\n    loop {{\n        let user_input_future = websys_dom.wait_for_event();\n        let internal_event_future = self.internal_dom.wait_for_work();\n\n        match select(user_input_future, internal_event_future).await {{\n            Either::Left((_, _)) => {{\n                let mutations = self.internal_dom.work_with_deadline(|| false);\n                websys_dom.apply_mutations(mutations);\n            }},\n            Either::Right((event, _)) => websys_dom.handle_event(event),\n        }}\n\n        // render\n    }}\n}}
\n", + } + p { + "It's important that you decode the real events from your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus " + code { "UserEvent" } + " type. Right now, the VirtualEvent system is modeled almost entirely around the HTML spec, but we are interested in slimming it down." + } + CodeBlock { + contents: "
\nfn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {{\n    match event.type_().as_str() {{\n        "keydown" => {{\n            let event: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();\n            UserEvent::KeyboardEvent(UserEvent {{\n                scope_id: None,\n                priority: EventPriority::Medium,\n                name: "keydown",\n                // This should be whatever element is focused\n                element: Some(ElementId(0)),\n                data: Arc::new(KeyboardData{{\n                    char_code: event.char_code(),\n                    key: event.key(),\n                    key_code: event.key_code(),\n                    alt_key: event.alt_key(),\n                    ctrl_key: event.ctrl_key(),\n                    meta_key: event.meta_key(),\n                    shift_key: event.shift_key(),\n                    location: event.location(),\n                    repeat: event.repeat(),\n                    which: event.which(),\n                }})\n            }})\n        }}\n        _ => todo!()\n    }}\n}}
\n", + } + h2 { id: "custom-raw-elements", + Link { + to: BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::CustomRawElements, + }, + class: "header", + "Custom raw elements" + } + } + p { + "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." + } + p { "These custom elements are defined as unit structs with trait implementations." } + p { + "For example, the " + code { "div" } + " element is (approximately!) defined as such:" + } + CodeBlock { + contents: "
\nstruct div;\nimpl div {{\n    /// Some glorious documentation about the class property.\n    const TAG_NAME: &'static str = "div";\n    const NAME_SPACE: Option<&'static str> = None;\n    // define the class attribute\n    pub fn class<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {{\n        cx.attr("class", val, None, false)\n    }}\n    // more attributes\n}}
\n", + } + p { + "You've probably noticed that many elements in the " + code { "rsx!" } + " macros support on-hover documentation. The approach we take to custom elements means that the unit struct is created immediately where the element is used in the macro. When the macro is expanded, the doc comments still apply to the unit struct, giving tons of in-editor feedback, even inside a proc macro." + } + h1 { id: "native-core", + Link { + to: BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::NativeCore, + }, + class: "header", + "Native Core" + } + } + p { + "If you are creating a renderer in rust, native-core provides some utilities to implement a renderer. It provides an abstraction over DomEdits and handles the layout for you." + } + h2 { id: "realdom", + Link { + to: BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::Realdom, + }, + class: "header", + "RealDom" + } + } + p { + "The " + code { "RealDom" } + " is a higher-level abstraction over updating the Dom. It updates with " + code { "DomEdits" } + " and provides a way to incrementally update the state of nodes based on what attributes change." + } + h3 { id: "example", + Link { + to: BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::Example, + }, + class: "header", + "Example" + } + } + p { + "Let's build a toy renderer with borders, size, and text color." + " " + "Before we start let's take a look at an example element we can render:" + } + CodeBlock { contents: "
\ncx.render(rsx!{{\n    div{{\n        color: "red",\n        p{{\n            border: "1px solid black",\n            "hello world"\n        }}\n    }}\n}})
\n" } + p { + "In this tree, the color depends on the parent's color. The size depends on the children's size, the current text, and the text size. The border depends on only the current node." + } + p { "In the following diagram arrows represent dataflow:" } + p { + Link { to: "https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNqdVNFqgzAU_RXJXizUUZPJmIM-jO0LukdhpCbO0JhIGteW0n9fNK1Oa0brfUnu9VxyzzkXjyCVhIIYZFzu0hwr7X2-JcIzsa3W3wqXuZdKoele22oddfa1Y0Tnfn31muvMfqeCDNq3GmvaNROmaKqZFO1DPTRhP8MOd1fTWYNDvzlmQbBMJZcq9JtjNgY1mLVUhBqQPQeojl3wGCw5PsjqnIe-zXqEL8GZ2Kz0gVMPmoeU3ND4IcuiaLGY2zRouuKncv_qGKv3VodpJe0JVU6QCQ5kgqMyWQVr8hbk4hm1PBcmsuwmnrCVH94rP7xN_ucp8sOB_EPSfz9drYVrkpc_AmH8_yTjJueUc-ntpOJkgt2os9tKjcYlt-DLUiD3UsB2KZCLcwjv3Aq33-g2v0M0xXA0MBy5DUdXi-gcJZriuLmAOSioKjAj5ld8rMsJ0DktaAJicyVYbRKQiJPBVSUx438QpqUCcYb5ls4BrrRcHUTaFizqnWGzR8W5evoFI-bJdw", + img { + src: "https://mermaid.ink/img/pako:eNqdVNFqgzAU_RXJXizUUZPJmIM-jO0LukdhpCbO0JhIGteW0n9fNK1Oa0brfUnu9VxyzzkXjyCVhIIYZFzu0hwr7X2-JcIzsa3W3wqXuZdKoele22oddfa1Y0Tnfn31muvMfqeCDNq3GmvaNROmaKqZFO1DPTRhP8MOd1fTWYNDvzlmQbBMJZcq9JtjNgY1mLVUhBqQPQeojl3wGCw5PsjqnIe-zXqEL8GZ2Kz0gVMPmoeU3ND4IcuiaLGY2zRouuKncv_qGKv3VodpJe0JVU6QCQ5kgqMyWQVr8hbk4hm1PBcmsuwmnrCVH94rP7xN_ucp8sOB_EPSfz9drYVrkpc_AmH8_yTjJueUc-ntpOJkgt2os9tKjcYlt-DLUiD3UsB2KZCLcwjv3Aq33-g2v0M0xXA0MBy5DUdXi-gcJZriuLmAOSioKjAj5ld8rMsJ0DktaAJicyVYbRKQiJPBVSUx438QpqUCcYb5ls4BrrRcHUTaFizqnWGzR8W5evoFI-bJdw", + alt: "", + title: "", + } + } + } + p { + "To help in building a Dom, native-core provides four traits: State, ChildDepState, ParentDepState, NodeDepState, and a RealDom struct. The ChildDepState, ParentDepState, and NodeDepState provide a way to describe how some information in a node relates to that of its relatives. By providing how to build a single node from its relations, native-core will derive a way to update the state of all nodes for you with " + code { "#[derive(State)]" } + ". Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom." + } + CodeBlock { + contents: "
\nuse dioxus_native_core::node_ref::*;\nuse dioxus_native_core::state::{{ChildDepState, NodeDepState, ParentDepState, State}};\nuse dioxus_native_core_macro::{{sorted_str_slice, State}};\n\n#[derive(Default, Copy, Clone)]\nstruct Size(f32, f32);\n// Size only depends on the current node and its children, so it implements ChildDepState\nimpl ChildDepState for Size {{\n    // Size accepts a font size context\n    type Ctx = f32;\n    // Size depends on the Size part of each child\n    type DepState = Self;\n    // Size only cares about the width, height, and text parts of the current node\n    const NODE_MASK: NodeMask =\n        NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["width", "height"]))).with_text();\n    fn reduce<'a>(\n        &mut self,\n        node: NodeView,\n        children: impl Iterator<Item = &'a Self::DepState>,\n        ctx: &Self::Ctx,\n    ) -> bool\n    where\n        Self::DepState: 'a,\n    {{\n        let mut width;\n        let mut height;\n        if let Some(text) = node.text() {{\n            // if the node has text, use the text to size our object\n            width = text.len() as f32 * ctx;\n            height = *ctx;\n        }} else {{\n            // otherwise, the size is the maximum size of the children\n            width = children\n                .by_ref()\n                .map(|item| item.0)\n                .reduce(|accum, item| if accum >= item {{ accum }} else {{ item }})\n                .unwrap_or(0.0);\n\n            height = children\n                .map(|item| item.1)\n                .reduce(|accum, item| if accum >= item {{ accum }} else {{ item }})\n                .unwrap_or(0.0);\n        }}\n        // if the node contains a width or height attribute it overrides the other size\n        for a in node.attributes(){{\n            match a.name{{\n                "width" => width = a.value.as_float32().unwrap(),\n                "height" => height = a.value.as_float32().unwrap(),\n                // because Size only depends on the width and height, no other attributes will be passed to the member\n                _ => panic!()\n            }}\n        }}\n        // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed\n        let changed = (width != self.0) || (height != self.1);\n        *self = Self(width, height);\n        changed\n    }}\n}}\n\n#[derive(Debug, Clone, Copy, PartialEq, Default)]\nstruct TextColor {{\n    r: u8,\n    g: u8,\n    b: u8,\n}}\n// TextColor only depends on the current node and its parent, so it implements ParentDepState\nimpl ParentDepState for TextColor {{\n    type Ctx = ();\n    // TextColor depends on the TextColor part of the parent\n    type DepState = Self;\n    // TextColor only cares about the color attribute of the current node\n    const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&["color"]));\n    fn reduce(\n        &mut self,\n        node: NodeView,\n        parent: Option<&Self::DepState>,\n        _ctx: &Self::Ctx,\n    ) -> bool {{\n        // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags\n        let new = match node.attributes().next().map(|attr| attr.name) {{\n            // if there is a color tag, translate it\n            Some("red") => TextColor {{ r: 255, g: 0, b: 0 }},\n            Some("green") => TextColor {{ r: 0, g: 255, b: 0 }},\n            Some("blue") => TextColor {{ r: 0, g: 0, b: 255 }},\n            Some(_) => panic!("unknown color"),\n            // otherwise check if the node has a parent and inherit that color\n            None => match parent {{\n                Some(parent) => *parent,\n                None => Self::default(),\n            }},\n        }};\n        // check if the member has changed\n        let changed = new != *self;\n        *self = new;\n        changed\n    }}\n}}\n\n#[derive(Debug, Clone, PartialEq, Default)]\nstruct Border(bool);\n// TextColor only depends on the current node, so it implements NodeDepState\nimpl NodeDepState<()> for Border {{\n    type Ctx = ();\n   \n    // Border does not depended on any other member in the current node\n    const NODE_MASK: NodeMask =\n        NodeMask::new_with_attrs(AttributeMask::Static(&["border"]));\n    fn reduce(&mut self, node: NodeView, _sibling: (), _ctx: &Self::Ctx) -> bool {{\n        // check if the node contians a border attribute\n        let new = Self(node.attributes().next().map(|a| a.name == "border").is_some());\n        // check if the member has changed\n        let changed = new != *self;\n        *self = new;\n        changed\n    }}\n}}\n\n// State provides a derive macro, but anotations on the members are needed in the form #[dep_type(dep_member, CtxType)]\n#[derive(State, Default, Clone)]\nstruct ToyState {{\n    // the color member of it's parent and no context\n    #[parent_dep_state(color)]\n    color: TextColor,\n    // depends on the node, and no context\n    #[node_dep_state()]\n    border: Border,\n    // depends on the layout_width member of children and f32 context (for text size)\n    #[child_dep_state(size, f32)]\n    size: Size,\n}}
\n", + } + p { + "Now that we have our state, we can put it to use in our dom. Re can update the dom with update_state to update the structure of the dom (adding, removing, and changing properties of nodes) and then apply_mutations to update the ToyState for each of the nodes that changed." + } + CodeBlock { + contents: "
\nfn main(){{\n    fn app(cx: Scope) -> Element {{\n        cx.render(rsx!{{\n            div{{\n                color: "red",\n                "hello world"\n            }}\n        }})\n    }}\n    let vdom = VirtualDom::new(app);\n    let rdom: RealDom<ToyState> = RealDom::new();\n\n    let mutations = dom.rebuild();\n    // update the structure of the real_dom tree\n    let to_update = rdom.apply_mutations(vec![mutations]);\n    let mut ctx = AnyMap::new();\n    // set the font size to 3.3\n    ctx.insert(3.3f32);\n    // update the ToyState for nodes in the real_dom tree\n    let _to_rerender = rdom.update_state(&dom, to_update, ctx).unwrap();\n\n    // we need to run the vdom in a async runtime\n    tokio::runtime::Builder::new_current_thread()\n        .enable_all()\n        .build()?\n        .block_on(async {{\n            loop{{\n                let wait = vdom.wait_for_work();\n                let mutations = vdom.work_with_deadline(|| false);\n                let to_update = rdom.apply_mutations(mutations);\n                let mut ctx = AnyMap::new();\n                ctx.insert(3.3);\n                let _to_rerender = rdom.update_state(vdom, to_update, ctx).unwrap();\n\n                // render...\n            }}\n        }})\n}}
\n", + } + h2 { id: "layout", + Link { + to: BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::Layout, + }, + class: "header", + "Layout" + } + } + p { + "For most platforms, the layout of the Elements will stay the same. The layout_attributes module provides a way to apply HTML attributes to a stretch layout style." + } + h2 { id: "conclusion", + Link { + to: BookRoute::CustomRendererIndex { + section: CustomRendererIndexSection::Conclusion, + }, + class: "header", + "Conclusion" + } + } + p { + "That should be it! You should have nearly all the knowledge required on how to implement your own renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderers, video game UI, and even augmented reality! If you're interested in contributing to any of these projects, don't be afraid to reach out or join the " + Link { to: "https://discord.gg/XgGxMSkvUM", "community" } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RoadmapSection { + #[default] + Empty, + RoadmapFeatureSet, + Features, + Roadmap, + Core, + Ssr, + Desktop, + Mobile, + BundlingCli, + EssentialHooks, + WorkInProgress, + BuildTool, + ServerComponentSupport, + NativeRendering, +} +impl std::str::FromStr for RoadmapSection { + type Err = RoadmapSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "roadmap--feature-set" => Ok(Self::RoadmapFeatureSet), + "features" => Ok(Self::Features), + "roadmap" => Ok(Self::Roadmap), + "core" => Ok(Self::Core), + "ssr" => Ok(Self::Ssr), + "desktop" => Ok(Self::Desktop), + "mobile" => Ok(Self::Mobile), + "bundling-cli" => Ok(Self::BundlingCli), + "essential-hooks" => Ok(Self::EssentialHooks), + "work-in-progress" => Ok(Self::WorkInProgress), + "build-tool" => Ok(Self::BuildTool), + "server-component-support" => Ok(Self::ServerComponentSupport), + "native-rendering" => Ok(Self::NativeRendering), + _ => Err(RoadmapSectionParseError), + } + } +} +impl std::fmt::Display for RoadmapSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::RoadmapFeatureSet => f.write_str("roadmap--feature-set"), + Self::Features => f.write_str("features"), + Self::Roadmap => f.write_str("roadmap"), + Self::Core => f.write_str("core"), + Self::Ssr => f.write_str("ssr"), + Self::Desktop => f.write_str("desktop"), + Self::Mobile => f.write_str("mobile"), + Self::BundlingCli => f.write_str("bundling-cli"), + Self::EssentialHooks => f.write_str("essential-hooks"), + Self::WorkInProgress => f.write_str("work-in-progress"), + Self::BuildTool => f.write_str("build-tool"), + Self::ServerComponentSupport => f.write_str("server-component-support"), + Self::NativeRendering => f.write_str("native-rendering"), + } + } +} +#[derive(Debug)] +pub struct RoadmapSectionParseError; +impl std::fmt::Display for RoadmapSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RoadmapSectionroadmap--feature-set, features, roadmap, core, ssr, desktop, mobile, bundling-cli, essential-hooks, work-in-progress, build-tool, server-component-support, native-rendering", + )?; + Ok(()) + } +} +impl std::error::Error for RoadmapSectionParseError {} +#[component(no_case_check)] +pub fn Roadmap(section: RoadmapSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "roadmap--feature-set", + Link { + to: BookRoute::Roadmap { + section: RoadmapSection::RoadmapFeatureSet, + }, + class: "header", + "Roadmap & Feature-set" + } + } + p { + "This feature set and roadmap can help you decide if what Dioxus can do today works for you." + } + p { + "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 " + Link { to: "https://discord.gg/XgGxMSkvUM", "joining the discord" } + "." + } + p { "Generally, here's the status of each platform:" } + ul { + li { + p { + strong { "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." + } + } + li { + p { + strong { "SSR" } + ": Dioxus is a great choice for pre-rendering, hydration, and rendering HTML on a web endpoint. Be warned – the VirtualDom is not (currently) " + code { "Send + Sync" } + "." + } + } + li { + p { + strong { "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." + } + } + li { + p { + strong { "Mobile" } + ": Mobile support is very young. You'll be figuring things out as you go and there are not many support crates for peripherals." + } + } + li { + p { + strong { "LiveView" } + ": LiveView support is very young. You'll be figuring things out as you go. Thankfully, none of it is too hard and any work can be upstreamed into Dioxus." + } + } + } + h2 { id: "features", + Link { + to: BookRoute::Roadmap { + section: RoadmapSection::Features, + }, + class: "header", + "Features" + } + } + hr {} + table { + thead { + th { "Feature" } + th { "Status" } + th { "Description" } + } + tr { + th { "Conditional Rendering" } + th { "✅" } + th { "if/then to hide/show component" } + } + tr { + th { "Map, Iterator" } + th { "✅" } + th { "map/filter/reduce to produce rsx!" } + } + tr { + th { "Keyed Components" } + th { "✅" } + th { "advanced diffing with keys" } + } + tr { + th { "Web" } + th { "✅" } + th { "renderer for web browser" } + } + tr { + th { "Desktop (webview)" } + th { "✅" } + th { "renderer for desktop" } + } + tr { + th { "Shared State (Context)" } + th { "✅" } + th { "share state through the tree" } + } + tr { + th { "Hooks" } + th { "✅" } + th { "memory cells in components" } + } + tr { + th { "SSR" } + th { "✅" } + th { "render directly to string" } + } + tr { + th { "Component Children" } + th { "✅" } + th { "cx.children() as a list of nodes" } + } + tr { + th { "Headless components" } + th { "✅" } + th { "components that don't return real elements" } + } + tr { + th { "Fragments" } + th { "✅" } + th { "multiple elements without a real root" } + } + tr { + th { "Manual Props" } + th { "✅" } + th { "Manually pass in props with spread syntax" } + } + tr { + th { "Controlled Inputs" } + th { "✅" } + th { "stateful wrappers around inputs" } + } + tr { + th { "CSS/Inline Styles" } + th { "✅" } + th { "syntax for inline styles/attribute groups" } + } + tr { + th { "Custom elements" } + th { "✅" } + th { "Define new element primitives" } + } + tr { + th { "Suspense" } + th { "✅" } + th { "schedule future render from future/promise" } + } + tr { + th { "Integrated error handling" } + th { "✅" } + th { "Gracefully handle errors with ? syntax" } + } + tr { + th { "NodeRef" } + th { "✅" } + th { "gain direct access to nodes" } + } + tr { + th { "Re-hydration" } + th { "✅" } + th { "Pre-render to HTML to speed up first contentful paint" } + } + tr { + th { "Jank-Free Rendering" } + th { "✅" } + th { "Large diffs are segmented across frames for silky-smooth transitions" } + } + tr { + th { "Effects" } + th { "✅" } + th { "Run effects after a component has been committed to render" } + } + tr { + th { "Portals" } + th { "🛠" } + th { "Render nodes outside of the traditional tree structure" } + } + tr { + th { "Cooperative Scheduling" } + th { "🛠" } + th { "Prioritize important events over non-important events" } + } + tr { + th { "Server Components" } + th { "🛠" } + th { "Hybrid components for SPA and Server" } + } + tr { + th { "Bundle Splitting" } + th { "👀" } + th { "Efficiently and asynchronously load the app" } + } + tr { + th { "Lazy Components" } + th { "👀" } + th { "Dynamically load the new components as the page is loaded" } + } + tr { + th { "1st class global state" } + th { "✅" } + th { "redux/recoil/mobx on top of context" } + } + tr { + th { "Runs natively" } + th { "✅" } + th { "runs as a portable binary w/o a runtime (Node)" } + } + tr { + th { "Subtree Memoization" } + th { "✅" } + th { "skip diffing static element subtrees" } + } + tr { + th { "High-efficiency templates" } + th { "✅" } + th { "rsx! calls are translated to templates on the DOM's side" } + } + tr { + th { "Compile-time correct" } + th { "✅" } + th { "Throw errors on invalid template layouts" } + } + tr { + th { "Heuristic Engine" } + th { "✅" } + th { "track component memory usage to minimize future allocations" } + } + tr { + th { "Fine-grained reactivity" } + th { "👀" } + th { "Skip diffing for fine-grain updates" } + } + } + ul { + li { "✅ = implemented and working" } + li { "🛠 = actively being worked on" } + li { "👀 = not yet implemented or being worked on" } + } + h2 { id: "roadmap", + Link { + to: BookRoute::Roadmap { + section: RoadmapSection::Roadmap, + }, + class: "header", + "Roadmap" + } + } + p { "These Features are planned for the future of Dioxus:" } + h3 { id: "core", + Link { + to: BookRoute::Roadmap { + section: RoadmapSection::Core, + }, + class: "header", + "Core" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Release of Dioxus Core" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Upgrade documentation to include more theory and be more comprehensive" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Support for HTML-side templates for lightning-fast dom manipulation" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Support for multiple renderers for same virtualdom (subtrees)" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Support for ThreadSafe (Send + Sync)" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Support for Portals" + } + } + h3 { id: "ssr", + Link { + to: BookRoute::Roadmap { + section: RoadmapSection::Ssr, + }, + class: "header", + "SSR" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "SSR Support + Hydration" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Integrated suspense support for SSR" + } + } + h3 { id: "desktop", + Link { + to: BookRoute::Roadmap { + section: RoadmapSection::Desktop, + }, + class: "header", + "Desktop" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Declarative window management" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Templates for building/bundling" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Fully native renderer" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Access to Canvas/WebGL context natively" + } + } + h3 { id: "mobile", + Link { + to: BookRoute::Roadmap { + section: RoadmapSection::Mobile, + }, + class: "header", + "Mobile" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Mobile standard library" + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "GPS" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Camera" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "filesystem" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Biometrics" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "WiFi" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Bluetooth" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Notifications" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Clipboard" + } + } + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Animations" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Native Renderer" + } + } + h3 { id: "bundling-cli", + Link { + to: BookRoute::Roadmap { + section: RoadmapSection::BundlingCli, + }, + class: "header", + "Bundling (CLI)" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Translation from HTML into RSX" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Dev server" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Live reload" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Translation from JSX into RSX" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Hot module replacement" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Code splitting" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Asset macros" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Css pipeline" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Image pipeline" + } + } + h3 { id: "essential-hooks", + Link { + to: BookRoute::Roadmap { + section: RoadmapSection::EssentialHooks, + }, + class: "header", + "Essential hooks" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Router" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Global state management" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Resize observer" + } + } + h2 { id: "work-in-progress", + Link { + to: BookRoute::Roadmap { + section: RoadmapSection::WorkInProgress, + }, + class: "header", + "Work in Progress" + } + } + h3 { id: "build-tool", + Link { + to: BookRoute::Roadmap { + section: RoadmapSection::BuildTool, + }, + class: "header", + "Build Tool" + } + } + p { + "We are currently working on our own build tool called " + Link { to: "https://github.com/DioxusLabs/cli", "Dioxus CLI" } + " which will support:" + } + ul { + li { "an interactive TUI" } + li { "on-the-fly reconfiguration" } + li { "hot CSS reloading" } + li { "two-way data binding between browser and source code" } + li { + "an interpreter for " + code { "rsx!" } + } + li { "ability to publish to github/netlify/vercel" } + li { "bundling for iOS/Desktop/etc" } + } + h3 { id: "server-component-support", + Link { + to: BookRoute::Roadmap { + section: RoadmapSection::ServerComponentSupport, + }, + class: "header", + "Server Component Support" + } + } + p { + "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." + } + h3 { id: "native-rendering", + Link { + to: BookRoute::Roadmap { + section: RoadmapSection::NativeRendering, + }, + class: "header", + "Native rendering" + } + } + p { + "We are currently working on a native renderer for Dioxus using WGPU called " + Link { to: "https://github.com/DioxusLabs/blitz/", "Blitz" } + ". This will allow you to build apps that are rendered natively for iOS, Android, and Desktop." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ContributingSection { + #[default] + Empty, + Contributing, + ImprovingDocs, + WorkingOnTheEcosystem, + BugsFeatures, +} +impl std::str::FromStr for ContributingSection { + type Err = ContributingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "contributing" => Ok(Self::Contributing), + "improving-docs" => Ok(Self::ImprovingDocs), + "working-on-the-ecosystem" => Ok(Self::WorkingOnTheEcosystem), + "bugs--features" => Ok(Self::BugsFeatures), + _ => Err(ContributingSectionParseError), + } + } +} +impl std::fmt::Display for ContributingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Contributing => f.write_str("contributing"), + Self::ImprovingDocs => f.write_str("improving-docs"), + Self::WorkingOnTheEcosystem => f.write_str("working-on-the-ecosystem"), + Self::BugsFeatures => f.write_str("bugs--features"), + } + } +} +#[derive(Debug)] +pub struct ContributingSectionParseError; +impl std::fmt::Display for ContributingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ContributingSectioncontributing, improving-docs, working-on-the-ecosystem, bugs--features", + )?; + Ok(()) + } +} +impl std::error::Error for ContributingSectionParseError {} +#[component(no_case_check)] +pub fn Contributing(section: ContributingSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "contributing", + Link { + to: BookRoute::Contributing { + section: ContributingSection::Contributing, + }, + class: "header", + "Contributing" + } + } + p { + "Development happens in the " + Link { to: "https://github.com/DioxusLabs/dioxus", "Dioxus GitHub repository" } + ". If you've found a bug or have an idea for a feature, please submit an issue (but first check if someone hasn't " + Link { to: "https://github.com/DioxusLabs/dioxus/issues", "done it already" } + ")." + } + p { + Link { to: "https://github.com/DioxusLabs/dioxus/discussions", "GitHub discussions" } + " can be used as a place to ask for help or talk about features. You can also join " + Link { to: "https://discord.gg/XgGxMSkvUM", "our Discord channel" } + " where some development discussion happens." + } + h2 { id: "improving-docs", + Link { + to: BookRoute::Contributing { + section: ContributingSection::ImprovingDocs, + }, + class: "header", + "Improving Docs" + } + } + p { + "If you'd like to improve the docs, PRs are welcome! Both Rust docs (" + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages", "source" } + ") and this guide (" + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/docs/guide", + "source" + } + ") can be found in the GitHub repo." + } + h2 { id: "working-on-the-ecosystem", + Link { + to: BookRoute::Contributing { + section: ContributingSection::WorkingOnTheEcosystem, + }, + class: "header", + "Working on the Ecosystem" + } + } + p { + "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 many people would benefit from, it will be appreciated. You can " + Link { to: "https://www.npmjs.com/search?q=keywords:react-component", "browse npm.js" } + " for inspiration." + } + h2 { id: "bugs--features", + Link { + to: BookRoute::Contributing { + section: ContributingSection::BugsFeatures, + }, + class: "header", + "Bugs & Features" + } + } + p { + "If you've fixed " + Link { to: "https://github.com/DioxusLabs/dioxus/issues", "an open issue" } + ", feel free to submit a PR! You can also take a look at " + Link { + to: BookRoute::Roadmap { + section: RoadmapSection::Empty, + }, + "the roadmap" + } + " and work on something in there. Consider " + Link { to: "https://discord.gg/XgGxMSkvUM", "reaching out" } + " to the team first to make sure everyone's on the same page, and you don't do useless work!" + } + p { + "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." + } + } +} + +use super::*; diff --git a/packages/docs-router/src/docs/router_04.rs b/packages/docs-router/src/docs/router_04.rs new file mode 100644 index 000000000..238a50f40 --- /dev/null +++ b/packages/docs-router/src/docs/router_04.rs @@ -0,0 +1,21423 @@ +use dioxus::prelude::*; +#[derive( + Clone, + Copy, + dioxus_router::prelude::Routable, + PartialEq, + Eq, + Hash, + Debug, + serde::Serialize, + serde::Deserialize, +)] +pub enum BookRoute { + #[route("/#:section")] + Index { section: IndexSection }, + #[route("/getting_started/#:section")] + GettingStartedIndex { section: GettingStartedIndexSection }, + #[route("/getting_started/choosing_a_web_renderer#:section")] + GettingStartedChoosingAWebRenderer { + section: GettingStartedChoosingAWebRendererSection, + }, + #[route("/getting_started/wasm#:section")] + GettingStartedWasm { section: GettingStartedWasmSection }, + #[route("/getting_started/liveview#:section")] + GettingStartedLiveview { + section: GettingStartedLiveviewSection, + }, + #[route("/getting_started/fullstack#:section")] + GettingStartedFullstack { + section: GettingStartedFullstackSection, + }, + #[route("/getting_started/desktop#:section")] + GettingStartedDesktop { + section: GettingStartedDesktopSection, + }, + #[route("/getting_started/mobile#:section")] + GettingStartedMobile { + section: GettingStartedMobileSection, + }, + #[route("/getting_started/tui#:section")] + GettingStartedTui { section: GettingStartedTuiSection }, + #[route("/guide/#:section")] + GuideIndex { section: GuideIndexSection }, + #[route("/guide/your_first_component#:section")] + GuideYourFirstComponent { + section: GuideYourFirstComponentSection, + }, + #[route("/guide/state#:section")] + GuideState { section: GuideStateSection }, + #[route("/guide/data_fetching#:section")] + GuideDataFetching { section: GuideDataFetchingSection }, + #[route("/guide/full_code#:section")] + GuideFullCode { section: GuideFullCodeSection }, + #[route("/reference/#:section")] + ReferenceIndex { section: ReferenceIndexSection }, + #[route("/reference/rsx#:section")] + ReferenceRsx { section: ReferenceRsxSection }, + #[route("/reference/components#:section")] + ReferenceComponents { section: ReferenceComponentsSection }, + #[route("/reference/component_props#:section")] + ReferenceComponentProps { + section: ReferenceComponentPropsSection, + }, + #[route("/reference/event_handlers#:section")] + ReferenceEventHandlers { + section: ReferenceEventHandlersSection, + }, + #[route("/reference/hooks#:section")] + ReferenceHooks { section: ReferenceHooksSection }, + #[route("/reference/user_input#:section")] + ReferenceUserInput { section: ReferenceUserInputSection }, + #[route("/reference/context#:section")] + ReferenceContext { section: ReferenceContextSection }, + #[route("/reference/dynamic_rendering#:section")] + ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection, + }, + #[route("/reference/router#:section")] + ReferenceRouter { section: ReferenceRouterSection }, + #[route("/reference/use_future#:section")] + ReferenceUseFuture { section: ReferenceUseFutureSection }, + #[route("/reference/use_coroutine#:section")] + ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection, + }, + #[route("/reference/spawn#:section")] + ReferenceSpawn { section: ReferenceSpawnSection }, + #[route("/reference/desktop/#:section")] + ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection, + }, + #[route("/reference/web/#:section")] + ReferenceWebIndex { section: ReferenceWebIndexSection }, + #[route("/reference/ssr#:section")] + ReferenceSsr { section: ReferenceSsrSection }, + #[route("/reference/liveview#:section")] + ReferenceLiveview { section: ReferenceLiveviewSection }, + #[route("/reference/fullstack/#:section")] + ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection, + }, + #[route("/reference/fullstack/server_functions#:section")] + ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection, + }, + #[route("/reference/fullstack/extractors#:section")] + ReferenceFullstackExtractors { + section: ReferenceFullstackExtractorsSection, + }, + #[route("/reference/fullstack/middleware#:section")] + ReferenceFullstackMiddleware { + section: ReferenceFullstackMiddlewareSection, + }, + #[route("/reference/fullstack/authentication#:section")] + ReferenceFullstackAuthentication { + section: ReferenceFullstackAuthenticationSection, + }, + #[route("/reference/fullstack/routing#:section")] + ReferenceFullstackRouting { + section: ReferenceFullstackRoutingSection, + }, + #[route("/router/#:section")] + RouterIndex { section: RouterIndexSection }, + #[route("/router/example/#:section")] + RouterExampleIndex { section: RouterExampleIndexSection }, + #[route("/router/example/first-route#:section")] + RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection, + }, + #[route("/router/example/building-a-nest#:section")] + RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection, + }, + #[route("/router/example/navigation-targets#:section")] + RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection, + }, + #[route("/router/example/redirection-perfection#:section")] + RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection, + }, + #[route("/router/example/full-code#:section")] + RouterExampleFullCode { + section: RouterExampleFullCodeSection, + }, + #[route("/router/reference/#:section")] + RouterReferenceIndex { + section: RouterReferenceIndexSection, + }, + #[route("/router/reference/routes/#:section")] + RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection, + }, + #[route("/router/reference/routes/nested#:section")] + RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection, + }, + #[route("/router/reference/layouts#:section")] + RouterReferenceLayouts { + section: RouterReferenceLayoutsSection, + }, + #[route("/router/reference/navigation/#:section")] + RouterReferenceNavigationIndex { + section: RouterReferenceNavigationIndexSection, + }, + #[route("/router/reference/navigation/programmatic#:section")] + RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection, + }, + #[route("/router/reference/history-providers#:section")] + RouterReferenceHistoryProviders { + section: RouterReferenceHistoryProvidersSection, + }, + #[route("/router/reference/history-buttons#:section")] + RouterReferenceHistoryButtons { + section: RouterReferenceHistoryButtonsSection, + }, + #[route("/router/reference/static-generation#:section")] + RouterReferenceStaticGeneration { + section: RouterReferenceStaticGenerationSection, + }, + #[route("/router/reference/routing-update-callback#:section")] + RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection, + }, + #[route("/cookbook/#:section")] + CookbookIndex { section: CookbookIndexSection }, + #[route("/cookbook/publishing#:section")] + CookbookPublishing { section: CookbookPublishingSection }, + #[route("/cookbook/antipatterns#:section")] + CookbookAntipatterns { + section: CookbookAntipatternsSection, + }, + #[route("/cookbook/error_handling#:section")] + CookbookErrorHandling { + section: CookbookErrorHandlingSection, + }, + #[route("/cookbook/integrations/#:section")] + CookbookIntegrationsIndex { + section: CookbookIntegrationsIndexSection, + }, + #[route("/cookbook/integrations/logging#:section")] + CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection, + }, + #[route("/cookbook/integrations/internationalization#:section")] + CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection, + }, + #[route("/cookbook/state/#:section")] + CookbookStateIndex { section: CookbookStateIndexSection }, + #[route("/cookbook/state/external/#:section")] + CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection, + }, + #[route("/cookbook/state/custom_hooks/#:section")] + CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection, + }, + #[route("/cookbook/testing#:section")] + CookbookTesting { section: CookbookTestingSection }, + #[route("/cookbook/examples#:section")] + CookbookExamples { section: CookbookExamplesSection }, + #[route("/cookbook/tailwind#:section")] + CookbookTailwind { section: CookbookTailwindSection }, + #[route("/cookbook/custom_renderer#:section")] + CookbookCustomRenderer { + section: CookbookCustomRendererSection, + }, + #[route("/cookbook/optimizing#:section")] + CookbookOptimizing { section: CookbookOptimizingSection }, + #[route("/CLI/#:section")] + CliIndex { section: CliIndexSection }, + #[route("/CLI/installation#:section")] + CliInstallation { section: CliInstallationSection }, + #[route("/CLI/creating#:section")] + CliCreating { section: CliCreatingSection }, + #[route("/CLI/configure#:section")] + CliConfigure { section: CliConfigureSection }, + #[route("/CLI/translate#:section")] + CliTranslate { section: CliTranslateSection }, + #[route("/contributing/#:section")] + ContributingIndex { section: ContributingIndexSection }, + #[route("/contributing/project_structure#:section")] + ContributingProjectStructure { + section: ContributingProjectStructureSection, + }, + #[route("/contributing/walkthrough_readme#:section")] + ContributingWalkthroughReadme { + section: ContributingWalkthroughReadmeSection, + }, + #[route("/contributing/guiding_principles#:section")] + ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection, + }, + #[route("/contributing/roadmap#:section")] + ContributingRoadmap { section: ContributingRoadmapSection }, + #[route("/migration/#:section")] + MigrationIndex { section: MigrationIndexSection }, + #[route("/migration/router#:section")] + MigrationRouter { section: MigrationRouterSection }, + #[route("/migration/hot_reload#:section")] + MigrationHotReload { section: MigrationHotReloadSection }, +} +impl BookRoute { + /// Get the markdown for a page by its ID + pub const fn page_markdown(id: use_mdbook::mdbook_shared::PageId) -> &'static str { + match id.0 { + 44usize => { + "# Adding the router to your application\n\nIn this chapter, we will learn how to add the router to our app. By itself, this\nis not very useful. However, it is a prerequisite for all the functionality\ndescribed in the other chapters.\n\n > \n > Make sure you added the `dioxus-router` dependency as explained in the\n > [introduction](../index.md).\n\nIn most cases, we want to add the router to the root component of our app. This\nway, we can ensure that we have access to all its functionality everywhere.\n\nFirst, we define the router with the router macro:\n\n````rust@first_route.rs\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n/// An enum of all of the possible routes in the app.\n#[derive(Routable, Clone)]\nenum Route {\n // The home page is at the / route\n #[route(\"/\")]\n // If the name of the component and variant are the same you can omit the component and props name\n // If they are different you can specify them like this:\n // #[route(\"/\", ComponentName, PropsName)]\n Home {},\n}\n````\n\nThen we render the router with the \\[`Router`\\] component.\n\n````rust@first_route.rs\nfn App(cx: Scope) -> Element {\n render! {\n Router:: {}\n }\n}\n````" + } + 25usize => { + "# Coroutines\n\nAnother tool in your async toolbox are coroutines. Coroutines are futures that can have values sent to them.\n\nLike regular futures, code in a coroutine will run until the next `await` point before yielding. This low-level control over asynchronous tasks is quite powerful, allowing for infinitely looping tasks like WebSocket polling, background timers, and other periodic actions.\n\n## use_coroutine\n\nThe `use_coroutine` hook allows you to create a coroutine. Most coroutines we write will be polling loops using await.\n\n````rust, no_run@use_coroutine_reference.rs\nuse futures_util::StreamExt;\n\nfn app(cx: Scope) {\n let ws: &Coroutine<()> = use_coroutine(cx, |rx| async move {\n // Connect to some sort of service\n let mut conn = connect_to_ws_server().await;\n\n // Wait for data on the service\n while let Some(msg) = conn.next().await {\n // handle messages\n }\n });\n}\n````\n\nFor many services, a simple async loop will handle the majority of use cases.\n\n## Yielding Values\n\nTo yield values from a coroutine, simply bring in a `UseState` handle and set the value whenever your coroutine completes its work.\n\nThe future must be `'static` – so any values captured by the task cannot carry any references to `cx`, such as a `UseState`.\n\nYou can use [to_owned](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned) to create a clone of the hook handle which can be moved into the async closure.\n\n````rust, no_run@use_coroutine_reference.rs\nlet sync_status = use_state(cx, || Status::Launching);\nlet sync_task = use_coroutine(cx, |rx: UnboundedReceiver| {\n let sync_status = sync_status.to_owned();\n async move {\n loop {\n tokio::time::sleep(Duration::from_secs(1)).await;\n sync_status.set(Status::Working);\n }\n }\n});\n````\n\nTo 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.\n\n````rust, no_run@use_coroutine_reference.rs\nlet sync_status = use_state(cx, || Status::Launching);\nlet load_status = use_state(cx, || Status::Launching);\nlet sync_task = use_coroutine(cx, |rx: UnboundedReceiver| {\n to_owned![sync_status, load_status];\n async move {\n // ...\n }\n});\n````\n\n## Sending Values\n\nYou might've noticed the `use_coroutine` closure takes an argument called `rx`. What is that? Well, a common pattern in complex apps is to handle a bunch of async code at once. With libraries like Redux Toolkit, managing multiple promises at once can be challenging and a common source of bugs.\n\nWith Coroutines, we can centralize our async logic. The `rx` parameter is an Channel that allows code external to the coroutine to send data *into* the coroutine. Instead of looping on an external service, we can loop on the channel itself, processing messages from within our app without needing to spawn a new future. To send data into the coroutine, we would call \"send\" on the handle.\n\n````rust, no_run@use_coroutine_reference.rs\nuse futures_util::StreamExt;\n\nenum ProfileUpdate {\n SetUsername(String),\n SetAge(i32),\n}\n\nlet profile = use_coroutine(cx, |mut rx: UnboundedReceiver| async move {\n let mut server = connect_to_server().await;\n\n while let Some(msg) = rx.next().await {\n match msg {\n ProfileUpdate::SetUsername(name) => server.update_username(name).await,\n ProfileUpdate::SetAge(age) => server.update_age(age).await,\n }\n }\n});\n\ncx.render(rsx! {\n button {\n onclick: move |_| profile.send(ProfileUpdate::SetUsername(\"Bob\".to_string())),\n \"Update username\"\n }\n})\n````\n\n > \n > Note: In order to use/run the `rx.next().await` statement you will need to extend the \\[`Stream`\\] trait (used by \\[`UnboundedReceiver`\\]) by adding 'futures_util' as a dependency to your project and adding the `use futures_util::stream::StreamExt;`.\n\nFor sufficiently complex apps, we could build a bunch of different useful \"services\" that loop on channels to update the app.\n\n````rust, no_run@use_coroutine_reference.rs\nlet profile = use_coroutine(cx, profile_service);\nlet editor = use_coroutine(cx, editor_service);\nlet sync = use_coroutine(cx, sync_service);\n\nasync fn profile_service(rx: UnboundedReceiver) {\n // do stuff\n}\n\nasync fn sync_service(rx: UnboundedReceiver) {\n // do stuff\n}\n\nasync fn editor_service(rx: UnboundedReceiver) {\n // do stuff\n}\n````\n\nWe can combine coroutines with [Fermi](https://docs.rs/fermi/latest/fermi/index.html) 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.\n\n````rust, no_run@use_coroutine_reference.rs\nstatic USERNAME: Atom = Atom(|_| \"default\".to_string());\n\nfn app(cx: Scope) -> Element {\n let atoms = use_atom_root(cx);\n\n use_coroutine(cx, |rx| sync_service(rx, atoms.clone()));\n\n cx.render(rsx! {\n Banner {}\n })\n}\n\nfn Banner(cx: Scope) -> Element {\n let username = use_read(cx, &USERNAME);\n\n cx.render(rsx! {\n h1 { \"Welcome back, {username}\" }\n })\n}\n````\n\nNow, in our sync service, we can structure our state however we want. We only need to update the view values when ready.\n\n````rust, no_run@use_coroutine_reference.rs\n\n````\n\n## Automatic injection into the Context API\n\nCoroutine handles are automatically injected through the context API. You can use the `use_coroutine_handle` hook with the message type as a generic to fetch a handle.\n\n````rust, no_run@use_coroutine_reference.rs\nfn Child(cx: Scope) -> Element {\n let sync_task = use_coroutine_handle::(cx).unwrap();\n\n sync_task.send(SyncAction::SetUsername);\n\n todo!()\n}\n````" + } + 41usize => { + "# Navigation Targets\n\nIn the previous chapter, we learned how to create links to pages within our app.\nWe told them where to go using the `target` property. This property takes something that can be converted to a [`NavigationTarget`].\n\n## What is a navigation target?\n\nA [`NavigationTarget`] is similar to the `href` of an HTML anchor element. It\ntells the router where to navigate to. The Dioxus Router knows two kinds of\nnavigation targets:\n\n* [`Internal`]: We used internal links in the previous chapter. It's a link to a page within our\n app represented as a Route enum.\n* [`External`]: This works exactly like an HTML anchors' `href`. Don't use this for in-app\n navigation as it will trigger a page reload by the browser.\n\n## External navigation\n\nIf we need a link to an external page we can do it like this:\n\n````rust@external_link.rs\nfn GoToDioxus(cx: Scope) -> Element {\n render! {\n Link {\n to: \"https://dioxuslabs.com\",\n \"ExternalTarget target\"\n }\n }\n}\n````" + } + 2usize => { + "# Choosing a web renderer\n\nDioxus has three different renderers that target the web:\n\n* [dioxus-web](wasm.md) allows you to render your application to HTML with [WebAssembly](https://rustwasm.github.io/docs/book/) on the client\n* [dioxus-liveview](liveview.md) allows you to run your application on the server and render it to HTML on the client with a websocket\n* [dioxus-fullstack](fullstack.md) allows you to initially render static HTML on the server and then update that HTML from the client with [WebAssembly](https://rustwasm.github.io/docs/book/)\n\nEach approach has its tradeoffs:\n\n### Dioxus Liveview\n\n* Liveview rendering communicates with the server over a WebSocket connection. It essentially moves all of the work that Client-side rendering does to the server.\n\n* This makes it **easy to communicate with the server, but more difficult to communicate with the client/browser APIS**.\n\n* Each interaction also requires a message to be sent to the server and back which can cause **issues with latency**.\n\n* Because Liveview uses a websocket to render, the page will be blank until the WebSocket connection has been established and the first renderer has been sent from the websocket. Just like with client side rendering, this can make the page **less SEO-friendly**.\n\n* Because the page is rendered on the server and the page is sent to the client piece by piece, you never need to send the entire application to the client. The initial load time can be faster than client-side rendering with large applications because Liveview only needs to send a constant small websocket script regardless of the size of the application.\n\n > \n > Liveview is a good fit for applications that already need to communicate with the server frequently (like real time collaborative apps), but don't need to communicate with as many client/browser APIs.\n\n[![](https://mermaid.ink/img/pako:eNplULFOw0AM_RXLc7Mw3sBQVUIMRYgKdcli5ZzkRHIuPl8QqvrvXJICRXiy3nt-9-6dsRHP6DAZGe8CdUpjNd3VEcpsVT4SK1TVPRxYJ1YHL_yeOdkqWMGF3w4U32Y6nSQmXvknMQYNXW8g7bfk2JPBg0g3MCTmdH1rJhenx2is1FiYri43wJ8or3O2H1Liv0w3hw724kMb2MMzdcUYNziyjhR8-f15Pq3Reh65RldWzy3lwWqs46VIKZscPmODzjTzBvPJ__aFrqUhFZR9MNH92uhS7OULYSF1lw?type=png)](https://mermaid.live/edit#pako:eNplULFOw0AM_RXLc7Mw3sBQVUIMRYgKdcli5ZzkRHIuPl8QqvrvXJICRXiy3nt-9-6dsRHP6DAZGe8CdUpjNd3VEcpsVT4SK1TVPRxYJ1YHL_yeOdkqWMGF3w4U32Y6nSQmXvknMQYNXW8g7bfk2JPBg0g3MCTmdH1rJhenx2is1FiYri43wJ8or3O2H1Liv0w3hw724kMb2MMzdcUYNziyjhR8-f15Pq3Reh65RldWzy3lwWqs46VIKZscPmODzjTzBvPJ__aFrqUhFZR9MNH92uhS7OULYSF1lw)\n\n### Dioxus Web\n\n* With Client side rendering, you send your application to the client, and then the client generates all of the HTML of the page dynamically.\n\n* This means that the page will be blank until the JavaScript bundle has loaded and the application has initialized. This can result in **slower first render times and poor SEO performance**.\n\n > \n > SEO stands for Search Engine Optimization. It refers to the practice of making your website more likely to appear in search engine results. Search engines like Google and Bing use web crawlers to index the content of websites. Most of these crawlers are not able to run JavaScript, so they will not be able to index the content of your page if it is rendered client-side.\n\n* Client-side rendered applications need to use **weakly typed requests to communicate with the server**.\n\n > \n > Client-side rendering is a good starting point for most applications. It is well supported and makes it easy to communicate with the client/browser APIs.\n\n[![](https://mermaid.ink/img/pako:eNpVkDFPwzAQhf-KdXOzMHpgqJAQAwytEIsXK35JLBJfez4Xoar_HSemQtzke9_z2e-u1HMAWcrqFU_Rj-KX7vLgkqm1F_7KENN1j-YIuUCsOeBckLUZmrjx_ezT54rziVNG42-sMBLHSQ0Pd8vH5NU8M48zTAby71sr3CYdkAIEoen37h-y5n3910tSiO81cqIdLZDFx1DDXNerjnTCAke2HgMGX2Z15NKtWn1RPn6nnqxKwY7KKfzFJzv4OVcVISrLa1vQtqfbDzd0ZKY?type=png)](https://mermaid.live/edit#pako:eNpVkDFPwzAQhf-KdXOzMHpgqJAQAwytEIsXK35JLBJfez4Xoar_HSemQtzke9_z2e-u1HMAWcrqFU_Rj-KX7vLgkqm1F_7KENN1j-YIuUCsOeBckLUZmrjx_ezT54rziVNG42-sMBLHSQ0Pd8vH5NU8M48zTAby71sr3CYdkAIEoen37h-y5n3910tSiO81cqIdLZDFx1DDXNerjnTCAke2HgMGX2Z15NKtWn1RPn6nnqxKwY7KKfzFJzv4OVcVISrLa1vQtqfbDzd0ZKY)\n\n### Dioxus Fullstack\n\nFullstack rendering happens in two parts:\n\n1. The page is rendered on the server. This can include fetching any data you need to render the page.\n1. The page is hydrated on the client. (Hydration is taking the HTML page from the server and adding all of the event listeners Dioxus needs on the client). Any updates to the page happen on the client after this point.\n\nBecause the page is initially rendered on the server, the page will be fully rendered when it is sent to the client. This results in a faster first render time and makes the page more SEO-friendly.\n\n* **Fast initial render**\n* **Works well with SEO**\n* **Type safe easy communication with the server**\n* **Access to the client/browser APIs**\n* **Fast interactivity**\n\nFinally, we can use [server functions](../reference/fullstack/server_functions.md) to communicate with the server in a type-safe way.\n\nThis approach uses both the dioxus-web and dioxus-ssr crates. To integrate those two packages and `axum`, `warp`, or `salvo`, Dioxus provides the `dioxus-fullstack` crate.\n\nThere can be more complexity with fullstack applications because your code runs in two different places. Dioxus tries to mitigate this with server functions and other helpers.\n\n[![](https://mermaid.ink/img/pako:eNpdkL1uwzAMhF9F4BwvHTV0KAIUHdohQdFFi2CdbQG2mFCUiyDIu9e2-hOUE3H34UDelVoOIEtZvWIffS9-auYHl8wyT8KfGWKa5tEcITPEmgPOBVkrUMXNPyAFCMJK5BOnjIq8scJI7Ac13N1RH4NX88zcjzAZyJX-8bfIl6QQ32qcv7PuhP-ANe_rpb8KJ9rRBJl8DMt71zXAkQ6Y4Mgua0Dny6iOXLotqC_Kx0tqyaoU7Kicwl8hZDs_5kVFiMryWivbmrt9AacxbGg?type=png)](https://mermaid.live/edit#pako:eNpdkL1uwzAMhF9F4BwvHTV0KAIUHdohQdFFi2CdbQG2mFCUiyDIu9e2-hOUE3H34UDelVoOIEtZvWIffS9-auYHl8wyT8KfGWKa5tEcITPEmgPOBVkrUMXNPyAFCMJK5BOnjIq8scJI7Ac13N1RH4NX88zcjzAZyJX-8bfIl6QQ32qcv7PuhP-ANe_rpb8KJ9rRBJl8DMt71zXAkQ6Y4Mgua0Dny6iOXLotqC_Kx0tqyaoU7Kicwl8hZDs_5kVFiMryWivbmrt9AacxbGg)" + } + 3usize => { + "# Web\n\nBuild 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` and `dioxus-web` crates.\n\nA build of Dioxus for the web will be roughly equivalent to the size of a React build (70kb vs 65kb) but it will load significantly faster because [WebAssembly can be compiled as it is streamed](https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/).\n\nExamples:\n\n* [TodoMVC](https://github.com/DioxusLabs/example-projects/tree/master/todomvc)\n* [ECommerce](https://github.com/DioxusLabs/example-projects/tree/master/ecommerce-site)\n\n[![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/example-projects/blob/master/todomvc)\n\n > \n > Note: Because of the limitations of Wasm, [not every crate will work](https://rustwasm.github.io/docs/book/reference/which-crates-work-with-wasm.html) with your web apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc).\n\n## Support\n\nThe Web is the best-supported target platform for Dioxus.\n\n* Because your app will be compiled to WASM you have access to browser APIs through [wasm-bindgen](https://rustwasm.github.io/docs/wasm-bindgen/introduction.html).\n* Dioxus provides hydration to resume apps that are rendered on the server. See the [fullstack](fullstack.md) getting started guide for more information.\n\n## Tooling\n\nTo develop your Dioxus app for the web, you'll need a tool to build and serve your assets. We recommend using [dioxus-cli](https://github.com/DioxusLabs/dioxus/tree/master/packages/cli) which includes a build system, Wasm optimization, a dev server, and support hot reloading:\n\n````shell\ncargo install dioxus-cli\n````\n\nMake sure the `wasm32-unknown-unknown` target for rust is installed:\n\n````shell\nrustup target add wasm32-unknown-unknown\n````\n\n## Creating a Project\n\nCreate a new crate:\n\n````shell\ncargo new --bin demo\ncd demo\n````\n\nAdd Dioxus and the web renderer as dependencies (this will edit your `Cargo.toml`):\n\n````bash\ncargo add dioxus\ncargo add dioxus-web\n````\n\nEdit your `main.rs`:\n\n````rust@hello_world_web.rs\n#![allow(non_snake_case)]\n// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types\nuse dioxus::prelude::*;\n\nfn main() {\n // launch the web app\n dioxus_web::launch(App);\n}\n\n// create a component that renders a div with the text \"Hello, world!\"\nfn App(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n \"Hello, world!\"\n }\n })\n}\n\n````\n\nAnd to serve our app:\n\n````bash\ndx serve\n````\n\nIf you open the browser and navigate to `127.0.0.1` you should see an app that looks like this:\n\n````inject-dioxus\nDemoFrame {\n hello_world::HelloWorldCounter {}\n}\n````\n\n## Hot Reload\n\n1. Hot reloading allows much faster iteration times inside of rsx calls by interpreting them and streaming the edits.\n1. It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program.\n\nFor the web renderer, you can use the dioxus cli to serve your application with hot reloading enabled.\n\n### Setup\n\nInstall [dioxus-cli](https://github.com/DioxusLabs/dioxus/tree/master/packages/cli).\n\n### Usage\n\n1. Run:\n\n````bash\ndx serve --hot-reload\n````\n\n2. Change some code within a rsx or render macro\n2. Open your localhost in a browser\n2. Save and watch the style change without recompiling\n\n### Limitations\n\n1. The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will require a full recompile to capture the expression.\n1. Components, Iterators, and some attributes can contain arbitrary rust code and will trigger a full recompile when changed." + } + 14usize => { + "# Dioxus Reference\n\nThis Reference contains more detailed explanations for all concepts covered in the [guide](../guide/index.md) and more.\n\n## Rendering\n\n* [`RSX`](rsx.md) Rsx is a HTML-like macro that allows you to declare UI\n* [`Components`](components.md) Components are the building blocks of UI in Dioxus\n* [`Props`](component_props.md) Props allow you pass information to Components\n* [`Event Listeners`](event_handlers.md) Event listeners let you respond to user input\n* [`User Input`](user_input.md) How to handle User input in Dioxus\n* [`Dynamic Rendering`](dynamic_rendering.md) How to dynamically render data in Dioxus\n\n## State\n\n* [`Hooks`](hooks.md): Hooks allow you to create components state\n* [`Context`](context.md): Context allows you to create state in a parent and consume it in children\n* [`Routing`](router.md): The router helps you manage the URL state\n* [`UseFuture`](use_future.md): Use future allows you to create an async task and monitor it's state\n* [`UseCoroutine`](use_coroutine.md): Use coroutine helps you manage external state\n* [`Spawn`](spawn.md): Spawn creates an async task\n\n## Platforms\n\n* [`Desktop`](desktop/index.md): Overview of desktop specific APIS\n* [`Web`](web/index.md): Overview of web specific APIS\n* [`SSR`](ssr.md): Overview of the SSR renderer\n* [`Liveview`](liveview.md): Overview of liveview specific APIS\n* [`Fullstack`](fullstack/index.md): Overview of Fullstack specific APIS\n * [`Server Functions`](fullstack/server_functions.md): Server functions make it easy to communicate between your server and client\n * [`Extractors`](fullstack/extractors.md): Extractors allow you to get extra information out of the headers of a request\n * [`Middleware`](fullstack/middleware.md): Middleware allows you to wrap a server function request or response\n * [`Authentication`](fullstack/authentication.md): An overview of how to handle authentication with server functions\n * [`Routing`](fullstack/routing.md): An overview of how to work with the router in the fullstack renderer" + } + 30usize => { + "# Liveview\n\nThis guide will cover concepts specific to the Dioxus liveview renderer.\n\n## Router Integration\n\nCurrently, the Dioxus router does not integrate with the browser history in the liveview renderer. If you are interested in contributing this feature to Dioxus this issue is tracked [here](https://github.com/DioxusLabs/dioxus/issues/1038).\n\n## Managing Latency\n\nLiveview makes it incredibly convenient to talk to your server from the client, but there are some downsides. Mainly in Dioxus Liveview every interaction goes through the server by default.\n\nBecause of this, with the liveview renderer you need to be very deliberate about managing latency. Events that would be fast enough on other renderers like [controlled inputs](../reference/user_input.md), can be frustrating to use in the liveview renderer.\n\nTo get around this issue you can inject bits of javascript in your liveview application. If you use a raw attribute as a listener, you can inject some javascript that will be run when the event is triggered:\n\n````rust\nrender! {\n div {\n input {\n \"oninput\": \"console.log('input changed!')\"\n }\n }\n}\n````" + } + 59usize => { + "# Logging\n\nDioxus has a wide range of supported platforms, each with their own logging requirements. We'll discuss the different options available to you.\n\n#### The Log Crate\n\nThe [Log](https://crates.io/crates/log) crate is the most universally recognized logging facade in Rust. It is also the easiest to work with in Dioxus; therefore we will be focusing on loggers that work with this crate.\n\nThe log crate provides a variety of simple `println`-like macros with varying levels of severity.\nThe available macros are as follows with the highest severity on the bottom:\n\n````rs\nfn main() {\n log::trace!(\"trace\");\n log::debug!(\"debug\");\n log::info!(\"info\");\n log::warn!(\"warn\");\n log::error!(\"error\");\n}\n````\n\nAll the loggers provided on this page are, besides configuration and initialization, interfaced using these macros. Often you will also utilize the log crate's `LevelFilter` enum. This enum usually represents the lowest log severity you want your application to emit and can be loaded from a configuration file, environment variable, or other.\n\nFor more information, visit log crate's [docs](https://docs.rs/log/latest/log/).\n\n## Dioxus Logger\n\n[Dioxus Logger](https://crates.io/crates/dioxus-logger) is a planned-to-be feature-rich logger that supports all of Dioxus' platforms. Currently only Desktop, Web, and any server-based targets work with Dioxus Logger.\n\nThe easiest way to use Dioxus Logger is by calling the `init()` function:\n\n````rs\nuse log::LevelFilter;\n\nfn main() {\n // Init logger\n dioxus_logger::init(LevelFilter::Info).expect(\"failed to init logger\");\n // Dioxus code\n}\n````\n\nThe `dioxus_logger::init()` function initializes Dioxus Logger with the log crate using the default configuration and provided `LevelFilter`.\n\n#### Custom Format\n\nDioxus Logger allows you more control with the ability to set a custom format using the `new` function on the `DioxusLogger` struct:\n\n````rs\nuse log::LevelFilter;\n\nfn main() {\n // Init logger\n dioxus_logger::DioxusLogger::new(LevelFilter::Info)\n .use_format(\"[{LEVEL}] {PATH} - {ARGS}\")\n .build()\n .expect(\"failed to init logger\");\n\n // Dioxus code\n}\n````\n\nIn this example, we are building a new `DioxusLogger` struct, providing the `LevelFilter`, calling the `use_format()` function, and initializing the logger with the `build()` function (acts as `init()` in the previous example).\n\nThe key function call in this example is `use_format()`. This function takes a `&str` that specifies how you want your logs to be formatted. To specify a variable in the format, you wrap it's name in `{}`.\n\nThe available variables are:\n\n* LEVEL - The `LevelFilter` of the emitted log.\n* PATH - The file path of where the log was emitted, or the crate name.\n* ARGS - The arguments passed through the log macro.\n* TIMESTAMP - A timestamp of when the log was emitted. (Requires `timestamps` feature)\n\n#### Timestamps\n\nAnother feature of Dioxus Logger is the ability to include timestamps with your logs. By default, this feature is disabled and has to be enabled by adding `timestamps` to your features section of the `dioxus-logger` dependency:\n\n````toml\ndioxus-logger = { version = \"*\", features = [\"timestamps\"] }\n````\n\nBy enabling this feature, you gain access to the `TIMESTAMP` format variable.\n\n#### Dioxus Logger Platform Intricacies\n\nOn web, Dioxus Logger will use [web-sys](https://crates.io/crates/web-sys) to interact with `console.log()` to output your logs to the browser's console. On Desktop and server-based targets, Dioxus Logger will output using `println()`.\n\n#### Final Notes\n\nDioxus Logger is the preferred logger to use with Dioxus if it suites your needs. There are many more features to come and Dioxus Logger is planned to become an integral part of Dioxus. If there are any feature suggestions or issues with Dioxus Logger, feel free to reach out on the [Dioxus Discord Server](https://discord.gg/XgGxMSkvUM)!\n\nFor more information, visit Dioxus Logger's [docs](https://docs.rs/dioxus-logger/latest/dioxus_logger/).\n\n## Desktop and Server\n\nFor Dioxus' desktop and server targets, you can generally use the logger of your choice.\n\nSome popular options are:\n\n* [env_logger](https://crates.io/crates/env_logger)\n* [simple_logger](https://crates.io/crates/simple_logger)\n* [pretty_env_logger](https://crates.io/crates/pretty_env_logger)\n\nTo keep this guide short, we will not be covering the usage of these loggers.\n\nFor a full list of popular log-based logging crates, visit [this](https://docs.rs/log/latest/log/#available-logging-implementations) list in the log crate's docs.\n\n## Web\n\n[WASM Logger](https://crates.io/crates/wasm-logger) is a logging interface that can be used with Dioxus' web platform.\n\nThe easiest way to use WASM Logger is with the `init` function:\n\n````rs\nfn main() {\n // Init logger\n wasm_logger::init(wasm_logger::Config::default());\n\n // Dioxus code\n}\n````\n\nThis starts WASM Logger with a `LevelFilter` of `Debug`.\n\nTo specify a custom `LevelFilter`, build the `Config` struct:\n\n````rs\nuse log::LevelFilter;\n\nfn main() {\n // Init logger\n let log_config = wasm_logger::Config::new(LevelFilter::Info);\n wasm_logger::init(log_config);\n\n // Dioxus code\n}\n````\n\n#### Wasm Logger Platform Intricacies\n\nWASM Logger also uses the [web-sys](https://crates.io/crates/web-sys) crate to output to the console.\n\nFor more information, visit wasm-logger's [docs](https://docs.rs/wasm-logger/latest/wasm_logger/).\n\n## Android\n\n[Android Logger](https://crates.io/crates/android_logger) is a logging interface that can be used when targeting Android. Android Logger runs whenever an event `native_activity_create` is called by the Android system:\n\n````rs\nuse log::LevelFilter;\nuse android_logger::Config;\n\nfn native_activity_create() {\n android_logger::init_once(\n Config::default()\n .with_max_level(LevelFilter::Info)\n .with_tag(\"myapp\");\n );\n}\n````\n\nThe `with_tag()` is what your app's logs will show as.\n\n#### Viewing Android Logs\n\nAndroid logs are sent to logcat. To use logcat through the Android debugger, run:\n\n````cmd\nadb -d logcat\n````\n\nYour Android device will need developer options/usb debugging enabled.\n\nFor more information, visit android_logger's [docs](https://docs.rs/android_logger/latest/android_logger/).\n\n## iOS\n\nThe current option for iOS is the [oslog](https://crates.io/crates/oslog) crate.\n\n````rs\nfn main() {\n // Init logger\n OsLogger::new(\"com.example.test\")\n .level_filter(LevelFilter::Debug)\n .init()\n .expect(\"failed to init logger\");\n // Dioxus code\n}\n````\n\n#### Viewing IOS Logs\n\nYou can view the emitted logs in Xcode.\n\nFor more information, visit [oslog](https://crates.io/crates/oslog)." + } + 75usize => { + "# Project Structure\n\nThere are many packages in the Dioxus organization. This document will help you understand the purpose of each package and how they fit together.\n\n## Renderers\n\n* [Desktop](https://github.com/DioxusLabs/dioxus/tree/master/packages/desktop): A Render that Runs Dioxus applications natively, but renders them with the system webview\n* [Mobile](https://github.com/DioxusLabs/dioxus/tree/master/packages/mobile): A Render that Runs Dioxus applications natively, but renders them with the system webview. This is currently a copy of the desktop render\n* [Web](https://github.com/DioxusLabs/dioxus/tree/master/packages/web): Renders Dioxus applications in the browser by compiling to WASM and manipulating the DOM\n* [Liveview](https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview): A Render that Runs on the server, and renders using a websocket proxy in the browser\n* [Rink](https://github.com/DioxusLabs/dioxus/tree/master/packages/rink): A Renderer that renders a HTML-like tree into a terminal\n* [TUI](https://github.com/DioxusLabs/dioxus/tree/master/packages/dioxus-tui): A Renderer that uses Rink to render a Dioxus application in a terminal\n* [Blitz-Core](https://github.com/DioxusLabs/blitz/tree/master/blitz-core): An experimental native renderer that renders a HTML-like tree using WGPU.\n* [Blitz](https://github.com/DioxusLabs/blitz): An experimental native renderer that uses Blitz-Core to render a Dioxus application using WGPU.\n* [SSR](https://github.com/DioxusLabs/dioxus/tree/master/packages/ssr): A Render that Runs Dioxus applications on the server, and renders them to HTML\n\n## State Management/Hooks\n\n* [Hooks](https://github.com/DioxusLabs/dioxus/tree/master/packages/hooks): A collection of common hooks for Dioxus applications\n* [Signals](https://github.com/DioxusLabs/dioxus/tree/master/packages/signals): A experimental state management library for Dioxus applications. This currently contains a `Copy` version of UseRef\n* [Dioxus STD](https://github.com/DioxusLabs/dioxus-std): A collection of platform agnostic hooks to interact with system interfaces (The clipboard, camera, etc.).\n* [Fermi](https://github.com/DioxusLabs/dioxus/tree/master/packages/fermi): A global state management library for Dioxus applications.\n* [Router](https://github.com/DioxusLabs/dioxus/tree/master/packages/router): A client-side router for Dioxus applications\n\n## Core utilities\n\n* [core](https://github.com/DioxusLabs/dioxus/tree/master/packages/core): The core virtual dom implementation every Dioxus application uses\n * You can read more about the architecture of the core [in this blog post](https://dioxuslabs.com/blog/templates-diffing/) and the [custom renderer section of the guide](../cookbook/custom_renderer.md)\n* [RSX](https://github.com/DioxusLabs/dioxus/tree/master/packages/rsx): The core parsing for RSX used for hot reloading, autoformatting, and the macro\n* [core-macro](https://github.com/DioxusLabs/dioxus/tree/master/packages/core-macro): The rsx! macro used to write Dioxus applications. (This is a wrapper over the RSX crate)\n* [HTML macro](https://github.com/DioxusLabs/dioxus-html-macro): A html-like alternative to the RSX macro\n\n## Native Renderer Utilities\n\n* [native-core](https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core): Incrementally computed tree of states (mostly styles)\n * You can read more about how native-core can help you build native renderers in the [custom renderer section of the guide](../cookbook/custom_renderer.md#native-core)\n* [native-core-macro](https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core-macro): A helper macro for native core\n* [Taffy](https://github.com/DioxusLabs/taffy): Layout engine powering Blitz-Core, Rink, and Bevy UI\n\n## Web renderer tooling\n\n* [HTML](https://github.com/DioxusLabs/dioxus/tree/master/packages/html): defines html specific elements, events, and attributes\n* [Interpreter](https://github.com/DioxusLabs/dioxus/tree/master/packages/interpreter): defines browser bindings used by the web and desktop renderers\n\n## Developer tooling\n\n* [hot-reload](https://github.com/DioxusLabs/dioxus/tree/master/packages/hot-reload): Macro that uses the RSX crate to hot reload static parts of any rsx! macro. This macro works with any non-web renderer with an [integration](https://crates.io/crates/dioxus-hot-reload)\n* [autofmt](https://github.com/DioxusLabs/dioxus/tree/master/packages/autofmt): Formats RSX code\n* [rsx-rosetta](https://github.com/DioxusLabs/dioxus/tree/master/packages/rsx-rosetta): Handles conversion between HTML and RSX\n* [CLI](https://github.com/DioxusLabs/dioxus/tree/master/packages/cli): A Command Line Interface and VSCode extension to assist with Dioxus usage" + } + 53usize => { + "# Routing Update Callback\n\nIn some cases, we might want to run custom code when the current route changes. For this reason, the \\[`RouterConfig`\\] exposes an `on_update` field.\n\n## How does the callback behave?\n\nThe `on_update` is called whenever the current routing information changes. It is called after the router updated its internal state, but before dependent components and hooks are updated.\n\nIf the callback returns a \\[`NavigationTarget`\\], the router will replace the current location with the specified target. It will not call the `on_update` again.\n\nIf at any point the router encounters a navigation failure, it will go to the appropriate state without calling the `on_update`. It doesn't matter if the invalid target initiated the navigation, was found as a redirect target, or was returned by the `on_update` itself.\n\n## Code Example\n\n````rust@routing_update.rs\n#[derive(Routable, Clone, PartialEq)]\nenum Route {\n #[route(\"/\")]\n Index {},\n #[route(\"/home\")]\n Home {},\n}\n\n#[component]\nfn Home(cx: Scope) -> Element {\n render! {\n p { \"Home\" }\n }\n}\n\n#[component]\nfn Index(cx: Scope) -> Element {\n render! {\n p { \"Index\" }\n }\n}\n\nfn app(cx: Scope) -> Element {\n render! {\n Router:: {\n config: || RouterConfig::default().on_update(|state|{\n (state.current() == Route::Index {}).then_some(\n NavigationTarget::Internal(Route::Home {})\n )\n })\n }\n }\n}\n````" + } + 38usize => { + "# Overview\n\nIn this guide, you'll learn to effectively use Dioxus Router whether you're\nbuilding a small todo app or the next FAANG company. We will create a small\nwebsite with a blog, homepage, and more!\n\n > \n > To follow along with the router example, you'll need a working Dioxus app.\n > Check out the [Dioxus book](https://dioxuslabs.com/learn/0.4/getting_started) to get started.\n\n > \n > Make sure to add Dioxus Router as a dependency, as explained in the\n > [introduction](../index.md).\n\n## You'll learn how to\n\n* Create routes and render \"pages\".\n* Utilize nested routes, create a navigation bar, and render content for a\n set of routes.\n* Parse URL parameters to dynamically display content.\n* Redirect visitors to different routes.\n\n > \n > **Disclaimer**\n > \n > The example will only display the features of Dioxus Router. It will not\n > include any actual functionality. To keep things simple we will only be using\n > a single file, this is not the recommended way of doing things with a real\n > application.\n\nYou can find the complete application in the [full code](full-code.md) chapter." + } + 73usize => { + "# Translating existing HTML\n\nDioxus uses a custom format called RSX to represent the HTML because it is more concise and looks more like Rust code. However, it can be a pain to convert existing HTML to RSX. That's why Dioxus comes with a tool called `dx translate` that can automatically convert HTML to RSX!\n\nDx translate can make converting large chunks of HTML to RSX much easier! Lets try translating some of the HTML from the Dioxus homepage:\n\n````sh\ndx translate --raw \"
\"\n````\n\nWe get the following RSX you can easily copy and paste into your code:\n\n````rs\ndiv { class: \"relative w-full mx-4 sm:mx-auto text-gray-600\",\n div { class: \"text-[3em] md:text-[5em] font-semibold dark:text-white text-ghdarkmetal font-sans py-12 flex flex-col\",\n span { \"Fullstack, crossplatform,\" }\n span { \"lightning fast, fully typed.\" }\n }\n h3 { class: \"text-[2em] dark:text-white font-extralight text-ghdarkmetal pt-4 max-w-screen-md mx-auto\",\n \"Dioxus is a Rust library for building apps that run on desktop, web, mobile, and more.\"\n }\n div { class: \"pt-12 text-white text-[1.2em] font-sans font-bold flex flex-row justify-center space-x-4\",\n a {\n href: \"/learn/0.4/getting_started\",\n data_dioxus_id: \"216\",\n dioxus_prevent_default: \"onclick\",\n class: \"bg-red-600 py-2 px-8 hover:-translate-y-2 transition-transform duration-300\",\n \"Quickstart\"\n }\n a {\n dioxus_prevent_default: \"onclick\",\n href: \"/learn/0.4/reference\",\n data_dioxus_id: \"214\",\n class: \"bg-blue-500 py-2 px-8 hover:-translate-y-2 transition-transform duration-300\",\n \"Read the docs\"\n }\n }\n div { class: \"max-w-screen-2xl mx-auto pt-36\",\n h1 { class: \"text-md\", \"Trusted by top companies\" }\n div { class: \"pt-4 flex flex-row flex-wrap justify-center\",\n div { class: \"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\",\n img { src: \"static/futurewei_bw.png\" }\n }\n div { class: \"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\",\n img { src: \"static/airbuslogo.svg\" }\n }\n div { class: \"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\",\n img { src: \"static/ESA_logo.svg\" }\n }\n div { class: \"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\",\n img { src: \"static/yclogo.svg\" }\n }\n div { class: \"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\",\n img { src: \"static/satellite.webp\" }\n }\n }\n }\n}\n````\n\n## Usage\n\nThe `dx translate` command has several flags you can use to control your html input and rsx output.\n\nYou can use the `--file` flag to translate an HTML file to RSX:\n\n````sh\ndx translate --file index.html\n````\n\nOr you can use the `--raw` flag to translate a string of HTML to RSX:\n\n````sh\ndx translate --raw \"
Hello world
\"\n````\n\nBoth of those commands will output the following RSX:\n\n````rs\ndiv { \"Hello world\" }\n````\n\nThe `dx translate` command will output the RSX to stdout. You can use the `--output` flag to write the RSX to a file instead.\n\n````sh\ndx translate --raw \"
Hello world
\" --output index.rs\n````\n\nYou can automatically create a component with the `--component` flag.\n\n````sh\ndx translate --raw \"
Hello world
\" --component\n````\n\nThis will output the following component:\n\n````rs\nfn component(cx: Scope) -> Element {\n cx.render(rsx! {\n div { \"Hello world\" }\n })\n}\n````\n\nTo learn more about the different flags `dx translate` supports, run `dx translate --help`." + } + 34usize => { + "# Middleware\n\nExtractors allow you to wrap your server function in some code that changes either the request or the response. Dioxus fullstack integrates with [Tower](https://docs.rs/tower/latest/tower/index.html) to allow you to wrap your server functions in middleware.\n\nYou can use the `#[middleware]` attribute to add a layer of middleware to your server function. Let's add a timeout middleware to a server function. This middleware will stop running the server function if it reaches a certain timeout:\n\n````rust@server_function_middleware.rs\n#[server]\n// Add a timeout middleware to the server function that will return an error if the function takes longer than 1 second to execute\n#[middleware(tower_http::timeout::TimeoutLayer::new(std::time::Duration::from_secs(1)))]\npub async fn timeout() -> Result<(), ServerFnError> {\n tokio::time::sleep(std::time::Duration::from_secs(2)).await;\n Ok(())\n}\n````" + } + 69usize => { + "# Introduction\n\nThe ✨**Dioxus CLI**✨ is a tool to get Dioxus projects off the ground.\n\nThere's no documentation for commands here, but you can see all commands using `dx --help` once you've installed the CLI! Furthermore, you can run `dx --help` to get help with a specific command.\n\n## Features\n\n* Build and pack a Dioxus project.\n* Format `rsx` code.\n* Hot Reload for `web` platform.\n* Create a Dioxus project from a template repository.\n* And more!" + } + 16usize => { + "# Components\n\nJust 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, you should break down the functionality of an app in logical parts called components.\n\nA component is a Rust function, named in UpperCamelCase, that takes a `Scope` parameter and returns an `Element` describing the UI it wants to render. In fact, our `App` function is a component!\n\n````rust, no_run@hello_world_desktop.rs\n// define a component that renders a div with the text \"Hello, world!\"\nfn App(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n \"Hello, world!\"\n }\n })\n}\n````\n\n > \n > You'll probably want to add `#![allow(non_snake_case)]` to the top of your crate to avoid warnings about UpperCamelCase component names\n\nA 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:\n\n````rust, no_run@components.rs\npub fn About(cx: Scope) -> Element {\n cx.render(rsx!(p {\n b {\"Dioxus Labs\"}\n \" An Open Source project dedicated to making Rust UI wonderful.\"\n }))\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\tcomponents::About {}\n}\n````\n\nThen, you can render your component in another component, similarly to how elements are rendered:\n\n````rust, no_run@components.rs\npub fn App(cx: Scope) -> Element {\n cx.render(rsx! {\n About {},\n About {},\n })\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\tcomponents::App {}\n}\n````\n\n > \n > 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!" + } + 71usize => { + "# Create a Project\n\nOnce you have the Dioxus CLI installed, you can use it to create your own project!\n\n## Initializing a project\n\nFirst, run the `dx create` command to create a new project.\n\n > \n > It clones this [template](https://github.com/DioxusLabs/dioxus-template), which is used for web apps.\n > \n > You can create your project from a different template by passing the `template` argument:\n > \n > ````\n > dx create --template gh:dioxuslabs/dioxus-template\n > ````\n\nNext, navigate into your new project using `cd project-name`, or simply opening it in an IDE.\n\n > \n > Make sure the WASM target is installed before running the projects.\n > You can install the WASM target for rust using rustup:\n > \n > ````\n > rustup target add wasm32-unknown-unknown\n > ````\n\nFinally, serve your project with `dx serve`! The CLI will tell you the address it is serving on, along with additional info such as code warnings." + } + 40usize => { + "# Building a Nest\n\nIn this chapter, we will begin to build the blog portion of our site which will\ninclude links, nested routes, and route parameters.\n\n## Site Navigation\n\nOur site visitors won't know all the available pages and blogs on our site so we\nshould provide a navigation bar for them. Our navbar will be a list of links going between our pages.\n\nWe want our navbar component to be rendered on several different pages on our site. Instead of duplicating the code, we can create a component that wraps all children routes. This is called a layout component. To tell the router where to render the child routes, we use the \\[`Outlet`\\] component.\n\nLet's create a new `NavBar` component:\n\n````rust@nested_routes.rs\n#[component]\nfn NavBar(cx: Scope) -> Element {\n render! {\n nav {\n ul {\n li { \"links\" }\n }\n }\n // The Outlet component will render child routes (In this case just the Home component) inside the Outlet component\n Outlet:: {}\n }\n}\n````\n\nNext, let's add our `NavBar` component as a layout to our Route enum:\n\n````rust@nested_routes.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // All routes under the NavBar layout will be rendered inside of the NavBar Outlet\n #[layout(NavBar)]\n #[route(\"/\")]\n Home {},\n #[end_layout]\n #[route(\"/:..route\")]\n PageNotFound { route: Vec },\n}\n````\n\nTo add links to our `NavBar`, we could always use an HTML anchor element but that has two issues:\n\n1. It causes a full-page reload\n1. We can accidentally link to a page that doesn't exist\n\nInstead, we want to use the [`Link`] component provided by Dioxus Router.\n\nThe [`Link`] is similar to a regular `` tag. It takes a target and children.\n\nUnlike a regular `` tag, we can pass in our Route enum as the target. Because we annotated our routes with the \\[`route(path)`\\] attribute, the [`Link`] will know how to generate the correct URL. If we use the Route enum, the rust compiler will prevent us from linking to a page that doesn't exist.\n\nLet's add our links:\n\n````rust@links.rs\n#[component]\nfn NavBar(cx: Scope) -> Element {\n render! {\n nav {\n ul {\n li {\n Link {\n // The Link component will navigate to the route specified\n // in the target prop which is checked to exist at compile time\n to: Route::Home {},\n \"Home\"\n }\n }\n }\n }\n Outlet:: {}\n }\n}\n````\n\n > \n > Using this method, the [`Link`] component only works for links within our\n > application. To learn more about navigation targets see\n > [here](./navigation-targets.md).\n\nNow you should see a list of links near the top of your page. Click on one and\nyou should seamlessly travel between pages.\n\n## URL Parameters and Nested Routes\n\nMany websites such as GitHub put parameters in their URL. For example,\n`https://github.com/DioxusLabs` utilizes the text after the domain to\ndynamically search and display content about an organization.\n\nWe want to store our blogs in a database and load them as needed. We also\nwant our users to be able to send people a link to a specific blog post.\nInstead of listing all of the blog titles at compile time, we can make a dynamic route.\n\nWe could utilize a search page that loads a blog when clicked but then our users\nwon't be able to share our blogs easily. This is where URL parameters come in.\n\nThe path to our blog will look like `/blog/myBlogPage`, `myBlogPage` being the\nURL parameter.\n\nFirst, let's create a layout component (similar to the navbar) that wraps the blog content. This allows us to add a heading that tells the user they are on the blog.\n\n````rust@dynamic_route.rs\n#[component]\nfn Blog(cx: Scope) -> Element {\n render! {\n h1 { \"Blog\" }\n Outlet:: {}\n }\n}\n````\n\nNow we'll create another index component, that'll be displayed when no blog post\nis selected:\n\n````rust@dynamic_route.rs\n#[component]\nfn BlogList(cx: Scope) -> Element {\n render! {\n h2 { \"Choose a post\" }\n ul {\n li {\n Link {\n to: Route::BlogPost { name: \"Blog post 1\".into() },\n \"Read the first blog post\"\n }\n }\n li {\n Link {\n to: Route::BlogPost { name: \"Blog post 2\".into() },\n \"Read the second blog post\"\n }\n }\n }\n }\n}\n````\n\nWe also need to create a component that displays an actual blog post. This component will accept the URL parameters as props:\n\n````rust@dynamic_route.rs\n// The name prop comes from the /:name route segment\n#[component]\nfn BlogPost(cx: Scope, name: String) -> Element {\n render! {\n h2 { \"Blog Post: {name}\"}\n }\n}\n````\n\nFinally, let's tell our router about those components:\n\n````rust@dynamic_route.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(NavBar)]\n #[route(\"/\")]\n Home {},\n #[nest(\"/blog\")]\n #[layout(Blog)]\n #[route(\"/\")]\n BlogList {},\n #[route(\"/post/:name\")]\n BlogPost { name: String },\n #[end_layout]\n #[end_nest]\n #[end_layout]\n #[route(\"/:..route\")]\n PageNotFound {\n route: Vec,\n },\n}\n````\n\nThat's it! If you head to `/blog/1` you should see our sample post.\n\n## Conclusion\n\nIn this chapter, we utilized Dioxus Router's Link, and Route Parameter\nfunctionality to build the blog portion of our application. In the next chapter,\nwe will go over how navigation targets (like the one we passed to our links)\nwork." + } + 0usize => { + "# Introduction\n\nDioxus 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.\n\n````rust@readme.rs\nuse dioxus::prelude::*;\n\npub fn App(cx: Scope) -> Element {\n let mut count = use_state(cx, || 0);\n\n cx.render(rsx! {\n h1 { \"High-Five counter: {count}\" }\n button { onclick: move |_| count += 1, \"Up high!\" }\n button { onclick: move |_| count -= 1, \"Down low!\" }\n })\n}\n\n````\n\n````inject-dioxus\nDemoFrame {\n readme::App {}\n}\n````\n\nDioxus is heavily inspired by React. If you know React, getting started with Dioxus will be a breeze.\n\n > \n > 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.\n\n## Features\n\n* Desktop apps running natively (no Electron!) in less than 10 lines of code.\n* Incredibly ergonomic and powerful state management.\n* Comprehensive inline documentation – hover and guides for all HTML elements, listeners, and events.\n* Extremely memory efficient – 0 global allocations for steady-state components.\n* Multi-channel asynchronous scheduler for first-class async support.\n* And more! Read the [full release post](https://dioxuslabs.com/blog/introducing-dioxus/).\n\n### Multiplatform\n\nDioxus 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.\n\nRight now, we have several 1st-party renderers:\n\n* WebSys (for WASM): Great support\n* Tao/Tokio (for Desktop apps): Good support\n* Tao/Tokio (for Mobile apps): Poor support\n* SSR (for generating static markup)\n* TUI/Rink (for terminal-based apps): Experimental\n\n## Stability\n\nDioxus has not reached a stable release yet.\n\nWeb: Since the web is a fairly mature platform, we expect there to be very little API churn for web-based features.\n\nDesktop: APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart.\n\nSSR: We don't expect the SSR API to change drastically in the future." + } + 47usize => { + "# Layouts\n\nLayouts allow you to wrap all child routes in a component. This can be useful when creating something like a header that will be used in many different routes.\n\n\\[`Outlet`\\] tells the router where to render content in layouts. In the following example,\nthe Index will be rendered within the \\[`Outlet`\\].\n\nThis page is built with the Dioxus. It uses Layouts in several different places. Here is an outline of how layouts are used on the current page. Hover over different layouts to see what elements they are on the page.\n\n````inject-dioxus\nLayoutsExplanation {}\n````\n\nHere is a more complete example of a layout wrapping the body of a page.\n\n````rust@outlet.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(Wrapper)]\n #[route(\"/\")]\n Index {},\n}\n\n#[component]\nfn Wrapper(cx: Scope) -> Element {\n render! {\n header { \"header\" }\n // The index route will be rendered here\n Outlet:: { }\n footer { \"footer\" }\n }\n}\n\n#[component]\nfn Index(cx: Scope) -> Element {\n render! {\n h1 { \"Index\" }\n }\n}\n````\n\nThe example above will output the following HTML (line breaks added for\nreadability):\n\n````html\n
header
\n

Index

\n
footer
\n````\n\n## Layouts with dynamic segments\n\nYou can combine layouts with [nested routes](./routes/nested.md) to create dynamic layouts with content that changes based on the current route.\n\nJust like routes, layouts components must accept a prop for each dynamic segment in the route. For example, if you have a route with a dynamic segment like `/:name`, your layout component must accept a `name` prop:\n\n````rust@outlet.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[nest(\"/:name\")]\n #[layout(Wrapper)]\n #[route(\"/\")]\n Index {\n name: String,\n },\n}\n\n#[component]\nfn Wrapper(cx: Scope, name: String) -> Element {\n render! {\n header { \"Welcome {name}!\" }\n // The index route will be rendered here\n Outlet:: { }\n footer { \"footer\" }\n }\n}\n\n#[component]\nfn Index(cx: Scope, name: String) -> Element {\n render! {\n h1 { \"This is a homepage for {name}\" }\n }\n}\n````\n\nOr to get the full route, you can use the [`use_route`](https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_route.html) hook.\n\n````rust@outlet.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(Wrapper)]\n #[route(\"/:name\")]\n Index {\n name: String,\n },\n}\n\n#[component]\nfn Wrapper(cx: Scope) -> Element {\n let full_route = use_route::(cx).unwrap();\n render! {\n header { \"Welcome to {full_route}!\" }\n // The index route will be rendered here\n Outlet:: { }\n footer { \"footer\" }\n }\n}\n\n#[component]\nfn Index(cx: Scope, name: String) -> Element {\n render! {\n h1 { \"This is a homepage for {name}\" }\n }\n}\n````" + } + 48usize => { + "# Links & Navigation\n\nWhen we split our app into pages, we need to provide our users with a way to\nnavigate between them. On regular web pages, we'd use an anchor element for that,\nlike this:\n\n````html\n
Link to an other page\n````\n\nHowever, we cannot do that when using the router for three reasons:\n\n1. Anchor tags make the browser load a new page from the server. This takes a\n lot of time, and it is much faster to let the router handle the navigation\n client-side.\n1. Navigation using anchor tags only works when the app is running inside a\n browser. This means we cannot use them inside apps using Dioxus Desktop.\n1. Anchor tags cannot check if the target page exists. This means we cannot\n prevent accidentally linking to non-existent pages.\n\nTo solve these problems, the router provides us with a \\[`Link`\\] component we can\nuse like this:\n\n````rust@links.rs\n#[component]\nfn NavBar(cx: Scope) -> Element {\n render! {\n nav {\n ul {\n li {\n Link {\n // The Link component will navigate to the route specified\n // in the target prop which is checked to exist at compile time\n to: Route::Home {},\n \"Home\"\n }\n }\n }\n }\n Outlet:: {}\n }\n}\n````\n\nThe `target` in the example above is similar to the `href` of a regular anchor\nelement. However, it tells the router more about what kind of navigation it\nshould perform. It accepts something that can be converted into a\n\\[`NavigationTarget`\\]:\n\n* The example uses a Internal route. This is the most common type of navigation.\n It tells the router to navigate to a page within our app by passing a variant of a \\[`Routable`\\] enum. This type of navigation can never fail if the link component is used inside a router component.\n* \\[`External`\\] allows us to navigate to URLs outside of our app. This is useful\n for links to external websites. NavigationTarget::External accepts an URL to navigate to. This type of navigation can fail if the URL is invalid.\n\n > \n > The \\[`Link`\\] accepts several props that modify its behavior. See the API docs\n > for more details." + } + 54usize => { + "# Cookbook\n\nThe cookbook contains common recipes for different patterns within Dioxus.\n\nThere are a few different sections in the cookbook:\n\n* [Publishing](publishing.md) will teach you how to present your app in a variety of delicious forms.\n* Explore the [Anti-patterns](antipatterns.md) section to discover what ingredients to avoid when preparing your application.\n* Within [Error Handling](error_handling.md), we'll master the fine art of managing spoiled ingredients in Dioxus.\n* Take a culinary journey through [State management](state/index.md), where we'll explore the world of handling local, global, and external state in Dioxus.\n* [Integrations](integrations/index.md) will guide you how to seamlessly blend external libraries into your Dioxus culinary creations.\n* [Testing](testing.md) explains how to examine the unique flavor of Dioxus-specific features, like components.\n* [Examples](examples.md) is a curated list of delightful recipes that demonstrate the various ways of using Dioxus ingredients.\n* [Tailwind](tailwind.md) reveals the secrets of combining your Tailwind and Dioxus ingredients into a complete meal. You will also learn about using other NPM ingredients (packages) with Dioxus.\n* In the [Custom Renderer](custom_renderer.md) section, we embark on a cooking adventure, inventing new ways to cook with Dioxus!\n* [Optimizing](optimizing.md) will show you how to maximize the quality of your ingredients." + } + 15usize => { + "# Describing the UI\n\nDioxus is a *declarative* framework. This means that instead of telling Dioxus what to do (e.g. to \"create an element\" or \"set the color to red\") we simply *declare* what we want the UI to look like using RSX.\n\nYou have already seen a simple example of RSX syntax in the \"hello world\" application:\n\n````rust, no_run@hello_world_desktop.rs\n// define a component that renders a div with the text \"Hello, world!\"\nfn App(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n \"Hello, world!\"\n }\n })\n}\n````\n\nHere, 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.\n\n## RSX Features\n\nRSX 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:\n\n````rust, no_run@rsx_overview.rs\ncx.render(rsx!(div {\n // attributes / listeners\n // children\n}))\n````\n\n````inject-dioxus\nDemoFrame {\n\t// old: rsx_overview::Empty {}\n\t__interactive_04::Empty {}\n}\n````\n\n### Attributes\n\nAttributes (and [event handlers](event_handlers.md)) modify the behavior or appearance of the element they are attached to. They are specified inside the `{}` brackets, using the `name: value` syntax. You can provide the value as a literal in the RSX:\n\n````rust, no_run@rsx_overview.rs\ncx.render(rsx!(img {\n src: \"https://avatars.githubusercontent.com/u/79236386?s=200&v=4\",\n class: \"primary_button\",\n width: \"10px\"\n}))\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Attributes {}\n}\n````\n\n > \n > Note: All attributes defined in `dioxus-html` follow the snake_case naming convention. They transform their `snake_case` names to HTML's `camelCase` attributes.\n\n > \n > Note: Styles can be used directly outside of the `style:` attribute. In the above example, `color: \"red\"` is turned into `style=\"color: red\"`.\n\n#### Custom Attributes\n\nDioxus 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:\n\n````rust, no_run@rsx_overview.rs\ncx.render(rsx!(div {\n \"style\": \"width: 20px; height: 20px; background-color: red;\",\n}))\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::CustomAttributes {}\n}\n````\n\n### Special Attributes\n\nWhile most attributes are simply passed on to the HTML, some have special behaviors.\n\n#### The HTML Escape Hatch\n\nIf 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`.\n\nFor 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):\n\n````rust, no_run@dangerous_inner_html.rs\n// this should come from a trusted source\nlet contents = \"live dangerously\";\n\ncx.render(rsx! {\n div {\n dangerous_inner_html: \"{contents}\",\n }\n})\n````\n\n````inject-dioxus\nDemoFrame {\n\tdangerous_inner_html::App {}\n}\n````\n\n > \n > 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.\n > \n > 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.\n\n#### Boolean Attributes\n\nMost 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.\n\nSo this RSX wouldn't actually render the `hidden` attribute:\n\n````rust, no_run@boolean_attribute.rs\ncx.render(rsx! {\n div {\n hidden: false,\n \"hello\"\n }\n})\n````\n\n````inject-dioxus\nDemoFrame {\n\tboolean_attribute::App {}\n}\n````\n\nNot all attributes work like this however. *Only the following attributes* have this behavior:\n\n* `allowfullscreen`\n* `allowpaymentrequest`\n* `async`\n* `autofocus`\n* `autoplay`\n* `checked`\n* `controls`\n* `default`\n* `defer`\n* `disabled`\n* `formnovalidate`\n* `hidden`\n* `ismap`\n* `itemscope`\n* `loop`\n* `multiple`\n* `muted`\n* `nomodule`\n* `novalidate`\n* `open`\n* `playsinline`\n* `readonly`\n* `required`\n* `reversed`\n* `selected`\n* `truespeed`\n\nFor any other attributes, a value of `\"false\"` will be sent directly to the DOM.\n\n### Interpolation\n\nSimilarly 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:\n\n````rust, no_run@rsx_overview.rs\nlet coordinates = (42, 0);\nlet country = \"es\";\ncx.render(rsx!(div {\n class: \"country-{country}\",\n left: \"{coordinates.0:?}\",\n top: \"{coordinates.1:?}\",\n // arbitrary expressions are allowed,\n // as long as they don't contain `{}`\n div {\n \"{country.to_uppercase()}\"\n },\n div {\n \"{7*6}\"\n },\n // {} can be escaped with {{}}\n div {\n \"{{}}\"\n },\n}))\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Formatting {}\n}\n````\n\n### Children\n\nTo add children to an element, put them inside the `{}` brackets after all attributes and listeners in the element. They can be other elements, text, or [components](components.md). For example, you could have an `ol` (ordered list) element, containing 3 `li` (list item) elements, each of which contains some text:\n\n````rust, no_run@rsx_overview.rs\ncx.render(rsx!(ol {\n li {\"First Item\"}\n li {\"Second Item\"}\n li {\"Third Item\"}\n}))\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Children {}\n}\n````\n\n### Fragments\n\nYou can render multiple elements at the top level of `rsx!` and they will be automatically grouped.\n\n````rust, no_run@rsx_overview.rs\ncx.render(rsx!(\n p {\"First Item\"},\n p {\"Second Item\"},\n))\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::ManyRoots {}\n}\n````\n\n### Expressions\n\nYou can include arbitrary Rust expressions as children within RSX that implements [IntoDynNode](https://docs.rs/dioxus-core/0.3/dioxus_core/trait.IntoDynNode.html). This is useful for displaying data from an [iterator](https://doc.rust-lang.org/stable/book/ch13-02-iterators.html#processing-a-series-of-items-with-iterators):\n\n````rust, no_run@rsx_overview.rs\nlet text = \"Dioxus\";\ncx.render(rsx!(span {\n text.to_uppercase(),\n // create a list of text from 0 to 9\n (0..10).map(|i| rsx!{ i.to_string() })\n}))\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Expression {}\n}\n````\n\n### Loops\n\nIn addition to iterators you can also use for loops directly within RSX:\n\n````rust, no_run@rsx_overview.rs\ncx.render(rsx! {\n // use a for loop where the body itself is RSX\n div {\n // create a list of text from 0 to 9\n for i in 0..3 {\n // NOTE: the body of the loop is RSX not a rust statement\n div {\n \"{i}\"\n }\n }\n }\n // iterator equivalent\n div {\n (0..3).map(|i| rsx!{ div { \"{i}\" } })\n }\n})\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Loops {}\n}\n````\n\n### If statements\n\nYou can also use if statements without an else branch within RSX:\n\n````rust, no_run@rsx_overview.rs\ncx.render(rsx! {\n // use if statements without an else\n if true {\n rsx!(div { \"true\" })\n }\n})\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::IfStatements {}\n}\n````" + } + 1usize => { + "# Getting Started\n\nThis section will help you set up your Dioxus project!\n\n## Prerequisites\n\n### An Editor\n\nDioxus 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.\n\n### Rust\n\nHead over to [https://rust-lang.org](http://rust-lang.org) and install the Rust compiler.\n\nWe strongly recommend going through the [official Rust book](https://doc.rust-lang.org/book/ch01-00-getting-started.html) *completely*. However, we hope that a Dioxus app can serve as a great first Rust project. With Dioxus, you'll learn about:\n\n* Error handling\n* Structs, Functions, Enums\n* Closures\n* Macros\n\nWe've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need deep knowledge of async, lifetimes, or smart pointers until you start building complex Dioxus apps.\n\n## Setup Guides\n\nDioxus supports multiple platforms. Choose the platform you want to target below to get platform-specific setup instructions:\n\n* [Choosing a Web Renderer](choosing_a_web_renderer.md)\n* [Client Side](wasm.md): runs in the browser through WebAssembly\n* [Liveview](liveview.md): runs on the server, renders in the browser using WebSockets\n* [Fullstack](fullstack.md): renders to HTML text on the server and hydrates it on the client\n* [Desktop](desktop.md): runs in a web view on desktop\n* [Mobile](mobile.md): runs in a web view on mobile\n* [Terminal UI](tui.md): renders text-based graphics in the terminal\n\n > \n > More information on any platform you choose is available in the section of the same name in the [Reference](../reference/index.md)" + } + 72usize => { + "# Configure Project\n\nThis chapter will teach you how to configure the CLI with the `Dioxus.toml` file. There's an [example](#config-example) which has comments to describe individual keys. You can copy that or view this documentation for a more complete learning experience.\n\n\"🔒\" indicates a mandatory item. Some headers are mandatory, but none of the keys inside them are. In that case, you only need to include the header, but no keys. It might look weird, but it's normal.\n\n## Structure\n\nEach header has its TOML form directly under it.\n\n### Application 🔒\n\n````toml\n[application]\n````\n\nApplication-wide configuration. Applies to both web and desktop.\n\n* **name** 🔒 - Project name & title.\n ````toml\n name = \"my_project\"\n ````\n\n* **default_platform** 🔒 - The platform this project targets\n ````toml\n # Currently supported platforms: web, desktop\n default_platform = \"web\"\n ````\n\n* **out_dir** - The directory to place the build artifacts from `dx build` or `dx serve` into. This is also where the `assets` directory will be copied into.\n ````toml\n out_dir = \"dist\"\n ````\n\n* **asset_dir** - The directory with your static assets. The CLI will automatically copy these assets into the **out_dir** after a build/serve.\n ````toml\n asset_dir = \"public\"\n ````\n\n* **sub_package** - The sub package in the workspace to build by default.\n ````toml\n sub_package = \"my-crate\"\n ````\n\n### Web.App 🔒\n\n````toml\n[web.app]\n````\n\nWeb-specific configuration.\n\n* **title** - The title of the web page.\n ````toml\n # HTML title tag content\n title = \"project_name\"\n ````\n\n* **base_path** - The base path to build the application for serving at. This can be useful when serving your application in a subdirectory under a domain. For example, when building a site to be served on GitHub Pages.\n ````toml\n # The application will be served at domain.com/my_application/, so we need to modify the base_path to the path where the application will be served\n base_path = \"my_application\"\n ````\n\n### Web.Watcher 🔒\n\n````toml\n[web.watcher]\n````\n\nDevelopment server configuration.\n\n* **reload_html** - If this is true, the cli will rebuild the index.html file every time the application is rebuilt\n \n ````toml\n reload_html = true\n ````\n\n* **watch_path** - The files & directories to monitor for changes\n \n ````toml\n watch_path = [\"src\", \"public\"]\n ````\n\n* **index_on_404** - If enabled, Dioxus will serve the root page when a route is not found.\n *This is needed when serving an application that uses the router*. However, when serving your app using something else than Dioxus (e.g. GitHub Pages), you will have to check how to configure it on that platform. In GitHub Pages, you can make a copy of `index.html` named `404.html` in the same directory.\n \n ````toml\n index_on_404 = true\n ````\n\n### Web.Resource 🔒\n\n````toml\n[web.resource]\n````\n\nStatic resource configuration.\n\n* **style** - CSS files to include in your application.\n \n ````toml\n style = [\n # Include from public_dir.\n \"./assets/style.css\",\n # Or some asset from online cdn.\n \"https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css\"\n ]\n ````\n\n* **script** - JavaScript files to include in your application.\n \n ````toml\n script = [\n # Include from asset_dir.\n \"./public/index.js\",\n # Or from an online CDN.\n \"https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js\"\n ]\n ````\n\n### Web.Resource.Dev 🔒\n\n````toml\n[web.resource.dev]\n````\n\nThis is the same as [`[web.resource]`](#webresource-), but it only works in development servers. For example, if you want to include a file in a `dx serve` server, but not a `dx serve --release` server, put it here.\n\n### Web.Proxy\n\n````toml\n[[web.proxy]]\n````\n\nConfiguration related to any proxies your application requires during development. Proxies will forward requests to a new service.\n\n* **backend** - The URL to the server to proxy. The CLI will forward any requests under the backend relative route to the backend instead of returning 404\n ````toml\n backend = \"http://localhost:8000/api/\"\n ````\n \n This will cause any requests made to the dev server with prefix /api/ to be redirected to the backend server at http://localhost:8000. The path and query parameters will be passed on as-is (path rewriting is currently not supported).\n\n## Config example\n\nThis includes all fields, mandatory or not.\n\n````toml\n[application]\n\n# App name\nname = \"project_name\"\n\n# The Dioxus platform to default to\ndefault_platform = \"web\"\n\n# `build` & `serve` output path\nout_dir = \"dist\"\n\n# The static resource path\nasset_dir = \"public\"\n\n[web.app]\n\n# HTML title tag content\ntitle = \"project_name\"\n\n[web.watcher]\n\n# When watcher is triggered, regenerate the `index.html`\nreload_html = true\n\n# Which files or dirs will be monitored\nwatch_path = [\"src\", \"public\"]\n\n# Include style or script assets\n[web.resource]\n\n# CSS style file\nstyle = []\n\n# Javascript code file\nscript = []\n\n[web.resource.dev]\n\n# Same as [web.resource], but for development servers\n\n# CSS style file\nstyle = []\n\n# JavaScript files\nscript = []\n\n[[web.proxy]]\nbackend = \"http://localhost:8000/api/\"\n````\n\n## Desktop and TUI\n\nFor the desktop and TUI (terminal user interface) renderers,\nDioxus bundles your app using `dx bundle` and the [tauri-bundler](https://docs.rs/crate/tauri-bundler/latest).\n\nYou can check out the [tauri-bundler docs.rs](https://docs.rs/tauri-bundler/latest/tauri_bundler/bundle/index.html).\nThis covers all the different settings. Keep in mind that `FooSettings` becomes just `foo` in the TOML.\n\nTauri uses a JSON file for the configuration, but Dioxus uses TOML. So this tauri-bundler example:\n\n````json\n{\n \"package\": {\n \"productName\": \"Your Awesome App\",\n \"version\": \"0.1.0\"\n },\n \"tauri\": {\n \"bundle\": {\n \"active\": true,\n \"identifier\": \"com.my.app\",\n \"shortDescription\": \"\",\n \"longDescription\": \"\",\n \"copyright\": \"Copyright (c) You 2021. All rights reserved.\",\n \"icon\": [\n \"icons/32x32.png\",\n \"icons/128x128.png\",\n \"icons/128x128@2x.png\",\n \"icons/icon.icns\",\n \"icons/icon.ico\"\n ],\n \"resources\": [\"./assets/**/*.png\"],\n \"deb\": {\n \"depends\": [\"debian-dependency1\", \"debian-dependency2\"]\n },\n \"macOS\": {\n \"frameworks\": [],\n \"minimumSystemVersion\": \"10.11\",\n \"license\": \"./LICENSE\"\n },\n \"externalBin\": [\"./sidecar-app\"]\n }\n }\n}\n````\n\nneeds to be translated to TOML.\n\nHowever, Dioxus has Dioxus-specific mandatory TOML fields that we need to include as well.\nWe can see what fields are mandatory from the documentation above.\n\nAdditionally, we also need to remove `tauri` from the TOML headers.\n\nThis is our final `Dioxus.toml`:\n\n````toml\n# From Dioxus\n[application]\nname = \"Your Awesome App\"\ndefault_platform = \"desktop\"\n\n# You might only be running on desktop, but the following \"web\" values are still required.\n[web.app]\ntitle = \"Awesome\"\n\n[web.watcher]\n\n[web.resource.dev]\n\n# From the tauri-bundler.\n[package]\nproductName = \"Your Awesome App\"\nversion = \"0.1.0\"\n\n[bundle]\nactive = true\nidentifier = \"com.my.app\"\nshortDescription = \"\"\nlongDescription = \"\"\ncopyright = \"Copyright (c) You 2021. All rights reserved.\"\nicon = [\n \"icons/32x32.png\",\n \"icons/128x128.png\",\n \"icons/128x128@2x.png\",\n \"icons/icon.icns\",\n \"icons/icon.ico\"\n]\nresources = [ \"./assets/**/*.png\" ]\nexternalBin = [ \"./sidecar-app\" ]\n\n[bundle.deb]\ndepends = [ \"debian-dependency1\", \"debian-dependency2\" ]\n\n[bundle.macOS]\nframeworks = [ ]\nminimumSystemVersion = \"10.11\"\nlicense = \"./LICENSE\"\n````" + } + 32usize => { + "# Communicating with the server\n\n`dioxus-fullstack` provides server functions that allow you to call an automatically generated API on the server from the client as if it were a local function.\n\nTo make a server function, simply add the `#[server(YourUniqueType)]` attribute to a function. The function must:\n\n* Be an async function\n* Have arguments and a return type that both implement serialize and deserialize (with [serde](https://serde.rs/)).\n* Return a `Result` with an error type of ServerFnError\n\nYou must call `register` on the type you passed into the server macro in your main function before starting your server to tell Dioxus about the server function.\n\nLet's continue building on the app we made in the [getting started](../../getting_started/fullstack.md) guide. We will add a server function to our app that allows us to double the count on the server.\n\nFirst, add serde as a dependency:\n\n````shell\ncargo add serde\n````\n\nNext, add the server function to your `main.rs`:\n\n````rust@server_function.rs\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\nuse dioxus_fullstack::prelude::*;\n\nfn main() {\n let config = LaunchBuilder::new(App);\n #[cfg(feature = \"ssr\")]\n let config = config.incremental(\n IncrementalRendererConfig::default().invalidate_after(std::time::Duration::from_secs(120)),\n );\n\n config.launch();\n}\n\nfn App(cx: Scope) -> Element {\n let mut count = use_state(cx, || 0);\n\n cx.render(rsx! {\n h1 { \"High-Five counter: {count}\" }\n button { onclick: move |_| count += 1, \"Up high!\" }\n button { onclick: move |_| count -= 1, \"Down low!\" }\n button {\n onclick: move |_| {\n to_owned![count];\n async move {\n // Call the server function just like a local async function\n if let Ok(new_count) = double_server(*count.current()).await {\n count.set(new_count);\n }\n }\n },\n \"Double\"\n }\n })\n}\n\n#[server]\nasync fn double_server(number: i32) -> Result {\n // Perform some expensive computation or access a database on the server\n tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n let result = number * 2;\n println!(\"server calculated {result}\");\n Ok(result)\n}\n\n````\n\nNow, build your client-side bundle with `dx build --features web` and run your server with `cargo run --features ssr`. You should see a new button that multiplies the count by 2.\n\n## Cached data fetching\n\nOne common use case for server functions is fetching data from the server:\n\n````rust@server_data_fetch.rs\n#![allow(non_snake_case, unused)]\n\nuse dioxus::prelude::*;\nuse dioxus_fullstack::prelude::*;\n\nfn main() {\n LaunchBuilder::new(app).launch();\n}\n\nfn app(cx: Scope) -> Element {\n let mut count = use_future(cx, (), |_| async { get_server_data().await });\n\n cx.render(rsx! {\n \"server data is {count.value():?}\"\n })\n}\n\n#[server]\nasync fn get_server_data() -> Result {\n // Access a database\n tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n Ok(\"Hello from the server!\".to_string())\n}\n\n````\n\nIf you navigate to the site above, you will first see `server data is None`, then after the `WASM` has loaded and the request to the server has finished, you will see `server data is Some(Ok(\"Hello from the server!\"))`.\n\nThis approach works, but it can be slow. Instead of waiting for the client to load and send a request to the server, what if we could get all of the data we needed for the page on the server and send it down to the client with the initial HTML page?\n\nThis is exactly what the `use_server_future` hook allows us to do! `use_server_future` is similar to the `use_future` hook, but it allows you to wait for a future on the server and send the result of the future down to the client.\n\nLet's change our data fetching to use `use_server_future`:\n\n````rust@server_data_prefetch.rs\n#![allow(non_snake_case, unused)]\n\nuse dioxus::prelude::*;\nuse dioxus_fullstack::prelude::*;\n\nfn main() {\n LaunchBuilder::new(app).launch();\n}\n\nfn app(cx: Scope) -> Element {\n let mut count = use_server_future(cx, (), |_| async { get_server_data().await })?;\n\n cx.render(rsx! {\n \"server data is {count.value():?}\"\n })\n}\n\n#[server]\nasync fn get_server_data() -> Result {\n // Access a database\n tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n Ok(\"Hello from the server!\".to_string())\n}\n\n````\n\n > \n > Notice the `?` after `use_server_future`. This is what tells Dioxus fullstack to wait for the future to resolve before continuing rendering. If you want to not wait for a specific future, you can just remove the ? and deal with the `Option` manually.\n\nNow when you load the page, you should see `server data is Ok(\"Hello from the server!\")`. No need to wait for the `WASM` to load or wait for the request to finish!\n\n````inject-dioxus\nSandBoxFrame {\n\turl: \"https://codesandbox.io/p/sandbox/dioxus-fullstack-server-future-qwpp4p?file=/src/main.rs:3,24\"\n}\n````\n\n## Running the client with dioxus-desktop\n\nThe project presented so far makes a web browser interact with the server, but it is also possible to make a desktop program interact with the server in a similar fashion. (The full example code is available in the [Dioxus repo](https://github.com/DioxusLabs/dioxus/tree/master/packages/fullstack/examples/axum-desktop))\n\nFirst, we need to make two binary targets, one for the desktop program (the `client.rs` file), one for the server (the `server.rs` file). The client app and the server functions are written in a shared `lib.rs` file.\n\nThe desktop and server targets have slightly different build configuration to enable additional dependencies or features.\nThe Cargo.toml in the full example has more information, but the main points are:\n\n* the client.rs has to be run with the `desktop` feature, so that the optional `dioxus-desktop` dependency is included\n* the server.rs has to be run with the `ssr` features; this will generate the server part of the server functions and will include the `axum` dependency to run as a server.\n\nOnce you create your project, you can run the server executable with:\n\n````bash\ncargo run --bin server --features ssr\n````\n\nand the client desktop executable with:\n\n````bash\ncargo run --bin client --features desktop\n````\n\n### Client code\n\nThe client file is pretty straightforward. You only need to set the server url in the client code, so it knows where to send the network requests. Then, dioxus_desktop launches the app.\n\nFor development, the example project runs the server on `localhost:8080`. **Before you release remember to update the url to your production url.**\n\n### Server code\n\nIn the server code, first you have to set the network address and port where the server will listen to.\n\n````rust@server_function_desktop_client.rs\nlet addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));\n````\n\nThen, you have to register the types declared in the server function macros into the axum server.\nFor example, consider this server function:\n\n````rust@server_function_desktop_client.rs\n#[server(GetServerData)]\nasync fn get_server_data() -> Result {\n Ok(\"Hello from the server!\".to_string())\n}\n````\n\nThe `GetServerData` type has to be registered in the axum server, which will add the corresponding route to the server.\n\n````rust@server_function_desktop_client.rs\nlet _ = GetServerData::register_explicit();\n````\n\nFinally, the server is started and it begins responding to requests." + } + 63usize => { + "# Custom Hooks\n\nHooks are a great way to encapsulate business logic. If none of the existing hooks work for your problem, you can write your own.\n\nWhen writing your hook, you can make a function that accepts `cx: &ScopeState` as a parameter to accept a scope with any Props.\n\n## Composing Hooks\n\nTo avoid repetition, you can encapsulate business logic based on existing hooks to create a new hook.\n\nFor example, if many components need to access an `AppSettings` struct, you can create a \"shortcut\" hook:\n\n````rust@hooks_composed.rs\nfn use_settings(cx: &ScopeState) -> &UseSharedState {\n use_shared_state::(cx).expect(\"App settings not provided\")\n}\n````\n\nOr if you want to wrap a hook that persists reloads with the storage API, you can build on top of the use_ref hook to work with mutable state:\n\n````rust@hooks_composed.rs\nuse gloo_storage::{LocalStorage, Storage};\nuse serde::{de::DeserializeOwned, Serialize};\n\n/// A persistent storage hook that can be used to store data across application reloads.\n#[allow(clippy::needless_return)]\npub fn use_persistent(\n cx: &ScopeState,\n // A unique key for the storage entry\n key: impl ToString,\n // A function that returns the initial value if the storage entry is empty\n init: impl FnOnce() -> T,\n) -> &UsePersistent {\n // Use the use_ref hook to create a mutable state for the storage entry\n let state = use_ref(cx, move || {\n // This closure will run when the hook is created\n let key = key.to_string();\n let value = LocalStorage::get(key.as_str()).ok().unwrap_or_else(init);\n StorageEntry { key, value }\n });\n\n // Wrap the state in a new struct with a custom API\n // Note: We use use_hook here so that this hook is easier to use in closures in the rsx. Any values with the same lifetime as the ScopeState can be used in the closure without cloning.\n cx.use_hook(|| UsePersistent {\n inner: state.clone(),\n })\n}\n\nstruct StorageEntry {\n key: String,\n value: T,\n}\n\n/// Storage that persists across application reloads\npub struct UsePersistent {\n inner: UseRef>,\n}\n\nimpl UsePersistent {\n /// Returns a reference to the value\n pub fn get(&self) -> T {\n self.inner.read().value.clone()\n }\n\n /// Sets the value\n pub fn set(&self, value: T) {\n let mut inner = self.inner.write();\n // Write the new value to local storage\n LocalStorage::set(inner.key.as_str(), &value);\n inner.value = value;\n }\n}\n````\n\n## Custom Hook Logic\n\nYou can use [`cx.use_hook`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html#method.use_hook) to build your own hooks. In fact, this is what all the standard hooks are built on!\n\n`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.\n\n > \n > 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)\n\nInside the initialization closure, you will typically make calls to other `cx` methods. For example:\n\n* The `use_state` hook tracks state in the hook value, and uses [`cx.schedule_update`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html#method.schedule_update) to make Dioxus re-render the component whenever it changes.\n\nHere is a simplified implementation of the `use_state` hook:\n\n````rust@hooks_custom_logic.rs\nuse std::cell::RefCell;\nuse std::rc::Rc;\nuse std::sync::Arc;\n\n#[derive(Clone)]\nstruct UseState {\n value: Rc>,\n update: Arc,\n}\n\nfn my_use_state(cx: &ScopeState, init: impl FnOnce() -> T) -> &UseState {\n cx.use_hook(|| {\n // The update function will trigger a re-render in the component cx is attached to\n let update = cx.schedule_update();\n // Create the initial state\n let value = Rc::new(RefCell::new(init()));\n\n UseState { value, update }\n })\n}\n\nimpl UseState {\n fn get(&self) -> T {\n self.value.borrow().clone()\n }\n\n fn set(&self, value: T) {\n // Update the state\n *self.value.borrow_mut() = value;\n // Trigger a re-render on the component the state is from\n (self.update)();\n }\n}\n````\n\n* The `use_context` hook calls [`cx.consume_context`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html#method.consume_context) (which would be expensive to call on every render) to get some context from the scope\n\nHere is an implementation of the `use_context` and `use_context_provider` hooks:\n\n````rust@hooks_custom_logic.rs\npub fn use_context(cx: &ScopeState) -> Option<&T> {\n cx.use_hook(|| cx.consume_context::()).as_ref()\n}\n\npub fn use_context_provider(cx: &ScopeState, f: impl FnOnce() -> T) -> &T {\n cx.use_hook(|| {\n let val = f();\n // Provide the context state to the scope\n cx.provide_context(val.clone());\n val\n })\n}\n\n````\n\n## Hook Anti-Patterns\n\nWhen writing a custom hook, you should avoid the following anti-patterns:\n\n* !Clone Hooks: To allow hooks to be used within async blocks, the hooks must be Clone. To make a hook clone, you can wrap data in Rc or Arc and avoid lifetimes in hooks.\n\nThis version of use_state may seem more efficient, but it is not cloneable:\n\n````rust@hooks_anti_patterns.rs\nuse std::cell::RefCell;\nuse std::rc::Rc;\nuse std::sync::Arc;\n\nstruct UseState<'a, T> {\n value: &'a RefCell,\n update: Arc,\n}\n\nfn my_use_state(cx: &ScopeState, init: impl FnOnce() -> T) -> UseState {\n // The update function will trigger a re-render in the component cx is attached to\n let update = cx.schedule_update();\n // Create the initial state\n let value = cx.use_hook(|| RefCell::new(init()));\n\n UseState { value, update }\n}\n\nimpl UseState<'_, T> {\n fn get(&self) -> T {\n self.value.borrow().clone()\n }\n\n fn set(&self, value: T) {\n // Update the state\n *self.value.borrow_mut() = value;\n // Trigger a re-render on the component the state is from\n (self.update)();\n }\n}\n````\n\nIf we try to use this hook in an async block, we will get a compile error:\n\n````rust\nfn FutureComponent(cx: &ScopeState) -> Element {\n\tlet my_state = my_use_state(cx, || 0);\n\tcx.spawn({\n\t\tto_owned![my_state];\n\t\tasync move {\n\t\t\tmy_state.set(1);\n\t\t}\n\t});\n\n\ttodo!()\n}\n````\n\nBut with the original version, we can use it in an async block:\n\n````rust\nfn FutureComponent(cx: &ScopeState) -> Element {\n\tlet my_state = use_state(cx, || 0);\n\tcx.spawn({\n\t\tto_owned![my_state];\n\t\tasync move {\n\t\t\tmy_state.set(1);\n\t\t}\n\t});\n\n\ttodo!()\n}\n````" + } + 61usize => { + "# State Cookbook\n\n* [External State](external/index.md)\n* [Custom Hook](custom_hooks/index.md)" + } + 55usize => { + "# Publishing\n\nAfter you have build your application, you will need to publish it somewhere. This reference will outline different methods of publishing your desktop or web application.\n\n## Web: Publishing with GitHub Pages\n\nEdit your `Dioxus.toml` to point your `out_dir` to the `docs` folder and the `base_path` to the name of your repo:\n\n````toml\n[application]\n# ...\nout_dir = \"docs\"\n\n[web.app]\nbase_path = \"your_repo\"\n````\n\nThen build your app and publish it to Github:\n\n* Make sure GitHub Pages is set up for your repo to publish any static files in the docs directory\n* Build your app with:\n\n````sh\ndx build --release\n````\n\n* Add and commit with git\n* Push to GitHub\n\n## Desktop: Creating an installer\n\nDioxus desktop app uses your operating system's WebView library, so it's portable to be distributed for other platforms.\n\nIn this section, we'll cover how to bundle your app for macOS, Windows, and Linux.\n\n## Preparing your application for bundling\n\nDepending on your platform, you may need to add some additional code to your `main.rs` file to make sure your app is ready for bundling. On Windows, you'll need to add the `#![windows_subsystem = \"windows\"]` attribute to your `main.rs` file to hide the terminal window that pops up when you run your app. **If you're developing on Windows, only use this when bundling.** It will disable the terminal, so you will not get logs of any kind. You can gate it behind a feature, like so:\n\n````toml\n# Cargo.toml\n[features]\nbundle = []\n````\n\nAnd then your `main.rs`:\n\n````rust\n#![cfg_attr(feature = \"bundle\", windows_subsystem = \"windows\")]\n````\n\n## Install `dioxus CLI`\n\nThe first thing we'll do is install the [dioxus-cli](https://github.com/DioxusLabs/dioxus/tree/master/packages/cli). This extension to cargo will make it very easy to package our app for the various platforms.\n\nTo install, simply run\n\n`cargo install dioxus-cli`\n\n## Building\n\nTo bundle your application you can simply run `dx bundle --release` (also add `--features bundle` if you're using that, see the [this](#preparing-your-application-for-bundling) for more) to produce a final app with all the optimizations and assets builtin.\n\nOnce you've ran the command, your app should be accessible in `dist/bundle/`.\n\nFor example, a macOS app would look like this:\n\n![Published App](/assets/static/publish.png)\n\nNice! 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.\n\n > \n > 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." + } + 20usize => { + "# User Input\n\nInterfaces 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.\n\n## Controlled Inputs\n\nWith 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:\n\n````rust, no_run@input_controlled.rs\npub fn App(cx: Scope) -> Element {\n let name = use_state(cx, || \"bob\".to_string());\n\n cx.render(rsx! {\n input {\n // we tell the component what to render\n value: \"{name}\",\n // and what to do when the value changes\n oninput: move |evt| name.set(evt.value.clone()),\n }\n })\n}\n````\n\n````inject-dioxus\nDemoFrame {\n input_controlled::App {}\n}\n````\n\nNotice the flexibility – you can:\n\n* Also display the same contents in another element, and they will be in sync\n* Transform the input every time it is modified (e.g. to make sure it is upper case)\n* Validate the input every time it changes\n* Have custom logic happening when the input changes (e.g. network request for autocompletion)\n* Programmatically change the value (e.g. a \"randomize\" button that fills the input with nonsense)\n\n## Uncontrolled Inputs\n\nAs 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 will be editable anyway (this is built into the browser). This approach can be more performant, but less flexible. For example, it's harder to keep the input in sync with another element.\n\nSince 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`):\n\n````rust, no_run@input_uncontrolled.rs\npub fn App(cx: Scope) -> Element {\n cx.render(rsx! {\n form {\n onsubmit: move |event| {\n log::info!(\"Submitted! {event:?}\")\n },\n input { name: \"name\", },\n input { name: \"age\", },\n input { name: \"date\", },\n input { r#type: \"submit\", },\n }\n })\n}\n````\n\n````inject-dioxus\nDemoFrame {\n input_uncontrolled::App {}\n}\n````\n\n````\nSubmitted! UiEvent { data: FormData { value: \"\", values: {\"age\": \"very old\", \"date\": \"1966\", \"name\": \"Fred\"} } }\n````\n\n## Handling files\n\nYou can insert a file picker by using an input element of type `file`. This element supports the `multiple` attribute, to let you pick more files at the same time. You can select a folder by adding the `directory` attribute: Dioxus will map this attribute to browser specific attributes, because there is no standardized way to allow a directory to be selected.\n\n`type` is a Rust keyword, so when specifying the type of the input field, you have to write it as `r#type:\"file\"`.\n\nExtracting the selected files is a bit different from what you may typically use in Javascript.\n\nThe `FormData` event contains a `files` field with data about the uploaded files. This field contains a `FileEngine` struct which lets you fetch the filenames selected by the user. This example saves the filenames of the selected files to a `Vec`:\n\n````rust, no_run@input_fileengine.rs\npub fn App(cx: Scope) -> Element {\n let filenames: &UseRef> = use_ref(cx, Vec::new);\n cx.render(rsx! {\n input {\n // tell the input to pick a file\n r#type:\"file\",\n // list the accepted extensions\n accept: \".txt,.rs\",\n // pick multiple files\n multiple: true,\n onchange: move |evt| {\n if let Some(file_engine) = &evt.files {\n let files = file_engine.files();\n for file_name in files {\n filenames.write().push(file_name);\n }\n }\n }\n }\n })\n}\n````\n\nIf you're planning to read the file content, you need to do it asynchronously, to keep the rest of the UI interactive. This example event handler loads the content of the selected files in an async closure:\n\n````rust, no_run@input_fileengine_async.rs\nonchange: move |evt| {\n // A helper macro to use hooks in async environments\n to_owned![files_uploaded];\n async move {\n if let Some(file_engine) = &evt.files {\n let files = file_engine.files();\n for file_name in &files {\n // Make sure to use async/await when doing heavy I/O operations,\n // to not freeze the interface in the meantime\n if let Some(file) = file_engine.read_file_to_string(file_name).await{\n files_uploaded.write().push(file);\n }\n }\n }\n }\n}\n````\n\nLastly, this example shows you how to select a folder, by setting the `directory` attribute to `true`.\n\n````rust, no_run@input_fileengine_folder.rs\ninput {\n r#type:\"file\",\n // Select a folder by setting the directory attribute\n directory: true,\n onchange: |evt| {\n if let Some(file_engine) = &evt.files {\n let files = file_engine.files();\n for file_name in files {\n println!(\"{}\", file_name);\n // Do something with the folder path\n }\n }\n }\n}\n````" + } + 60usize => { + "# Internationalization\n\nIf your application supports multiple languages, the [Dioxus SDK](https://github.com/DioxusLabs/sdk) crate contains helpers to make working with translations in your application easier.\n\n## The full code for internationalization\n\n````rust@i18n.rs\nuse dioxus::prelude::*;\nuse dioxus_std::i18n::*;\nuse dioxus_std::translate;\nuse std::str::FromStr;\n\nfn main() {\n dioxus_web::launch(app);\n}\n\nstatic EN_US: &str = r#\"{\n \"id\": \"en-US\",\n \"texts\": {\n \"messages\": {\n \"hello_world\": \"Hello World!\"\n },\n \"messages.hello\": \"Hello {name}\"\n }\n}\"#;\nstatic ES_ES: &str = r#\"{\n \"id\": \"es-ES\",\n \"texts\": {\n \"messages\": {\n \"hello_world\": \"Hola Mundo!\"\n },\n \"messages.hello\": \"Hola {name}\"\n }\n}\"#;\n\n#[allow(non_snake_case)]\nfn Body(cx: Scope) -> Element {\n let i18 = use_i18(cx);\n\n let change_to_english = move |_| i18.set_language(\"en-US\".parse().unwrap());\n let change_to_spanish = move |_| i18.set_language(\"es-ES\".parse().unwrap());\n\n render!(\n button {\n onclick: change_to_english,\n label {\n \"English\"\n }\n }\n button {\n onclick: change_to_spanish,\n label {\n \"Spanish\"\n }\n }\n p { translate!(i18, \"messages.hello_world\") }\n p { translate!(i18, \"messages.hello\", name: \"Dioxus\") }\n )\n}\n\nfn app(cx: Scope) -> Element {\n use_init_i18n(\n cx,\n \"en-US\".parse().unwrap(),\n \"en-US\".parse().unwrap(),\n || {\n let en_us = Language::from_str(EN_US).unwrap();\n let es_es = Language::from_str(ES_ES).unwrap();\n vec![en_us, es_es]\n },\n );\n\n render!(Body {})\n}\n\n````" + } + 81usize => { + "# Hot reloading\n\nDesktop hot reloading has changed in the `0.4` release to use the [Dioxus CLI](../CLI/index.md) for all platforms.\n\nPreviously, you may have included the `hot_reload_init!` macro in your main function. This is no longer needed.\n\nold:\n\n````rust\nfn main() {\n hot_reload_init!();\n // ...\n}\n````\n\nnew:\n\n````rust\nfn main() {\n // ...\n}\n````\n\nNow you can run your project with the dioxus CLI by passing the `--platform` flag:\n\n````sh\ndx serve --platform desktop --hot-reload\n````" + } + 11usize => { + "# Interactivity\n\nIn this chapter, we will add a preview for articles you hover over or links you focus on.\n\n## Creating a Preview\n\nFirst, let's split our app into a Stories component on the left side of the screen, and a preview component on the right side of the screen:\n\n````rust@hackernews_state.rs\npub fn App(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n display: \"flex\",\n flex_direction: \"row\",\n width: \"100%\",\n div {\n width: \"50%\",\n Stories {}\n }\n div {\n width: \"50%\",\n Preview {}\n }\n }\n })\n}\n\n// New\nfn Stories(cx: Scope) -> Element {\n render! {\n StoryListing {\n story: StoryItem {\n id: 0,\n title: \"hello hackernews\".to_string(),\n url: None,\n text: None,\n by: \"Author\".to_string(),\n score: 0,\n descendants: 0,\n time: chrono::Utc::now(),\n kids: vec![],\n r#type: \"\".to_string(),\n }\n }\n }\n}\n\n// New\n#[derive(Clone, Debug)]\nenum PreviewState {\n Unset,\n Loading,\n Loaded(StoryPageData),\n}\n\n// New\nfn Preview(cx: Scope) -> Element {\n let preview_state = PreviewState::Unset;\n match preview_state {\n PreviewState::Unset => render! {\n \"Hover over a story to preview it here\"\n },\n PreviewState::Loading => render! {\n \"Loading...\"\n },\n PreviewState::Loaded(story) => {\n let title = &story.item.title;\n let url = story.item.url.as_deref().unwrap_or_default();\n let text = story.item.text.as_deref().unwrap_or_default();\n render! {\n div {\n padding: \"0.5rem\",\n div {\n font_size: \"1.5rem\",\n a {\n href: \"{url}\",\n \"{title}\"\n }\n }\n div {\n dangerous_inner_html: \"{text}\",\n }\n for comment in &story.comments {\n Comment { comment: comment.clone() }\n }\n }\n }\n }\n }\n}\n\n// NEW\n#[component]\nfn Comment(cx: Scope, comment: Comment) -> Element<'a> {\n render! {\n div {\n padding: \"0.5rem\",\n div {\n color: \"gray\",\n \"by {comment.by}\"\n }\n div {\n dangerous_inner_html: \"{comment.text}\"\n }\n for kid in &comment.sub_comments {\n Comment { comment: kid.clone() }\n }\n }\n }\n}\n\n````\n\n````inject-dioxus\nDemoFrame {\n hackernews_state::app_v1::App {}\n}\n````\n\n## Event Handlers\n\nNext, we need to detect when the user hovers over a section or focuses a link. We can use an [event listener](../reference/event_handlers.md) to listen for the hover and focus events.\n\nEvent 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 it listens for is triggered. When an event is triggered, information about the event is passed to the closure though the [Event](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Event.html) structure.\n\nLet's create a [`onmouseenter`](https://docs.rs/dioxus/latest/dioxus/events/fn.onmouseenter.html) event listener in the `StoryListing` component:\n\n````rust@hackernews_state.rs\ncx.render(rsx! {\n div {\n padding: \"0.5rem\",\n position: \"relative\",\n onmouseenter: move |_| {\n // NEW\n },\n div {\n font_size: \"1.5rem\",\n a {\n href: url,\n onfocus: move |_event| {\n // NEW\n },\n \"{title}\"\n }\n a {\n color: \"gray\",\n href: \"https://news.ycombinator.com/from?site={hostname}\",\n text_decoration: \"none\",\n \" ({hostname})\"\n }\n }\n div {\n display: \"flex\",\n flex_direction: \"row\",\n color: \"gray\",\n div {\n \"{score}\"\n }\n div {\n padding_left: \"0.5rem\",\n \"by {by}\"\n }\n div {\n padding_left: \"0.5rem\",\n \"{time}\"\n }\n div {\n padding_left: \"0.5rem\",\n \"{comments}\"\n }\n }\n }\n})\n````\n\n > \n > You can read more about Event Handlers in the [Event Handler reference](../reference/event_handlers.md)\n\n## State\n\nSo far our components have had no state like normal rust functions. To make our application change when we hover over a link we need state to store the currently hovered link in the root of the application.\n\nYou can create state in dioxus using 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.\n\nIn this case, we will use the `use_shared_state_provider` and `use_shared_state` hooks:\n\n* You can provide a closure to `use_shared_state_provider` that determines the initial value of the shared state and provides the value to all child components\n* You can then use the `use_shared_state` hook to read and modify that state in the `Preview` and `StoryListing` components\n* When the value updates, `use_shared_state` will make the component re-render, and provides you with the new value\n\n > \n > Note: You should prefer local state hooks like use_state or use_ref when you only use state in one component. Because we use state in multiple components, we can use a [global state pattern](../reference/context.md)\n\n````rust@hackernews_state.rs\npub fn App(cx: Scope) -> Element {\n use_shared_state_provider(cx, || PreviewState::Unset);\n````\n\n````rust@hackernews_state.rs\n#[component]\nfn StoryListing(cx: Scope, story: StoryItem) -> Element {\n // New\n let preview_state = use_shared_state::(cx).unwrap();\n let StoryItem {\n title,\n url,\n by,\n score,\n time,\n kids,\n ..\n } = story;\n\n let url = url.as_deref().unwrap_or_default();\n let hostname = url\n .trim_start_matches(\"https://\")\n .trim_start_matches(\"http://\")\n .trim_start_matches(\"www.\");\n let score = format!(\"{score} {}\", if *score == 1 { \" point\" } else { \" points\" });\n let comments = format!(\n \"{} {}\",\n kids.len(),\n if kids.len() == 1 {\n \" comment\"\n } else {\n \" comments\"\n }\n );\n let time = time.format(\"%D %l:%M %p\");\n\n cx.render(rsx! {\n div {\n padding: \"0.5rem\",\n position: \"relative\",\n onmouseenter: move |_event| {\n // NEW\n // set the preview state to this story\n *preview_state.write() = PreviewState::Loaded(StoryPageData {\n item: story.clone(),\n comments: vec![],\n });\n },\n div {\n font_size: \"1.5rem\",\n a {\n href: url,\n onfocus: move |_event| {\n // NEW\n // set the preview state to this story\n *preview_state.write() = PreviewState::Loaded(StoryPageData {\n item: story.clone(),\n comments: vec![],\n });\n },\n````\n\n````rust@hackernews_state.rs\nfn Preview(cx: Scope) -> Element {\n // New\n let preview_state = use_shared_state::(cx)?;\n\n // New\n match &*preview_state.read() {\n````\n\n````inject-dioxus\nDemoFrame {\n hackernews_state::App {}\n}\n````\n\n > \n > You can read more about Hooks in the [Hooks reference](../reference/hooks.md)\n\n### The Rules of Hooks\n\nHooks are a powerful way to manage state in Dioxus, but there are some rules you need to follow to insure they work as expected. Dioxus uses the order you call hooks to differentiate between hooks. Because the order you call hooks matters, you must follow these rules:\n\n1. Hooks may be only used in components or other hooks (we'll get to that later)\n1. On every call to the component function\n 1. The same hooks must be called\n 1. In the same order\n1. Hooks name's should start with `use_` so you don't accidentally confuse them with regular functions\n\nThese rules mean that there are certain things you can't do with hooks:\n\n#### No Hooks in Conditionals\n\n````rust@hooks_bad.rs\n// ❌ don't call hooks in conditionals!\n// We must ensure that the same hooks will be called every time\n// But `if` statements only run if the conditional is true!\n// So we might violate rule 2.\nif you_are_happy && you_know_it {\n let something = use_state(cx, || \"hands\");\n println!(\"clap your {something}\")\n}\n\n// ✅ instead, *always* call use_state\n// You can put other stuff in the conditional though\nlet something = use_state(cx, || \"hands\");\nif you_are_happy && you_know_it {\n println!(\"clap your {something}\")\n}\n````\n\n#### No Hooks in Closures\n\n````rust@hooks_bad.rs\n// ❌ don't call hooks inside closures!\n// We can't guarantee that the closure, if used, will be called in the same order every time\nlet _a = || {\n let b = use_state(cx, || 0);\n b.get()\n};\n\n// ✅ instead, move hook `b` outside\nlet b = use_state(cx, || 0);\nlet _a = || b.get();\n````\n\n#### No Hooks in Loops\n\n````rust@hooks_bad.rs\n// `names` is a Vec<&str>\n\n// ❌ Do not use hooks in loops!\n// In this case, if the length of the Vec changes, we break rule 2\nfor _name in &names {\n let is_selected = use_state(cx, || false);\n println!(\"selected: {is_selected}\");\n}\n\n// ✅ Instead, use a hashmap with use_ref\nlet selection_map = use_ref(cx, HashMap::<&str, bool>::new);\n\nfor name in &names {\n let is_selected = selection_map.read()[name];\n println!(\"selected: {is_selected}\");\n}\n````" + } + 50usize => { + "# History Providers\n\n\\[`HistoryProvider`\\]s are used by the router to keep track of the navigation history\nand update any external state (e.g. the browser's URL).\n\nThe router provides two \\[`HistoryProvider`\\]s, but you can also create your own.\nThe two default implementations are:\n\n* The \\[`MemoryHistory`\\] is a custom implementation that works in memory.\n* The \\[`WebHistory`\\] integrates with the browser's URL.\n\nBy default, the router uses the \\[`MemoryHistory`\\]. It might be changed to use\n\\[`WebHistory`\\] when the `web` feature is active, but that is not guaranteed.\n\nYou can override the default history:\n\n````rust@history_provider.rs\n#[component]\nfn App(cx: Scope) -> Element {\n render! {\n Router:: {\n config: || RouterConfig::default().history(WebHistory::default())\n }\n }\n}\n````" + } + 27usize => { + "# Desktop\n\nThis guide will cover concepts specific to the Dioxus desktop renderer.\n\n## Running Javascript\n\nDioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose.\n\nFor these cases, Dioxus desktop exposes the use_eval hook that allows you to run raw Javascript in the webview:\n\n````rust@eval.rs\nuse dioxus::prelude::*;\n\nfn main() {\n dioxus_desktop::launch(app);\n}\n\nfn app(cx: Scope) -> Element {\n // Use eval returns a function that can spawn eval instances\n let create_eval = use_eval(cx);\n\n // You can create as many eval instances as you want\n let mut eval = create_eval(\n r#\"\n // You can send messages from JavaScript to Rust with the dioxus.send function\n dioxus.send(\"Hi from JS!\");\n // You can receive messages from Rust to JavaScript with the dioxus.recv function\n let msg = await dioxus.recv();\n console.log(msg);\n \"#,\n )\n .unwrap();\n\n // You can send messages to JavaScript with the send method\n eval.send(\"Hi from Rust!\".into()).unwrap();\n\n let future = use_future(cx, (), |_| {\n to_owned![eval];\n async move {\n // You can receive any message from JavaScript with the recv method\n eval.recv().await.unwrap()\n }\n });\n\n match future.value() {\n Some(v) => cx.render(rsx!(\n p { \"{v}\" }\n )),\n _ => cx.render(rsx!(\n p { \"hello\" }\n )),\n }\n}\n\n````\n\n## Custom Assets\n\nYou can link to local assets in dioxus desktop instead of using a url:\n\n````rust@custom_assets.rs\nuse dioxus::prelude::*;\n\nfn main() {\n dioxus_desktop::launch(app);\n}\n\nfn app(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n img { src: \"examples/assets/logo.png\" }\n }\n })\n}\n\n````\n\n## Integrating with Wry\n\nIn cases where you need more low level control over your window, you can use wry APIs exposed through the [Desktop Config](https://docs.rs/dioxus-desktop/0.3.0/dioxus_desktop/struct.Config.html) and the [use_window hook](https://docs.rs/dioxus-desktop/0.4.0/dioxus_desktop/fn.use_window.html)" + } + 28usize => { + "# Web\n\nThis guide will cover concepts specific to the Dioxus web renderer.\n\n## Running Javascript\n\nDioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose.\n\nFor these cases, Dioxus desktop exposes the use_eval hook that allows you to run raw Javascript in the webview:\n\n````rust@eval.rs\nuse dioxus::prelude::*;\n\nfn main() {\n dioxus_desktop::launch(app);\n}\n\nfn app(cx: Scope) -> Element {\n // Use eval returns a function that can spawn eval instances\n let create_eval = use_eval(cx);\n\n // You can create as many eval instances as you want\n let mut eval = create_eval(\n r#\"\n // You can send messages from JavaScript to Rust with the dioxus.send function\n dioxus.send(\"Hi from JS!\");\n // You can receive messages from Rust to JavaScript with the dioxus.recv function\n let msg = await dioxus.recv();\n console.log(msg);\n \"#,\n )\n .unwrap();\n\n // You can send messages to JavaScript with the send method\n eval.send(\"Hi from Rust!\".into()).unwrap();\n\n let future = use_future(cx, (), |_| {\n to_owned![eval];\n async move {\n // You can receive any message from JavaScript with the recv method\n eval.recv().await.unwrap()\n }\n });\n\n match future.value() {\n Some(v) => cx.render(rsx!(\n p { \"{v}\" }\n )),\n _ => cx.render(rsx!(\n p { \"hello\" }\n )),\n }\n}\n\n````\n\nIf you are targeting web, but don't plan on targeting any other Dioxus renderer you can also use the generated wrappers in the [web-sys](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html) and [gloo](https://gloo-rs.web.app/) crates.\n\n## Customizing Index Template\n\nDioxus supports providing custom index.html templates. The index.html must include a `div` with the id `main` to be used. Hot Reload is still supported. An example\nis provided in the [PWA-Example](https://github.com/DioxusLabs/Dioxus/examples/PWA-example/index.html)." + } + 37usize => { + "# Introduction\n\n > \n > If you are not familiar with Dioxus itself, check out the [Dioxus guide](../guide/index.md) first.\n\nWhether you are building a website, desktop app, or mobile app, splitting your app's views into \"pages\" can be an effective method for organization and maintainability.\n\nFor this purpose, Dioxus provides a router. Use the `cargo add` command to add the dependency:\n\n````sh\ncargo add dioxus-router\n````\n\nThen, add this to your `Dioxus.toml` (learn more about configuration [here](../CLI/configure.md)):\n\n````toml\n[web.watcher]\nindex_on_404 = true\n````\n\n > \n > This configuration only works when using `dx serve`. If you host your app in a different way (which you most likely do in production), you need to find out how to add a fallback 404 page to your app, and make it a copy of the generated `dist/index.html`.\n\nThis will instruct `dx serve` to redirect any unknown route to the index, to then be resolved by the router.\nThe router works on the client. If we connect through the index route (e.g., `localhost:8080`, then click a link to go to `localhost:8080/contact`), the app renders the new route without reloading.\nHowever, when we go to a route *before* going to the index (go straight to `localhost:8080/contact`), we are trying to access a static route from the server, but the only static route on our server is the index (because the Dioxus frontend is a Single Page Application) and it will fail unless we redirect all missing routes to the index.\n\nThis book is intended to get you up to speed with Dioxus Router. It is split\ninto two sections:\n\n1. The [reference](reference/index.md) section explains individual features in\n depth. You can read it from start to finish, or you can read individual chapters\n in whatever order you want.\n1. If you prefer a learning-by-doing approach, you can check out the\n *[example project](example/index.md)*. It guides you through\n creating a dioxus app, setting up the router, and using some of its\n functionality.\n\n > \n > Please note that this is not the only documentation for the Dioxus Router. You\n > can also check out the [API Docs](https://docs.rs/dioxus-router/)." + } + 70usize => { + "# Installation\n\n## Install the stable version (recommended)\n\n````\ncargo install dioxus-cli\n````\n\nIf you get an OpenSSL error on installation, ensure the dependencies listed [here](https://docs.rs/openssl/latest/openssl/#automatic) are installed.\n\n## Install the latest development build through git\n\nTo get the latest bug fixes and features, you can install the development version from git. However, this is not fully tested. That means you're probably going to have more bugs despite having the latest bug fixes.\n\n````\ncargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli\n````\n\nThis will download the CLI from the master branch, and install it in Cargo's global binary directory (`~/.cargo/bin/` by default).\n\nRun `dx --help` for a list of all the available commands. Furthermore, you can run `dx --help` to get help with a specific command." + } + 23usize => { + "# Router\n\nIn 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. For a desktop app, these scenes might be different views in your app.\n\nTo unify these platforms, Dioxus provides a first-party solution for scene management called Dioxus Router.\n\n## What is it?\n\nFor an app like the Dioxus landing page (https://dioxuslabs.com), we want to have several different scenes:\n\n* Homepage\n* Blog\n\nEach of these scenes is independent – we don't want to render both the homepage and blog at the same time.\n\nThe Dioxus router makes it easy to create these scenes. To make sure we're using the router, add the `dioxus-router` package to your `Cargo.toml`.\n\n````shell\ncargo add dioxus-router\n````\n\n## Using the router\n\nUnlike other routers in the Rust ecosystem, our router is built declaratively at compile time. This makes it possible to compose our app layout simply by defining an enum.\n\n````rust\n// All of our routes will be a variant of this Route enum\nenum Route {\n\t// if the current location is \"/home\", render the Home component\n\t#[route(\"/home\")]\n\tHome {},\n\t// if the current location is \"/blog\", render the Blog component\n\t#[route(\"/blog\")]\n\tBlog {},\n}\n````\n\nWhenever we visit this app, we will get either the Home component or the Blog component rendered depending on which route we enter at. If neither of these routes match the current location, then nothing will render.\n\nWe can fix this one of two ways:\n\n* A fallback 404 page\n\n````rust\nenum Route {\n\t#[route(\"/home\")]\n\tHome {},\n\t#[route(\"/blog\")]\n\tBlog {},\n\t// if the current location doesn't match any of the above routes, render the NotFound component\n\t#[route(\"/:..segments\")]\n\tNotFound { segments: Vec },\n}\n````\n\n* Redirect 404 to home\n\n````rust\nenum Route {\n\t#[route(\"/home\")]\n\t// if the current location doesn't match any of the above routes, redirect to \"/home\"\n\t#[redirect(\"/:..segments\", |segments: Vec| Route::Home {})]\n\tHome {},\n\t#[route(\"/blog\")]\n\tBlog {},\n\t// if the current location doesn't match any of the above routes, render the NotFound component\n\t#[route(\"/:..segments\")]\n\tNotFound { segments: Vec },\n}\n````\n\n## Links\n\nFor our app to navigate these routes, we can provide clickable elements called Links. These simply wrap `` elements that, when clicked, navigate the app to the given location. Because our route is an enum of valid routes, if you try to link to a page that doesn't exist, you will get a compiler error.\n\n````rust\nrsx! {\n\tLink {\n\t\tto: Route::Home {},\n\t\t\"Go home!\"\n\t}\n}\n````\n\n## More reading\n\nThis page is just a very brief overview of the router. For more information, check out the [router book](../router/index.md) or some of the [router examples](https://github.com/DioxusLabs/dioxus/blob/master/examples/router.rs)." + } + 13usize => { + "# Conclusion\n\nWell done! You've completed the Dioxus guide and built a hackernews application in Dioxus.\n\nTo continue your journey, you can attempt a challenge listed below, or look at the [Dioxus reference](../reference/index.md).\n\n## Challenges\n\n* Organize your components into separate files for better maintainability.\n* Give your app some style if you haven't already.\n* Integrate your application with the [Dioxus router](../router/index.md).\n\n## The full code for the hacker news project\n\n````rust@hackernews_complete.rs\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\n\npub fn App(cx: Scope) -> Element {\n use_shared_state_provider(cx, || PreviewState::Unset);\n\n cx.render(rsx! {\n div {\n display: \"flex\",\n flex_direction: \"row\",\n width: \"100%\",\n div {\n width: \"50%\",\n Stories {}\n }\n div {\n width: \"50%\",\n Preview {}\n }\n }\n })\n}\n\nfn Stories(cx: Scope) -> Element {\n let story = use_future(cx, (), |_| get_stories(10));\n\n match story.value() {\n Some(Ok(list)) => render! {\n div {\n for story in list {\n StoryListing { story: story.clone() }\n }\n }\n },\n Some(Err(err)) => render! {\"An error occurred while fetching stories {err}\"},\n None => render! {\"Loading items\"},\n }\n}\n\nasync fn resolve_story(\n full_story: UseRef>,\n preview_state: UseSharedState,\n story_id: i64,\n) {\n if let Some(cached) = &*full_story.read() {\n *preview_state.write() = PreviewState::Loaded(cached.clone());\n return;\n }\n\n *preview_state.write() = PreviewState::Loading;\n if let Ok(story) = get_story(story_id).await {\n *preview_state.write() = PreviewState::Loaded(story.clone());\n *full_story.write() = Some(story);\n }\n}\n\n#[component]\nfn StoryListing(cx: Scope, story: StoryItem) -> Element {\n let preview_state = use_shared_state::(cx).unwrap();\n let StoryItem {\n title,\n url,\n by,\n score,\n time,\n kids,\n id,\n ..\n } = story;\n let full_story = use_ref(cx, || None);\n\n let url = url.as_deref().unwrap_or_default();\n let hostname = url\n .trim_start_matches(\"https://\")\n .trim_start_matches(\"http://\")\n .trim_start_matches(\"www.\");\n let score = format!(\"{score} {}\", if *score == 1 { \" point\" } else { \" points\" });\n let comments = format!(\n \"{} {}\",\n kids.len(),\n if kids.len() == 1 {\n \" comment\"\n } else {\n \" comments\"\n }\n );\n let time = time.format(\"%D %l:%M %p\");\n\n cx.render(rsx! {\n div {\n padding: \"0.5rem\",\n position: \"relative\",\n onmouseenter: move |_event| {\n resolve_story(full_story.clone(), preview_state.clone(), *id)\n },\n div {\n font_size: \"1.5rem\",\n a {\n href: url,\n onfocus: move |_event| {\n resolve_story(full_story.clone(), preview_state.clone(), *id)\n },\n \"{title}\"\n }\n a {\n color: \"gray\",\n href: \"https://news.ycombinator.com/from?site={hostname}\",\n text_decoration: \"none\",\n \" ({hostname})\"\n }\n }\n div {\n display: \"flex\",\n flex_direction: \"row\",\n color: \"gray\",\n div {\n \"{score}\"\n }\n div {\n padding_left: \"0.5rem\",\n \"by {by}\"\n }\n div {\n padding_left: \"0.5rem\",\n \"{time}\"\n }\n div {\n padding_left: \"0.5rem\",\n \"{comments}\"\n }\n }\n }\n })\n}\n\n#[derive(Clone, Debug)]\nenum PreviewState {\n Unset,\n Loading,\n Loaded(StoryPageData),\n}\n\nfn Preview(cx: Scope) -> Element {\n let preview_state = use_shared_state::(cx)?;\n\n match &*preview_state.read() {\n PreviewState::Unset => render! {\n \"Hover over a story to preview it here\"\n },\n PreviewState::Loading => render! {\n \"Loading...\"\n },\n PreviewState::Loaded(story) => {\n let title = &story.item.title;\n let url = story.item.url.as_deref().unwrap_or_default();\n let text = story.item.text.as_deref().unwrap_or_default();\n render! {\n div {\n padding: \"0.5rem\",\n div {\n font_size: \"1.5rem\",\n a {\n href: \"{url}\",\n \"{title}\"\n }\n }\n div {\n dangerous_inner_html: \"{text}\",\n }\n for comment in &story.comments {\n Comment { comment: comment.clone() }\n }\n }\n }\n }\n }\n}\n\n#[component]\nfn Comment(cx: Scope, comment: Comment) -> Element<'a> {\n render! {\n div {\n padding: \"0.5rem\",\n div {\n color: \"gray\",\n \"by {comment.by}\"\n }\n div {\n dangerous_inner_html: \"{comment.text}\"\n }\n for kid in &comment.sub_comments {\n Comment { comment: kid.clone() }\n }\n }\n }\n}\n\n// Define the Hackernews API and types\nuse chrono::{DateTime, Utc};\nuse futures::future::join_all;\nuse serde::{Deserialize, Serialize};\n\npub static BASE_API_URL: &str = \"https://hacker-news.firebaseio.com/v0/\";\npub static ITEM_API: &str = \"item/\";\npub static USER_API: &str = \"user/\";\nconst COMMENT_DEPTH: i64 = 2;\n\npub async fn get_story_preview(id: i64) -> Result {\n let url = format!(\"{}{}{}.json\", BASE_API_URL, ITEM_API, id);\n reqwest::get(&url).await?.json().await\n}\n\npub async fn get_stories(count: usize) -> Result, reqwest::Error> {\n let url = format!(\"{}topstories.json\", BASE_API_URL);\n let stories_ids = &reqwest::get(&url).await?.json::>().await?[..count];\n\n let story_futures = stories_ids[..usize::min(stories_ids.len(), count)]\n .iter()\n .map(|&story_id| get_story_preview(story_id));\n Ok(join_all(story_futures)\n .await\n .into_iter()\n .filter_map(|story| story.ok())\n .collect())\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryPageData {\n #[serde(flatten)]\n pub item: StoryItem,\n #[serde(default)]\n pub comments: Vec,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct Comment {\n pub id: i64,\n /// there will be no by field if the comment was deleted\n #[serde(default)]\n pub by: String,\n #[serde(default)]\n pub text: String,\n #[serde(with = \"chrono::serde::ts_seconds\")]\n pub time: DateTime,\n #[serde(default)]\n pub kids: Vec,\n #[serde(default)]\n pub sub_comments: Vec,\n pub r#type: String,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryItem {\n pub id: i64,\n pub title: String,\n pub url: Option,\n pub text: Option,\n #[serde(default)]\n pub by: String,\n #[serde(default)]\n pub score: i64,\n #[serde(default)]\n pub descendants: i64,\n #[serde(with = \"chrono::serde::ts_seconds\")]\n pub time: DateTime,\n #[serde(default)]\n pub kids: Vec,\n pub r#type: String,\n}\n\npub async fn get_story(id: i64) -> Result {\n let url = format!(\"{}{}{}.json\", BASE_API_URL, ITEM_API, id);\n let mut story = reqwest::get(&url).await?.json::().await?;\n let comment_futures = story.item.kids.iter().map(|&id| get_comment(id));\n let comments = join_all(comment_futures)\n .await\n .into_iter()\n .filter_map(|c| c.ok())\n .collect();\n\n story.comments = comments;\n Ok(story)\n}\n\n#[async_recursion::async_recursion(?Send)]\npub async fn get_comment_with_depth(id: i64, depth: i64) -> Result {\n let url = format!(\"{}{}{}.json\", BASE_API_URL, ITEM_API, id);\n let mut comment = reqwest::get(&url).await?.json::().await?;\n if depth > 0 {\n let sub_comments_futures = comment\n .kids\n .iter()\n .map(|story_id| get_comment_with_depth(*story_id, depth - 1));\n comment.sub_comments = join_all(sub_comments_futures)\n .await\n .into_iter()\n .filter_map(|c| c.ok())\n .collect();\n }\n Ok(comment)\n}\n\npub async fn get_comment(comment_id: i64) -> Result {\n get_comment_with_depth(comment_id, COMMENT_DEPTH).await\n}\n\n````" + } + 12usize => { + "# Fetching Data\n\nIn this chapter, we will fetch data from the hacker news API and use it to render the list of top posts in our application.\n\n## Defining the API\n\nFirst we need to create some utilities to fetch data from the hackernews API using [reqwest](https://docs.rs/reqwest/latest/reqwest/index.html):\n\n````rust@hackernews_async.rs\n// Define the Hackernews API\nuse futures::future::join_all;\n\npub static BASE_API_URL: &str = \"https://hacker-news.firebaseio.com/v0/\";\npub static ITEM_API: &str = \"item/\";\npub static USER_API: &str = \"user/\";\nconst COMMENT_DEPTH: i64 = 2;\n\npub async fn get_story_preview(id: i64) -> Result {\n let url = format!(\"{}{}{}.json\", BASE_API_URL, ITEM_API, id);\n reqwest::get(&url).await?.json().await\n}\n\npub async fn get_stories(count: usize) -> Result, reqwest::Error> {\n let url = format!(\"{}topstories.json\", BASE_API_URL);\n let stories_ids = &reqwest::get(&url).await?.json::>().await?[..count];\n\n let story_futures = stories_ids[..usize::min(stories_ids.len(), count)]\n .iter()\n .map(|&story_id| get_story_preview(story_id));\n let stories = join_all(story_futures)\n .await\n .into_iter()\n .filter_map(|story| story.ok())\n .collect();\n Ok(stories)\n}\n\npub async fn get_story(id: i64) -> Result {\n let url = format!(\"{}{}{}.json\", BASE_API_URL, ITEM_API, id);\n let mut story = reqwest::get(&url).await?.json::().await?;\n let comment_futures = story.item.kids.iter().map(|&id| get_comment(id));\n let comments = join_all(comment_futures)\n .await\n .into_iter()\n .filter_map(|c| c.ok())\n .collect();\n\n story.comments = comments;\n Ok(story)\n}\n\n#[async_recursion::async_recursion(?Send)]\npub async fn get_comment_with_depth(id: i64, depth: i64) -> Result {\n let url = format!(\"{}{}{}.json\", BASE_API_URL, ITEM_API, id);\n let mut comment = reqwest::get(&url).await?.json::().await?;\n if depth > 0 {\n let sub_comments_futures = comment\n .kids\n .iter()\n .map(|story_id| get_comment_with_depth(*story_id, depth - 1));\n comment.sub_comments = join_all(sub_comments_futures)\n .await\n .into_iter()\n .filter_map(|c| c.ok())\n .collect();\n }\n Ok(comment)\n}\n\npub async fn get_comment(comment_id: i64) -> Result {\n let comment = get_comment_with_depth(comment_id, COMMENT_DEPTH).await?;\n Ok(comment)\n}\n````\n\n## Working with Async\n\n[`use_future`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_future.html) is a [hook](./state.md) that lets you run an async closure, and provides you with its result.\n\nFor example, we can make an API request (using [reqwest](https://docs.rs/reqwest/latest/reqwest/index.html)) inside `use_future`:\n\n````rust@hackernews_async.rs\nfn Stories(cx: Scope) -> Element {\n // Fetch the top 10 stories on Hackernews\n let stories = use_future(cx, (), |_| get_stories(10));\n\n // check if the future is resolved\n match stories.value() {\n Some(Ok(list)) => {\n // if it is, render the stories\n render! {\n div {\n // iterate over the stories with a for loop\n for story in list {\n // render every story with the StoryListing component\n StoryListing { story: story.clone() }\n }\n }\n }\n }\n Some(Err(err)) => {\n // if there was an error, render the error\n render! {\"An error occurred while fetching stories {err}\"}\n }\n None => {\n // if the future is not resolved yet, render a loading message\n render! {\"Loading items\"}\n }\n }\n}\n````\n\nThe code inside `use_future` will be submitted to the Dioxus scheduler once the component has rendered.\n\nWe 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.\n\nWe can then render the result by looping over each of the posts and rendering them with the `StoryListing` component.\n\n````inject-dioxus\nDemoFrame {\n\thackernews_async::fetch::App {}\n}\n````\n\n > \n > You can read more about working with Async in Dioxus in the [Async reference](../reference/index.md)\n\n## Lazily Fetching Data\n\nFinally, we will lazily fetch the comments on each post as the user hovers over the post.\n\nWe need to revisit the code that handles hovering over an item. Instead of passing an empty list of comments, we can fetch all the related comments when the user hovers over the item.\n\nWe will cache the list of comments with a [use_ref](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_ref.html) hook. This hook allows you to store some state in a single component. When the user triggers fetching the comments we will check if the response has already been cached before fetching the data from the hackernews API.\n\n````rust@hackernews_async.rs\n// New\nasync fn resolve_story(\n full_story: UseRef>,\n preview_state: UseSharedState,\n story_id: i64,\n) {\n if let Some(cached) = &*full_story.read() {\n *preview_state.write() = PreviewState::Loaded(cached.clone());\n return;\n }\n\n *preview_state.write() = PreviewState::Loading;\n if let Ok(story) = get_story(story_id).await {\n *preview_state.write() = PreviewState::Loaded(story.clone());\n *full_story.write() = Some(story);\n }\n}\n\n#[component]\nfn StoryListing(cx: Scope, story: StoryItem) -> Element {\n let preview_state = use_shared_state::(cx).unwrap();\n let StoryItem {\n title,\n url,\n by,\n score,\n time,\n kids,\n id,\n ..\n } = story;\n // New\n let full_story = use_ref(cx, || None);\n\n let url = url.as_deref().unwrap_or_default();\n let hostname = url\n .trim_start_matches(\"https://\")\n .trim_start_matches(\"http://\")\n .trim_start_matches(\"www.\");\n let score = format!(\"{score} {}\", if *score == 1 { \" point\" } else { \" points\" });\n let comments = format!(\n \"{} {}\",\n kids.len(),\n if kids.len() == 1 {\n \" comment\"\n } else {\n \" comments\"\n }\n );\n let time = time.format(\"%D %l:%M %p\");\n\n cx.render(rsx! {\n div {\n padding: \"0.5rem\",\n position: \"relative\",\n onmouseenter: move |_event| {\n // New\n // If you return a future from an event handler, it will be run automatically\n resolve_story(full_story.clone(), preview_state.clone(), *id)\n },\n div {\n font_size: \"1.5rem\",\n a {\n href: url,\n onfocus: move |_event| {\n // New\n resolve_story(full_story.clone(), preview_state.clone(), *id)\n },\n // ...\n\n````\n\n````inject-dioxus\nDemoFrame {\n\thackernews_async::App {}\n}\n````" + } + 7usize => { + "# Mobile App\n\nBuild a mobile app with Dioxus!\n\nExample: [Todo App](https://github.com/DioxusLabs/example-projects/blob/master/ios_demo)\n\n## Support\n\nMobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with either the platform's WebView or experimentally with [WGPU](https://github.com/DioxusLabs/blitz). WebView doesn't support animations, transparency, and native widgets.\n\nMobile 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.\n\n## Getting Set up\n\nGetting set up with mobile can be quite challenging. The tooling here isn't great (yet) and might take some hacking around to get things working.\n\n### Setting up dependencies\n\n#### Android Dependencies\n\nFirst, install the rust Android targets:\n\n````sh\nrustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android\n````\n\nTo develop on Android, you will need to [install Android Studio](https://developer.android.com/studio).\n\nOnce you have installed Android Studio, you will need to install the Android SDK and NDK:\n\n1. Create a blank Android project\n1. Select `Tools > SDK manager`\n1. Navigate to the `SDK tools` window:\n\n![NDK install window](/assets/static/android_ndk_install.png)\n\nThen select:\n\n* The SDK\n* The SDK Command line tools\n* The NDK (side by side)\n* CMAKE\n\n4. Select `apply` and follow the prompts\n\n > \n > More details that could be useful for debugging any errors you encounter are available [in the official android docs](https://developer.android.com/studio/intro/update#sdk-manager)\n\nNext set the Java, Android and NDK home variables:\n\nMac:\n\n````sh\nexport JAVA_HOME=\"/Applications/Android Studio.app/Contents/jbr/Contents/Home\"\nexport ANDROID_HOME=\"$HOME/Library/Android/sdk\"\nexport NDK_HOME=\"$ANDROID_HOME/ndk/25.2.9519653\"\n````\n\nWindows:\n\n````powershell\n[System.Environment]::SetEnvironmentVariable(\"JAVA_HOME\", \"C:\\Program Files\\Android\\Android Studio\\jbr\", \"User\")\n[System.Environment]::SetEnvironmentVariable(\"ANDROID_HOME\", \"$env:LocalAppData\\Android\\Sdk\", \"User\")\n[System.Environment]::SetEnvironmentVariable(\"NDK_HOME\", \"$env:LocalAppData\\Android\\Sdk\\ndk\\25.2.9519653\", \"User\")\n````\n\n > \n > The NDK version in the paths should match the version you installed in the last step\n\n#### IOS Dependencies\n\nFirst, install the rust IOS targets:\n\n````sh\nrustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim\n````\n\nTo develop on IOS, you will need to [install XCode](https://apps.apple.com/us/app/xcode/id497799835).\n\n > \n > Note: On Apple silicon you must run Xcode on rosetta. Goto Application > Right Click Xcode > Get Info > Open in Rosetta.\n > If you are using M1, you will have to run `cargo build --target x86_64-apple-ios` instead of `cargo apple build` if you want to run in simulator.\n\n### Setting up your project\n\nFirst, we need to create a rust project:\n\n````sh\ncargo new dioxus-mobile-test\ncd dioxus-mobile-test\n````\n\nNext, we can use `cargo-mobile2` to create a project for mobile:\n\n````shell\ncargo install --git https://github.com/tauri-apps/cargo-mobile2\ncargo mobile init\n````\n\nWhen you run `cargo mobile init`, you will be asked a series of questions about your project. One of those questions is what template you should use. Dioxus currently doesn't have a template in Tauri mobile, instead you can use the `wry` template.\n\n > \n > You may also be asked to input your team ID for IOS. You can find your team id [here](https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/) or create a team id by creating a developer account [here](https://developer.apple.com/help/account/get-started/about-your-developer-account)\n\nNext, we need to modify our dependencies to include dioxus:\n\n````sh\ncargo add dioxus\ncargo add dioxus-desktop --no-default-features --features tokio_runtime\n````\n\nFinally, we need to add a component to renderer. Modify your main function:\n\n````rust\nuse dioxus::prelude::*;\n\npub fn main() -> Result<()> {\n // Right now we're going through dioxus-desktop but we'd like to go through dioxus-mobile\n // That will seed the index.html with some fixes that prevent the page from scrolling/zooming etc\n dioxus_desktop::launch_cfg(\n app,\n // Note that we have to disable the viewport goofiness of the browser.\n // Dioxus_mobile should do this for us\n dioxus_desktop::Config::default().with_custom_index(r#\"\n \n \n Dioxus app\n \n \n \n \n
\n \n \n \n \"#.into()),\n );\n\n Ok(())\n}\n\nfn app(cx: Scope) -> Element {\n let items = cx.use_hook(|| vec![1, 2, 3]);\n\n log::debug!(\"Hello from the app\");\n\n render! {\n div {\n h1 { \"Hello, Mobile\"}\n div { margin_left: \"auto\", margin_right: \"auto\", width: \"200px\", padding: \"10px\", border: \"1px solid black\",\n button {\n onclick: move|_| {\n println!(\"Clicked!\");\n items.push(items.len());\n cx.needs_update_any(ScopeId(0));\n println!(\"Requested update\");\n },\n \"Add item\"\n }\n for item in items.iter() {\n div { \"- {item}\" }\n }\n }\n }\n }\n}\n````\n\n## Running\n\nFrom there, you'll want to get a build of the crate using whichever platform you're targeting (simulator or actual hardware). For now, we'll just stick with the simulator\n\n### IOS\n\nTo build your project for IOS, you can run:\n\n````sh\ncargo build --target aarch64-apple-ios-sim\n````\n\nNext, open XCode (this might take awhile if you've never opened XCode before):\n\n````sh\ncargo apple open\n````\n\nThis will open XCode with this particular project.\n\nFrom there, just click the \"play\" button with the right target and the app should be running!\n\n![ios_demo](/assets/static/IOS-dioxus-demo.png)\n\nNote that clicking play doesn't cause a new build, so you'll need to keep rebuilding the app between changes. The tooling here is very young, so please be patient. If you want to contribute to make things easier, please do! We'll be happy to help.\n\n### Android\n\nTo build your project on Android you can run:\n`cargo android build`\n\nNext, open Android studio:\n\n````sh\ncargo android open\n````\n\nThis will open an android studio project for this application.\n\nNext we need to create a simulator in Android studio to run our app in. To create a simulator click on the phone icon in the top right of Android studio:\n\n![android studio manage devices](/assets/static/android-studio-simulator.png)\n\nThen click the `create a virtual device` button and follow the prompts:\n\n![android studio devices](/assets/static/android-studio-devices.png)\n\nFinally, launch your device by clicking the play button on the device you created:\n\n![android studio device](/assets/static/android-studio-device.png)\n\nNow you can start your application from your terminal by running:\n\n````sh\ncargo android run\n````\n\n![android_demo](/assets/static/Android-Dioxus-demo.png)\n\n > \n > More information is available in the Android docs:\n > \n > * https://developer.android.com/ndk/guides\n > * https://developer.android.com/studio/projects/install-ndk\n > * https://source.android.com/docs/setup/build/rust/building-rust-modules/overview" + } + 18usize => { + "# Event Handlers\n\nEvent handlers are used to respond to user actions. For example, an event handler could be triggered when the user clicks, scrolls, moves the mouse, or types a character.\n\nEvent handlers are attached to elements. For example, we usually don't care about all the clicks that happen within an app, only those on a particular button.\n\nEvent 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 it listens for is triggered and will be passed that event.\n\nFor example, to handle clicks on an element, we can specify an `onclick` handler:\n\n````rust, no_run@event_click.rs\ncx.render(rsx! {\n button {\n onclick: move |event| log::info!(\"Clicked! Event: {event:?}\"),\n \"click me!\"\n }\n})\n````\n\n````inject-dioxus\nDemoFrame {\n event_click::App {}\n}\n````\n\n## The Event object\n\nEvent handlers receive an [`Event`](https://docs.rs/dioxus-core/latest/dioxus_core/struct.Event.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.\n\nIn the example above, this event data was logged to the terminal:\n\n````\nClicked! Event: UiEvent { bubble_state: Cell { value: true }, data: MouseData { coordinates: Coordinates { screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } }\nClicked! Event: UiEvent { bubble_state: Cell { value: true }, data: MouseData { coordinates: Coordinates { screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } }\n````\n\nTo learn what the different event types for HTML provide, read the [events module docs](https://docs.rs/dioxus-html/latest/dioxus_html/events/index.html).\n\n### Event propagation\n\nSome events will trigger first on the element the event originated at upward. For example, a click event on a `button` inside a `div` would first trigger the button's event listener and then the div's event listener.\n\n > \n > For more information about event propagation see [the mdn docs on event bubbling](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling)\n\nIf you want to prevent this behavior, you can call `stop_propagation()` on the event:\n\n````rust, no_run@event_nested.rs\ncx.render(rsx! {\n div {\n onclick: move |_event| {},\n \"outer\",\n button {\n onclick: move |event| {\n // now, outer won't be triggered\n event.stop_propagation();\n },\n \"inner\"\n }\n }\n})\n````\n\n## Prevent Default\n\nSome events have a default behavior. For keyboard events, this might be entering the typed character. For mouse events, this might be selecting some text.\n\nIn 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 can be used for multiple handlers using their name separated by spaces:\n\n````rust, no_run@event_prevent_default.rs\ncx.render(rsx! {\n a {\n href: \"https://example.com\",\n prevent_default: \"onclick\",\n onclick: |_| log::info!(\"link clicked\"),\n \"example.com\",\n }\n})\n````\n\n````inject-dioxus\nDemoFrame {\n event_prevent_default::App {}\n}\n````\n\nAny event handlers will still be called.\n\n > \n > 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 based on the data in the event.\n\n## Handler Props\n\nSometimes, 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:\n\n````rust, no_run@event_handler_prop.rs\n#[derive(Props)]\npub struct FancyButtonProps<'a> {\n on_click: EventHandler<'a, MouseEvent>,\n}\n\npub fn FancyButton<'a>(cx: Scope<'a, FancyButtonProps<'a>>) -> Element<'a> {\n cx.render(rsx!(button {\n class: \"fancy-button\",\n onclick: move |evt| cx.props.on_click.call(evt),\n \"click me pls.\"\n }))\n}\n````\n\nThen, you can use it like any other handler:\n\n````rust, no_run@event_handler_prop.rs\ncx.render(rsx! {\n FancyButton {\n on_click: move |event| println!(\"Clicked! {event:?}\")\n }\n})\n````\n\n > \n > 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.\n\n## Custom Data\n\nEvent Handlers are generic over any type, so you can pass in any data you want to them, e.g:\n\n````rust, no_run@event_handler_prop.rs\nstruct ComplexData(i32);\n\n#[derive(Props)]\npub struct CustomFancyButtonProps<'a> {\n on_click: EventHandler<'a, ComplexData>,\n}\n\npub fn CustomFancyButton<'a>(cx: Scope<'a, CustomFancyButtonProps<'a>>) -> Element<'a> {\n cx.render(rsx!(button {\n class: \"fancy-button\",\n onclick: move |_| cx.props.on_click.call(ComplexData(0)),\n \"click me pls.\"\n }))\n}\n````" + } + 5usize => { + "# Fullstack\n\n > \n > This guide assumes you read the [Web](wasm.md) getting started guide and installed the [Dioxus-cli](https://github.com/DioxusLabs/dioxus/tree/master/packages/cli)\n\n# Getting Started\n\n## Setup\n\nFor this guide, we're going to show how to use Dioxus with [Axum](https://docs.rs/axum/latest/axum/), but `dioxus-fullstack` also integrates with the [Warp](https://docs.rs/warp/latest/warp/) and [Salvo](https://docs.rs/salvo/latest/salvo/) web frameworks.\n\nMake sure you have Rust and Cargo installed, and then create a new project:\n\n````shell\ncargo new --bin demo\ncd demo\n````\n\nAdd `dioxus` and `dioxus-fullstack` as dependencies:\n\n````shell\ncargo add dioxus\ncargo add dioxus-fullstack\n````\n\nNext, set up features for the server (`ssr`) and the client (`web`):\n\n````toml\n[features]\ndefault = []\nssr = [\"dioxus-fullstack/axum\"]\nweb = [\"dioxus-fullstack/web\"]\n````\n\nYour dependencies should look roughly like this:\n\n````toml\n[dependencies]\ndioxus = { version = \"*\" }\ndioxus-fullstack = { version = \"*\" }\n\n[features]\ndefault = []\nssr = [\"dioxus-fullstack/axum\"]\nweb = [\"dioxus-fullstack/web\"]\n````\n\nNow, set up your Axum app to serve the Dioxus app.\n\n````rust@server_basic.rs\n#![allow(non_snake_case, unused)]\nuse dioxus::prelude::*;\nuse dioxus_fullstack::prelude::*;\n\nfn main() {\n LaunchBuilder::new(app).launch();\n}\n\nfn app(cx: Scope) -> Element {\n let mut count = use_state(cx, || 0);\n\n cx.render(rsx! {\n h1 { \"High-Five counter: {count}\" }\n button { onclick: move |_| count += 1, \"Up high!\" }\n button { onclick: move |_| count -= 1, \"Down low!\" }\n })\n}\n\n````\n\nNow, run your app with:\n\n````\ndx build --features web --release\ncargo run --features ssr --release\n````\n\nFinally, open `http://localhost:8080` in your browser. You should see a server-side rendered page with a counter.\n\n````inject-dioxus\nSandBoxFrame {\n\turl: \"https://codesandbox.io/p/sandbox/dioxus-fullstack-2nwsrz?file=%2Fsrc%2Fmain.rs%3A5%2C1\"\n}\n````\n\n## Hot Reload\n\n1. Hot reloading allows much faster iteration times inside of rsx calls by interpreting them and streaming the edits.\n1. It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program.\n\n### Usage\n\n1. Run:\n\n````bash\ndx build --features web\ndx serve --features ssr --hot-reload --platform desktop\n````\n\n2. Change some code within a rsx or render macro\n2. Save and watch the style change without recompiling\n\n### Limitations\n\n1. The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will require a full recompile to capture the expression.\n1. Components, Iterators, and some attributes can contain arbitrary rust code and will trigger a full recompile when changed." + } + 17usize => { + "# Component Props\n\nJust like you can pass arguments to a function or attributes to an element, 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.\n\n## derive(Props)\n\nComponent 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`.\n\nThere are 2 flavors of Props structs:\n\n* Owned props:\n * Don't have an associated lifetime\n * Implement `PartialEq`, allow for memoization (if the props don't change, Dioxus won't re-render the component)\n* Borrowed props:\n * [Borrow](https://doc.rust-lang.org/beta/rust-by-example/scope/borrow.html) from a parent component\n * Cannot be memoized due to lifetime constraints\n\n### Owned Props\n\nOwned Props are very simple – they don't borrow anything. Example:\n\n````rust, no_run@component_owned_props.rs\n// Remember: Owned props must implement `PartialEq`!\n#[derive(PartialEq, Props)]\nstruct LikesProps {\n score: i32,\n}\n\nfn Likes(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n \"This post has \",\n b { \"{cx.props.score}\" },\n \" likes\"\n }\n })\n}\n````\n\nYou can then pass prop values to the component the same way you would pass attributes to an element:\n\n````rust, no_run@component_owned_props.rs\npub fn App(cx: Scope) -> Element {\n cx.render(rsx! {\n Likes {\n score: 42,\n },\n })\n}\n````\n\n````inject-dioxus\nDemoFrame {\n component_owned_props::App {}\n}\n````\n\n### Borrowed Props\n\nOwned props work 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.\n\nRust allows for something more efficient – borrowing the String as a `&str` – this is what Borrowed Props are for!\n\n````rust, no_run@component_borrowed_props.rs\n#[derive(Props)]\nstruct TitleCardProps<'a> {\n title: &'a str,\n}\n\nfn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element {\n cx.render(rsx! {\n h1 { \"{cx.props.title}\" }\n })\n}\n````\n\nWe can then use the component like this:\n\n````rust, no_run@component_borrowed_props.rs\npub fn App(cx: Scope) -> Element {\n let hello = \"Hello Dioxus!\";\n\n cx.render(rsx!(TitleCard { title: hello }))\n}\n````\n\n````inject-dioxus\nDemoFrame {\n // original: component_borrowed_props::App {}\n __interactive_04::component_borrowed_props {}\n}\n````\n\nBorrowed props can be very useful, but they do not allow for memorization so they will *always* rerun when the parent scope is rerendered. Because of this Borrowed Props should be reserved for components that are cheap to rerun or places where cloning data is an issue. Using Borrowed Props everywhere will result in large parts of your app rerunning every interaction.\n\n## Prop Options\n\nThe `#[derive(Props)]` macro has some features that let you customize the behavior of props.\n\n### Optional Props\n\nYou can create optional fields by using the `Option<…>` type for a field:\n\n````rust, no_run@component_props_options.rs\n#[derive(Props)]\nstruct OptionalProps<'a> {\n title: &'a str,\n subtitle: Option<&'a str>,\n}\n\nfn Title<'a>(cx: Scope<'a, OptionalProps>) -> Element<'a> {\n cx.render(rsx!(h1{\n \"{cx.props.title}: \",\n cx.props.subtitle.unwrap_or(\"No subtitle provided\"),\n }))\n}\n````\n\nThen, you can choose to either provide them or not:\n\n````rust, no_run@component_props_options.rs\nTitle {\ntitle: \"Some Title\",\n},\nTitle {\ntitle: \"Some Title\",\nsubtitle: \"Some Subtitle\",\n},\n// Providing an Option explicitly won't compile though:\n// Title {\n// title: \"Some Title\",\n// subtitle: None,\n// },\n````\n\n### Explicitly Required `Option`s\n\nIf you want to explicitly require an `Option`, and not an optional prop, you can annotate it with `#[props(!optional)]`:\n\n````rust, no_run@component_props_options.rs\n#[derive(Props)]\nstruct ExplicitOptionProps<'a> {\n title: &'a str,\n #[props(!optional)]\n subtitle: Option<&'a str>,\n}\n\nfn ExplicitOption<'a>(cx: Scope<'a, ExplicitOptionProps>) -> Element<'a> {\n cx.render(rsx!(h1 {\n \"{cx.props.title}: \",\n cx.props.subtitle.unwrap_or(\"No subtitle provided\"),\n }))\n}\n````\n\nThen, you have to explicitly pass either `Some(\"str\")` or `None`:\n\n````rust, no_run@component_props_options.rs\nExplicitOption {\ntitle: \"Some Title\",\nsubtitle: None,\n},\nExplicitOption {\ntitle: \"Some Title\",\nsubtitle: Some(\"Some Title\"),\n},\n// This won't compile:\n// ExplicitOption {\n// title: \"Some Title\",\n// },\n````\n\n### Default Props\n\nYou can use `#[props(default = 42)]` to make a field optional and specify its default value:\n\n````rust, no_run@component_props_options.rs\n#[derive(PartialEq, Props)]\nstruct DefaultProps {\n // default to 42 when not provided\n #[props(default = 42)]\n number: i64,\n}\n\nfn DefaultComponent(cx: Scope) -> Element {\n cx.render(rsx!(h1 { \"{cx.props.number}\" }))\n}\n````\n\nThen, similarly to optional props, you don't have to provide it:\n\n````rust, no_run@component_props_options.rs\nDefaultComponent {\nnumber: 5,\n},\nDefaultComponent {},\n````\n\n### Automatic Conversion with `.into`\n\nIt 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`:\n\n````rust, no_run@component_props_options.rs\n#[derive(PartialEq, Props)]\nstruct IntoProps {\n #[props(into)]\n string: String,\n}\n\nfn IntoComponent(cx: Scope) -> Element {\n cx.render(rsx!(h1 { \"{cx.props.string}\" }))\n}\n````\n\nThen, you can use it so:\n\n````rust, no_run@component_props_options.rs\nIntoComponent {\nstring: \"some &str\",\n},\n````\n\n## The component macro\n\nSo 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!\n\n`component` allows you to do just that. Instead of typing the \"full\" version:\n\n````rust, no_run\n#[derive(Props, PartialEq)]\nstruct TitleCardProps {\n title: String,\n}\n\nfn TitleCard(cx: Scope) -> Element {\n cx.render(rsx!{\n h1 { \"{cx.props.title}\" }\n })\n}\n````\n\n...you can define a function that accepts props as arguments. Then, just annotate it with `#[component]`, and the macro will turn it into a regular Component for you:\n\n````rust, no_run\n#[component]\nfn TitleCard(cx: Scope, title: String) -> Element {\n cx.render(rsx!{\n h1 { \"{title}\" }\n })\n}\n````\n\n > \n > 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.\n\n## Component Children\n\nIn 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`:\n\n````rust, no_run@component_element_props.rs\n#[derive(Props)]\nstruct ClickableProps<'a> {\n href: &'a str,\n body: Element<'a>,\n}\n\nfn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {\n cx.render(rsx!(\n a {\n href: \"{cx.props.href}\",\n class: \"fancy-button\",\n &cx.props.body\n }\n ))\n}\n````\n\nThen, when rendering the component, you can pass in the output of `cx.render(rsx!(...))`:\n\n````rust, no_run@component_element_props.rs\ncx.render(rsx! {\n Clickable {\n href: \"https://www.youtube.com/watch?v=C-M2hs3sXGo\",\n body: cx.render(rsx!(\"How to \" i {\"not\"} \" be seen\")),\n }\n})\n````\n\n > \n > Note: Since `Element<'a>` is a borrowed prop, there will be no memoization.\n\n > \n > Warning: While it may compile, do not include the same `Element` more than once in the RSX. The resulting behavior is unspecified.\n\n### The children field\n\nRather 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:\n\n````rust, no_run@component_children.rs\n#[derive(Props)]\nstruct ClickableProps<'a> {\n href: &'a str,\n children: Element<'a>,\n}\n\nfn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {\n cx.render(rsx!(\n a {\n href: \"{cx.props.href}\",\n class: \"fancy-button\",\n &cx.props.children\n }\n ))\n}\n````\n\nThis 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!\n\n````rust, no_run@component_children.rs\ncx.render(rsx! {\n Clickable {\n href: \"https://www.youtube.com/watch?v=C-M2hs3sXGo\",\n \"How to \" i {\"not\"} \" be seen\"\n }\n})\n````\n\n````inject-dioxus\nDemoFrame {\n component_children::App {}\n}\n````" + } + 36usize => { + "# Routing\n\nYou can easily integrate your fullstack application with a client side router using the `launch_router` macro. The `launch_router` macro works the same as the `launch` macro except it accepts a Router instead of a Component:\n\n````rust@server_router.rs\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\nuse dioxus_fullstack::prelude::*;\nuse dioxus_router::prelude::*;\nuse serde::{Deserialize, Serialize};\n\nfn main() {\n let config = LaunchBuilder::>::router();\n #[cfg(feature = \"ssr\")]\n config\n .incremental(\n IncrementalRendererConfig::default()\n .invalidate_after(std::time::Duration::from_secs(120)),\n )\n .launch();\n\n #[cfg(not(feature = \"ssr\"))]\n config.launch();\n}\n\n#[derive(Clone, Routable, Debug, PartialEq, Serialize, Deserialize)]\nenum Route {\n #[route(\"/\")]\n Home {},\n #[route(\"/blog/:id\")]\n Blog { id: i32 },\n}\n\n#[component]\nfn Blog(cx: Scope, id: i32) -> Element {\n render! {\n Link { to: Route::Home {}, \"Go to counter\" }\n table {\n tbody {\n for _ in 0..*id {\n tr {\n for _ in 0..*id {\n td { \"hello world!\" }\n }\n }\n }\n }\n }\n }\n}\n\n#[component]\nfn Home(cx: Scope) -> Element {\n let mut count = use_state(cx, || 0);\n let text = use_state(cx, || \"...\".to_string());\n\n cx.render(rsx! {\n Link {\n to: Route::Blog {\n id: *count.get()\n },\n \"Go to blog\"\n }\n div {\n h1 { \"High-Five counter: {count}\" }\n button { onclick: move |_| count += 1, \"Up high!\" }\n button { onclick: move |_| count -= 1, \"Down low!\" }\n button {\n onclick: move |_| {\n to_owned![text];\n async move {\n if let Ok(data) = get_server_data().await {\n println!(\"Client received: {}\", data);\n text.set(data.clone());\n post_server_data(data).await.unwrap();\n }\n }\n },\n \"Run server function!\"\n }\n \"Server said: {text}\"\n }\n })\n}\n\n#[server(PostServerData)]\nasync fn post_server_data(data: String) -> Result<(), ServerFnError> {\n println!(\"Server received: {}\", data);\n\n Ok(())\n}\n\n#[server(GetServerData)]\nasync fn get_server_data() -> Result {\n Ok(\"Hello from the server!\".to_string())\n}\n\n````\n\n````inject-dioxus\nSandBoxFrame {\n\turl: \"https://codesandbox.io/p/sandbox/dioxus-fullstack-router-s75v5q?file=%2Fsrc%2Fmain.rs%3A7%2C1\"\n}\n````" + } + 29usize => { + "# Server-Side Rendering\n\nFor lower-level control over the rendering process, you can use the `dioxus-ssr` crate directly. This can be useful when integrating with a web framework that `dioxus-fullstack` does not support, or pre-rendering pages.\n\n## Setup\n\nFor this guide, we're going to show how to use Dioxus SSR with [Axum](https://docs.rs/axum/latest/axum/).\n\nMake sure you have Rust and Cargo installed, and then create a new project:\n\n````shell\ncargo new --bin demo\ncd demo\n````\n\nAdd Dioxus and the ssr renderer as dependencies:\n\n````shell\ncargo add dioxus\ncargo add dioxus-ssr\n````\n\nNext, add all the Axum dependencies. This will be different if you're using a different Web Framework\n\n````\ncargo add tokio --features full\ncargo add axum\n````\n\nYour dependencies should look roughly like this:\n\n````toml\n[dependencies]\naxum = \"0.4.5\"\ndioxus = { version = \"*\" }\ndioxus-ssr = { version = \"*\" }\ntokio = { version = \"1.15.0\", features = [\"full\"] }\n````\n\nNow, set up your Axum app to respond on an endpoint.\n\n````rust\nuse axum::{response::Html, routing::get, Router};\nuse dioxus::prelude::*;\n\n#[tokio::main]\nasync fn main() {\n\tlet addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));\n\tprintln!(\"listening on http://{}\", addr);\n\n\taxum::Server::bind(&addr)\n\t\t.serve(\n\t\t\tRouter::new()\n\t\t\t\t.route(\"/\", get(app_endpoint))\n\t\t\t\t.into_make_service(),\n\t\t)\n\t\t.await\n\t\t.unwrap();\n}\n````\n\nAnd then add our endpoint. We can either render `rsx!` directly:\n\n````rust\nasync fn app_endpoint() -> Html {\n\t// render the rsx! macro to HTML\n\tHtml(dioxus_ssr::render_lazy(rsx! {\n\t\tdiv { \"hello world!\" }\n\t}))\n}\n````\n\nOr we can render VirtualDoms.\n\n````rust\nasync fn app_endpoint() -> Html {\n\t// create a component that renders a div with the text \"hello world\"\n\tfn app(cx: Scope) -> Element {\n\t\tcx.render(rsx!(div { \"hello world\" }))\n\t}\n\t// create a VirtualDom with the app component\n\tlet mut app = VirtualDom::new(app);\n\t// rebuild the VirtualDom before rendering\n\tlet _ = app.rebuild();\n\n\t// render the VirtualDom to HTML\n\tHtml(dioxus_ssr::render_vdom(&app))\n}\n````\n\n## Multithreaded Support\n\nThe Dioxus VirtualDom, sadly, is not currently `Send`. Internally, we use quite a bit of interior mutability which is not thread-safe.\nWhen 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 spawn a VirtualDom on its own thread and communicate with it via channels or create a pool of VirtualDoms.\nYou might notice that you cannot hold the VirtualDom across an await point. Because Dioxus is currently not ThreadSafe, it *must* remain on the thread it started. We are working on loosening this requirement." + } + 51usize => { + "# History Buttons\n\nSome platforms, like web browsers, provide users with an easy way to navigate\nthrough an app's history. They have UI elements or integrate with the OS.\n\nHowever, native platforms usually don't provide such amenities, which means that\napps wanting users to have access to them, need to implement them. For this\nreason, the router comes with two components, which emulate a browser's back and\nforward buttons:\n\n* [`GoBackButton`](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoBackButton.html)\n* [`GoForwardButton`](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoForwardButton.html)\n\n > \n > If you want to navigate through the history programmatically, take a look at\n > [`programmatic navigation`](./navigation/programmatic.md).\n\n````rust@history_buttons.rs\nfn HistoryNavigation(cx: Scope) -> Element {\n render! {\n GoBackButton {\n \"Back to the Past\"\n }\n GoForwardButton {\n \"Back to the Future\" /* You see what I did there? 😉 */\n }\n }\n}\n````\n\nAs you might know, browsers usually disable the back and forward buttons if\nthere is no history to navigate to. The router's history buttons try to do that\ntoo, but depending on the \\[history provider\\] that might not be possible.\n\nImportantly, neither \\[`WebHistory`\\] supports that feature.\nThis is due to limitations of the browser History API.\n\nHowever, in both cases, the router will just ignore button presses, if there is\nno history to navigate to.\n\nAlso, when using \\[`WebHistory`\\], the history buttons might\nnavigate a user to a history entry outside your app." + } + 64usize => { + "# Testing\n\nWhen building application or libraries with Dioxus, you may want to include some tests to check the behavior of parts of your application. This guide will teach you how to test different parts of your Dioxus application.\n\n## Component Testing\n\nYou can use a combination of [pretty-assertions](https://docs.rs/pretty_assertions/latest/pretty_assertions/) and [dioxus-ssr](https://docs.rs/dioxus-ssr/latest/dioxus_ssr/) to check that two snippets of rsx are equal:\n\n````rust@component_test.rs\nuse futures::FutureExt;\nuse std::{cell::RefCell, sync::Arc};\n\nuse dioxus::prelude::*;\n\n#[test]\nfn test() {\n assert_rsx_eq(\n rsx! {\n div {\n \"Hello world\"\n }\n div {\n \"Hello world\"\n }\n },\n rsx! {\n for _ in 0..2 {\n div {\n \"Hello world\"\n }\n }\n },\n )\n}\n\nfn assert_rsx_eq(first: LazyNodes<'static, 'static>, second: LazyNodes<'static, 'static>) {\n let first = dioxus_ssr::render_lazy(first);\n let second = dioxus_ssr::render_lazy(second);\n pretty_assertions::assert_str_eq!(first, second);\n}\n\n````\n\n## Hook Testing\n\nWhen creating libraries around Dioxus, it can be helpful to make tests for your [custom hooks](./state/custom_hooks/index.md).\n\nDioxus does not currently have a full hook testing library, but you can build a bespoke testing framework by manually driving the virtual dom.\n\n````rust@hook_test.rs\nuse futures::FutureExt;\nuse std::{cell::RefCell, sync::Arc};\n\nuse dioxus::prelude::*;\n\n#[test]\nfn test() {\n test_hook(\n |cx| use_ref(cx, || 0).clone(),\n |value, mut proxy| match proxy.generation {\n 0 => {\n value.set(1);\n }\n 1 => {\n assert_eq!(*value.read(), 1);\n value.set(2);\n }\n 2 => {\n proxy.rerun();\n }\n 3 => {}\n _ => todo!(),\n },\n |proxy| assert_eq!(proxy.generation, 4),\n );\n}\n\nfn test_hook(\n initialize: impl FnMut(&ScopeState) -> V + 'static,\n check: impl FnMut(V, MockProxy) + 'static,\n mut final_check: impl FnMut(MockProxy) + 'static,\n) {\n #[derive(Props)]\n struct MockAppComponent<\n I: FnMut(&ScopeState) -> V + 'static,\n C: FnMut(V, MockProxy) + 'static,\n V,\n > {\n hook: RefCell,\n check: RefCell,\n }\n\n impl V, C: FnMut(V, MockProxy), V> PartialEq\n for MockAppComponent\n {\n fn eq(&self, _: &Self) -> bool {\n true\n }\n }\n\n fn mock_app V, C: FnMut(V, MockProxy), V>(\n cx: Scope>,\n ) -> Element {\n let value = cx.props.hook.borrow_mut()(cx);\n\n cx.props.check.borrow_mut()(value, MockProxy::new(cx));\n\n render! {\n div {}\n }\n }\n\n let mut vdom = VirtualDom::new_with_props(\n mock_app,\n MockAppComponent {\n hook: RefCell::new(initialize),\n check: RefCell::new(check),\n },\n );\n\n let _ = vdom.rebuild();\n\n while vdom.wait_for_work().now_or_never().is_some() {\n let _ = vdom.render_immediate();\n }\n\n final_check(MockProxy::new(vdom.base_scope()));\n}\n\nstruct MockProxy {\n rerender: Arc,\n pub generation: usize,\n}\n\nimpl MockProxy {\n fn new(scope: &ScopeState) -> Self {\n let generation = scope.generation();\n let rerender = scope.schedule_update();\n\n Self {\n rerender,\n generation,\n }\n }\n\n pub fn rerun(&mut self) {\n (self.rerender)();\n }\n}\n\n````\n\n## End to End Testing\n\nYou can use [Playwright](https://playwright.dev/) to create end to end tests for your dioxus application.\n\nIn your `playwright.config.js`, you will need to run cargo run or dx serve instead of the default build command. Here is a snippet from the end to end web example:\n\n````js\n//...\nwebServer: [\n {\n cwd: path.join(process.cwd(), 'playwright-tests', 'web'),\n command: 'dx serve',\n port: 8080,\n timeout: 10 * 60 * 1000,\n reuseExistingServer: !process.env.CI,\n stdout: \"pipe\",\n },\n],\n````\n\n* [Web example](https://github.com/DioxusLabs/dioxus/tree/master/playwright-tests/web)\n* [Liveview example](https://github.com/DioxusLabs/dioxus/tree/master/playwright-tests/liveview)\n* [Fullstack example](https://github.com/DioxusLabs/dioxus/tree/master/playwright-tests/fullstack)" + } + 66usize => { + "# Tailwind\n\nYou can style your Dioxus application with whatever CSS framework you choose, or just write vanilla CSS.\n\nOne popular option for styling your Dioxus application is [Tailwind](https://tailwindcss.com/). Tailwind allows you to style your elements with CSS utility classes. This guide will show you how to setup tailwind CSS with your Dioxus application.\n\n## Setup\n\n1. Install the Dioxus CLI:\n \n ````bash\n cargo install --git https://github.com/DioxusLabs/cli\n ````\n\n1. Install npm: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm\n\n1. Install the tailwind css cli: https://tailwindcss.com/docs/installation\n\n1. Initialize the tailwind css project:\n \n ````bash\n npx tailwindcss init\n ````\n \n This should create a `tailwind.config.js` file in the root of the project.\n\n1. Edit the `tailwind.config.js` file to include rust files:\n \n ````js\n module.exports = {\n mode: \"all\",\n content: [\n // include all rust, html and css files in the src directory\n \"./src/**/*.{rs,html,css}\",\n // include all html files in the output (dist) directory\n \"./dist/**/*.html\",\n ],\n theme: {\n extend: {},\n },\n plugins: [],\n }\n ````\n\n1. Create a `input.css` file with the following content:\n \n ````css\n @tailwind base;\n @tailwind components;\n @tailwind utilities;\n ````\n\n1. Create a `Dioxus.toml` file with the following content that links to the `tailwind.css` file:\n \n ````toml\n [application]\n \n # App (Project) Name\n name = \"Tailwind CSS + Dioxus\"\n \n # Dioxus App Default Platform\n # desktop, web, mobile, ssr\n default_platform = \"web\"\n \n # `build` & `serve` dist path\n out_dir = \"dist\"\n \n # resource (public) file folder\n asset_dir = \"public\"\n \n [web.app]\n \n # HTML title tag content\n title = \"dioxus | ⛺\"\n \n [web.watcher]\n \n # when watcher trigger, regenerate the `index.html`\n reload_html = true\n \n # which files or dirs will be watcher monitoring\n watch_path = [\"src\", \"public\"]\n \n # uncomment line below if using Router\n # index_on_404 = true\n \n # include `assets` in web platform\n [web.resource]\n \n # CSS style file\n style = [\"/tailwind.css\"]\n \n # Javascript code file\n script = []\n \n [web.resource.dev]\n \n # serve: [dev-server] only\n \n # CSS style file\n style = []\n \n # Javascript code file\n script = []\n ````\n\n### Bonus Steps\n\n1. Install the tailwind css vs code extension\n\n1. Go to the settings for the extension and find the experimental regex support section. Edit the setting.json file to look like this:\n \n ````json\n \"tailwindCSS.experimental.classRegex\": [\"class: \\\"(.*)\\\"\"],\n \"tailwindCSS.includeLanguages\": {\n \"rust\": \"html\"\n },\n ````\n\n## Development\n\n* Run the following command in the root of the project to start the tailwind css compiler:\n \n ````bash\n npx tailwindcss -i ./input.css -o ./public/tailwind.css --watch\n ````\n\n### Web\n\n* Run the following command in the root of the project to start the dioxus dev server:\n \n ````bash\n dx serve --hot-reload\n ````\n\n* Open the browser to [http://localhost:8080](http://localhost:8080).\n\n### Desktop\n\n* Add a custom head pointing to the generated tailwind CSS file in your `main`. It looks like:\n \n ````rust\n dioxus_desktop::launch_cfg(\n App,\n dioxus_desktop::Config::new()\n .with_custom_head(r#\"\"#.to_string()))\n ````\n\n* Launch the dioxus desktop app:\n \n ````bash\n cargo run\n ````" + } + 42usize => { + "# Redirection Perfection\n\nYou're well on your way to becoming a routing master!\n\nIn this chapter, we will cover creating redirects\n\n## Creating Redirects\n\nA redirect is very simple. When dioxus encounters a redirect while finding out\nwhat components to render, it will redirect the user to the target of the\nredirect.\n\nAs a simple example, let's say you want user to still land on your blog, even\nif they used the path `/myblog` or `/myblog/:name`.\n\nRedirects are special attributes in the router enum that accept a route and a closure\nwith the route parameters. The closure should return a route to redirect to.\n\nLet's add a redirect to our router enum:\n\n````rust@full_example.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(NavBar)]\n #[route(\"/\")]\n Home {},\n #[nest(\"/blog\")]\n #[layout(Blog)]\n #[route(\"/\")]\n BlogList {},\n #[route(\"/post/:name\")]\n BlogPost { name: String },\n #[end_layout]\n #[end_nest]\n #[end_layout]\n #[nest(\"/myblog\")]\n #[redirect(\"/\", || Route::BlogList {})]\n #[redirect(\"/:name\", |name: String| Route::BlogPost { name })]\n #[end_nest]\n #[route(\"/:..route\")]\n PageNotFound {\n route: Vec,\n },\n}\n````\n\nThat's it! Now your users will be redirected to the blog.\n\n### Conclusion\n\nWell done! You've completed the Dioxus Router guide. You've built a small\napplication and learned about the many things you can do with Dioxus Router.\nTo continue your journey, you attempt a challenge listed below, look at the [router examples](https://github.com/DioxusLabs/dioxus/tree/master/packages/router/examples), or\ncan check out the [API reference](https://docs.rs/dioxus-router/).\n\n### Challenges\n\n* Organize your components into separate files for better maintainability.\n* Give your app some style if you haven't already.\n* Build an about page so your visitors know who you are.\n* Add a user system that uses URL parameters.\n* Create a simple admin system to create, delete, and edit blogs.\n* If you want to go to the max, hook up your application to a rest API and database." + } + 6usize => { + "# Desktop overview\n\nBuild a standalone native desktop app that looks and feels the same across operating systems.\n\nApps built with Dioxus desktop use the system WebView to render the page. This makes the final size of application much smaller than other WebView renderers (typically under 5MB).\n\nAlthough desktop apps are rendered in a WebView, your Rust code runs natively. 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 easily accessible though system APIs.\n\nDioxus desktop is built off [Tauri](https://tauri.app/). Right now there are limited Dioxus abstractions over the menubar, event handling, etc. In some places you may need to leverage Tauri directly – through [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao).\n\n > \n > In the future, we plan to move to a custom web renderer-based DOM renderer with WGPU integrations ([Blitz](https://github.com/DioxusLabs/blitz)).\n\n## Examples\n\n* [File Explorer](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer)\n* [WiFi Scanner](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner)\n\n[![File Explorer screenshot](https://github.com/DioxusLabs/example-projects/raw/master/file-explorer/assets/image.png)](https://github.com/DioxusLabs/example-projects/tree/master/file-explorer)\n\nHere's a [query](https://github.com/search?q=repo%3ADioxusLabs%2Fdioxus+path%3A%2F%5Eexamples%5C%2F%2F+%22use+dioxus_desktop%22&type=code) for the main repo to find examples which use `dioxus_desktop` (might not be 100% acurrate).\n\n# Getting started\n\n## Platform-specific dependencies\n\nDioxus desktop renders through a WebView. Depending on your platform, you might need to install some dependencies.\n\n### Windows\n\nWindows apps depend on WebView2 – a library that 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:\n\n1. A tiny \"evergreen\" *bootstrapper* that fetches an installer from Microsoft's CDN.\n1. A tiny *installer* that fetches WebView2 from Microsoft's CDN.\n1. A statically linked version of WebView2 in your final binary for offline users.\n\nFor development purposes, use Option 1.\n\n### Linux\n\nWebView Linux apps require WebkitGtk. When distributing, this can be part of your dependency tree in your `.rpm` or `.deb`. However, likely, your users will already have WebkitGtk.\n\n````bash\nsudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev\n````\n\nWhen using Debian/bullseye `libappindicator3-dev` is no longer available but replaced by `libayatana-appindicator3-dev`.\n\n````bash\n# on Debian/bullseye use:\nsudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev\n````\n\nIf you run into issues, make sure you have all the basics installed, as outlined in the [Tauri docs](https://beta.tauri.app/guides/prerequisites/).\n\n### MacOS\n\nCurrently – 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).\n\n## Creating a Project\n\nCreate a new crate:\n\n````shell\ncargo new --bin demo\ncd demo\n````\n\nAdd Dioxus and the desktop renderer as dependencies (this will edit your `Cargo.toml`):\n\n````shell\ncargo add dioxus\ncargo add dioxus-desktop\n````\n\nEdit your `main.rs`:\n\n````rust@hello_world_desktop.rs\n#![allow(non_snake_case)]\n// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types\nuse dioxus::prelude::*;\n\nfn main() {\n // launch the dioxus app in a webview\n dioxus_desktop::launch(App);\n}\n\n// define a component that renders a div with the text \"Hello, world!\"\nfn App(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n \"Hello, world!\"\n }\n })\n}\n````\n\n## Hot Reload\n\n1. Hot reloading allows much faster iteration times inside of RSX calls by interpreting them and streaming the edits.\n1. It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program.\n\n### Setup\n\nInstall [dioxus-cli](https://github.com/DioxusLabs/dioxus/tree/master/packages/cli).\n\n### Usage\n\n1. Run:\n\n````bash\ndx serve --hot-reload --platform desktop\n````\n\n2. Change some code within a `rsx` or `render` macro.\n2. Save and watch the style change without recompiling.\n\n### Limitations\n\n1. The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the RSX call, it will require a full recompile to capture the expression.\n1. Components, Iterators, and some attributes can contain arbitrary rust code and will trigger a full recompile when changed." + } + 26usize => { + "# Spawning Futures\n\nThe `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`:\n\n````rust@spawn.rs\nlet response = use_state(cx, || String::from(\"...\"));\n\nlet log_in = move |_| {\n cx.spawn({\n to_owned![response];\n\n async move {\n let resp = reqwest::Client::new()\n .get(\"https://dioxuslabs.com\")\n .send()\n .await;\n\n match resp {\n Ok(_data) => {\n log::info!(\"dioxuslabs.com responded!\");\n response.set(\"dioxuslabs.com responded!\".into());\n }\n Err(err) => {\n log::info!(\"Request failed with error: {err:?}\")\n }\n }\n }\n });\n};\n\nrender! {\n button {\n onclick: log_in,\n \"Response: {response}\",\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n spawn::App {}\n}\n````\n\n > \n > Note: `spawn` will always spawn a *new* future. You most likely don't want to call it on every render.\n\nCalling `spawn` will give you a `JoinHandle` which lets you cancel or pause the future.\n\n## Spawning Tokio Tasks\n\nSometimes, 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:\n\n````rust@spawn.rs\ncx.spawn(async {\n let _ = tokio::spawn(async {}).await;\n\n let _ = tokio::task::spawn_local(async {\n // some !Send work\n })\n .await;\n});\n````" + } + 43usize => { + "# Full Code\n\n````rust@full_example.rs\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n// ANCHOR: router\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(NavBar)]\n #[route(\"/\")]\n Home {},\n #[nest(\"/blog\")]\n #[layout(Blog)]\n #[route(\"/\")]\n BlogList {},\n #[route(\"/post/:name\")]\n BlogPost { name: String },\n #[end_layout]\n #[end_nest]\n #[end_layout]\n #[nest(\"/myblog\")]\n #[redirect(\"/\", || Route::BlogList {})]\n #[redirect(\"/:name\", |name: String| Route::BlogPost { name })]\n #[end_nest]\n #[route(\"/:..route\")]\n PageNotFound {\n route: Vec,\n },\n}\n// ANCHOR_END: router\n\npub fn App(cx: Scope) -> Element {\n render! {\n Router:: {}\n }\n}\n\n#[component]\nfn NavBar(cx: Scope) -> Element {\n render! {\n nav {\n ul {\n li { Link { to: Route::Home {}, \"Home\" } }\n li { Link { to: Route::BlogList {}, \"Blog\" } }\n }\n }\n Outlet:: {}\n }\n}\n\n#[component]\nfn Home(cx: Scope) -> Element {\n render! {\n h1 { \"Welcome to the Dioxus Blog!\" }\n }\n}\n\n#[component]\nfn Blog(cx: Scope) -> Element {\n render! {\n h1 { \"Blog\" }\n Outlet:: {}\n }\n}\n\n#[component]\nfn BlogList(cx: Scope) -> Element {\n render! {\n h2 { \"Choose a post\" }\n ul {\n li {\n Link {\n to: Route::BlogPost { name: \"Blog post 1\".into() },\n \"Read the first blog post\"\n }\n }\n li {\n Link {\n to: Route::BlogPost { name: \"Blog post 2\".into() },\n \"Read the second blog post\"\n }\n }\n }\n }\n}\n\n#[component]\nfn BlogPost(cx: Scope, name: String) -> Element {\n render! {\n h2 { \"Blog Post: {name}\"}\n }\n}\n\n#[component]\nfn PageNotFound(cx: Scope, route: Vec) -> Element {\n render! {\n h1 { \"Page not found\" }\n p { \"We are terribly sorry, but the page you requested doesn't exist.\" }\n pre {\n color: \"red\",\n \"log:\\nattemped to navigate to: {route:?}\"\n }\n }\n}\n\n````" + } + 58usize => { + "This section of the guide provides getting started guides for common tools used with Dioxus.\n\n* [Logging](./logging.md)\n* [Internationalization](./internationalization.md)" + } + 45usize => { + "# Defining Routes\n\nWhen creating a \\[`Routable`\\] enum, we can define routes for our application using the `route(\"path\")` attribute.\n\n## Route Segments\n\nEach route is made up of segments. Most segments are separated by `/` characters in the path.\n\nThere are four fundamental types of segments:\n\n1. [Static segments](#static-segments) are fixed strings that must be present in the path.\n1. [Dynamic segments](#dynamic-segments) are types that can be parsed from a segment.\n1. [Catch-all segments](#catch-all-segments) are types that can be parsed from multiple segments.\n1. [Query segments](#query-segments) are types that can be parsed from the query string.\n\nRoutes are matched:\n\n* First, from most specific to least specific (Static then Dynamic then Catch All) (Query is always matched)\n* Then, if multiple routes match the same path, the order in which they are defined in the enum is followed.\n\n## Static segments\n\nFixed routes match a specific path. For example, the route `#[route(\"/about\")]` will match the path `/about`.\n\n````rust@static_segments.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // Routes always start with a slash\n #[route(\"/\")]\n Home {},\n // You can have multiple segments in a route\n #[route(\"/hello/world\")]\n HelloWorld {},\n}\n\n#[component]\nfn Home(cx: Scope) -> Element {\n todo!()\n}\n\n#[component]\nfn HelloWorld(cx: Scope) -> Element {\n todo!()\n}\n````\n\n## Dynamic Segments\n\nDynamic segments are in the form of `:name` where `name` is\nthe name of the field in the route variant. If the segment is parsed\nsuccessfully then the route matches, otherwise the matching continues.\n\nThe segment can be of any type that implements `FromStr`.\n\n````rust@dynamic_segments.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // segments that start with : are dynamic segments\n #[route(\"/post/:name\")]\n BlogPost {\n // You must include dynamic segments in child variants\n name: String,\n },\n #[route(\"/document/:id\")]\n Document {\n // You can use any type that implements FromStr\n // If the segment can't be parsed, the route will not match\n id: usize,\n },\n}\n\n// Components must contain the same dynamic segments as their corresponding variant\n#[component]\nfn BlogPost(cx: Scope, name: String) -> Element {\n todo!()\n}\n\n#[component]\nfn Document(cx: Scope, id: usize) -> Element {\n todo!()\n}\n````\n\n## Catch All Segments\n\nCatch All segments are in the form of `:..name` where `name` is the name of the field in the route variant. If the segments are parsed successfully then the route matches, otherwise the matching continues.\n\nThe segment can be of any type that implements `FromSegments`. (Vec implements this by default)\n\nCatch All segments must be the *last route segment* in the path (query segments are not counted) and cannot be included in nests.\n\n````rust@catch_all_segments.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // segments that start with :.. are catch all segments\n #[route(\"/blog/:..segments\")]\n BlogPost {\n // You must include catch all segment in child variants\n segments: Vec,\n },\n}\n\n// Components must contain the same catch all segments as their corresponding variant\n#[component]\nfn BlogPost(cx: Scope, segments: Vec) -> Element {\n todo!()\n}\n````\n\n## Query Segments\n\nQuery segments are in the form of `?:name` where `name` is the name of the field in the route variant.\n\nUnlike [Dynamic Segments](#dynamic-segments) and [Catch All Segments](#catch-all-segments), parsing a Query segment must not fail.\n\nThe segment can be of any type that implements `FromQuery`.\n\nQuery segments must be the *after all route segments* and cannot be included in nests.\n\n````rust@query_segments.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // segments that start with ?: are query segments\n #[route(\"/blog?:query_params\")]\n BlogPost {\n // You must include query segments in child variants\n query_params: BlogQuerySegments,\n },\n}\n\n#[derive(Debug, Clone, PartialEq)]\nstruct BlogQuerySegments {\n name: String,\n surname: String,\n}\n\n/// The display impl needs to display the query in a way that can be parsed:\nimpl Display for BlogQuerySegments {\n fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n write!(f, \"name={}&surname={}\", self.name, self.surname)\n }\n}\n\n/// The query segment is anything that implements https://docs.rs/dioxus-router/latest/dioxus_router/routable/trait.FromQuery.html. You can implement that trait for a struct if you want to parse multiple query parameters.\nimpl FromQuery for BlogQuerySegments {\n fn from_query(query: &str) -> Self {\n let mut name = None;\n let mut surname = None;\n let pairs = form_urlencoded::parse(query.as_bytes());\n pairs.for_each(|(key, value)| {\n if key == \"name\" {\n name = Some(value.clone().into());\n }\n if key == \"surname\" {\n surname = Some(value.clone().into());\n }\n });\n Self {\n name: name.unwrap(),\n surname: surname.unwrap(),\n }\n }\n}\n\n#[component]\nfn BlogPost(cx: Scope, query_params: BlogQuerySegments) -> Element {\n render! {\n div{\"This is your blogpost with a query segment:\"}\n div{format!(\"{:?}\", query_params)}\n }\n}\n\nfn App(cx: Scope) -> Element {\n render! { Router::{} }\n}\n\nfn main() {}\n````" + } + 77usize => { + "# Overall Goals\n\nThis document outlines some of the overall goals for Dioxus. These goals are not set in stone, but they represent general guidelines for the project.\n\nThe goal of Dioxus is to make it easy to build **cross-platform applications that scale**.\n\n## Cross-Platform\n\nDioxus is designed to be cross-platform by default. This means that it should be easy to build applications that run on the web, desktop, and mobile. However, Dioxus should also be flexible enough to allow users to opt into platform-specific features when needed. The `use_eval` is one example of this. By default, Dioxus does not assume that the platform supports JavaScript, but it does provide a hook that allows users to opt into JavaScript when needed.\n\n## Performance\n\nAs Dioxus applications grow, they should remain relatively performant without the need for manual optimizations. There will be cases where manual optimizations are needed, but Dioxus should try to make these cases as rare as possible.\n\nOne of the benefits of the core architecture of Dioxus is that it delivers reasonable performance even when components are rerendered often. It is based on a Virtual Dom which performs diffing which should prevent unnecessary re-renders even when large parts of the component tree are rerun. On top of this, Dioxus groups static parts of the RSX tree together to skip diffing them entirely.\n\n## Type Safety\n\nAs teams grow, the Type safety of Rust is a huge advantage. Dioxus should leverage this advantage to make it easy to build applications with large teams.\n\nTo take full advantage of Rust's type system, Dioxus should try to avoid exposing public `Any` types and string-ly typed APIs where possible.\n\n## Developer Experience\n\nDioxus should be easy to learn and ergonomic to use.\n\n* The API of Dioxus attempts to remain close to React's API where possible. This makes it easier for people to learn Dioxus if they already know React\n\n* We can avoid the tradeoff between simplicity and flexibility by providing multiple layers of API: One for the very common use case, one for low-level control\n \n * Hooks: the hooks crate has the most common use cases, but `cx.hook` provides a way to access the underlying persistent reference if needed.\n * The builder pattern in platform Configs: The builder pattern is used to default to the most common use case, but users can change the defaults if needed.\n* Documentation:\n \n * All public APIs should have rust documentation\n * Examples should be provided for all public features. These examples both serve as documentation and testing. They are checked by CI to ensure that they continue to compile\n * The most common workflows should be documented in the guide" + } + 39usize => { + "# Creating Our First Route\n\nIn this chapter, we will start utilizing Dioxus Router and add a homepage and a\n404 page to our project.\n\n## Fundamentals\n\nThe core of the Dioxus Router is the \\[`Routable`\\] macro and the \\[`Router`\\] component.\n\nRoutable is a trait for anything that can:\n\n* Be parsed from a URL\n* Be turned into a URL\n* Be rendered as to a Element\n\nLet's create a new router. First, we need an actual page to route to! Let's add a homepage component:\n\n````rust@first_route.rs\n#[component]\nfn Home(cx: Scope) -> Element {\n render! {\n h1 { \"Welcome to the Dioxus Blog!\" }\n }\n}\n````\n\n## Creating Routes\n\nWe want to use Dioxus Router to separate our application into different \"pages\".\nDioxus Router will then determine which page to render based on the URL path.\n\nTo start using Dioxus Router, we need to use the \\[`Routable`\\] macro.\n\nThe \\[`Routable`\\] macro takes an enum with all of the possible routes in our application. Each variant of the enum represents a route and must be annotated with the \\[`route(path)`\\] attribute.\n\n````rust@first_route.rs\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n/// An enum of all of the possible routes in the app.\n#[derive(Routable, Clone)]\nenum Route {\n // The home page is at the / route\n #[route(\"/\")]\n // If the name of the component and variant are the same you can omit the component and props name\n // If they are different you can specify them like this:\n // #[route(\"/\", ComponentName, PropsName)]\n Home {},\n}\n````\n\nAll other hooks and components the router provides can only be used as a descendant of a \\[`Router`\\] component.\n\nIf you head to your application's browser tab, you should now see the text\n`Welcome to Dioxus Blog!` when on the root URL (`http://localhost:8080/`). If\nyou enter a different path for the URL, nothing should be displayed.\n\nThis is because we told Dioxus Router to render the `Home` component only when\nthe URL path is `/`.\n\n## Fallback Route\n\nIn our example, when a route doesn't exist Dioxus Router doesn't render anything. Many sites also have a \"404\" page when a path does not exist. Let's add one to our site.\n\nFirst, we create a new `PageNotFound` component.\n\n````rust@catch_all.rs\n#[component]\nfn PageNotFound(cx: Scope, route: Vec) -> Element {\n render! {\n h1 { \"Page not found\" }\n p { \"We are terribly sorry, but the page you requested doesn't exist.\" }\n pre {\n color: \"red\",\n \"log:\\nattemped to navigate to: {route:?}\"\n }\n }\n}\n````\n\nNext, register the route in the Route enum to match if all other routes fail.\n\n````rust@catch_all.rs\n#[derive(Routable, Clone)]\nenum Route {\n #[route(\"/\")]\n Home {},\n // PageNotFound is a catch all route that will match any route and placing the matched segments in the route field\n #[route(\"/:..route\")]\n PageNotFound { route: Vec },\n}\n````\n\nNow when you go to a route that doesn't exist, you should see the page not found\ntext.\n\n## Conclusion\n\nIn this chapter, we learned how to create a route and tell Dioxus Router what\ncomponent to render when the URL path is `/`. We also created a 404 page to\nhandle when a route doesn't exist. Next, we'll create the blog portion of our\nsite. We will utilize nested routes and URL parameters." + } + 78usize => { + "# Roadmap & Feature-set\n\nThis feature set and roadmap can help you decide if what Dioxus can do today works for you.\n\nIf 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).\n\nGenerally, here's the status of each platform:\n\n* **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.\n\n* **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`.\n\n* **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.\n\n* **Mobile**: Mobile support is very young. You'll be figuring things out as you go and there are not many support crates for peripherals.\n\n* **LiveView**: LiveView support is very young. You'll be figuring things out as you go. Thankfully, none of it is too hard and any work can be upstreamed into Dioxus.\n\n## Features\n\n---\n\n|Feature|Status|Description|\n|-------|------|-----------|\n|Conditional Rendering|x|if/then to hide/show component|\n|Map, Iterator|x|map/filter/reduce to produce rsx!|\n|Keyed Components|x|advanced diffing with keys|\n|Web|x|renderer for web browser|\n|Desktop (webview)|x|renderer for desktop|\n|Shared State (Context)|x|share state through the tree|\n|Hooks|x|memory cells in components|\n|SSR|x|render directly to string|\n|Component Children|x|cx.children() as a list of nodes|\n|Headless components|x|components that don't return real elements|\n|Fragments|x|multiple elements without a real root|\n|Manual Props|x|Manually pass in props with spread syntax|\n|Controlled Inputs|x|stateful wrappers around inputs|\n|CSS/Inline Styles|x|syntax for inline styles/attribute groups|\n|Custom elements|x|Define new element primitives|\n|Suspense|x|schedule future render from future/promise|\n|Integrated error handling|x|Gracefully handle errors with ? syntax|\n|NodeRef|x|gain direct access to nodes|\n|Re-hydration|x|Pre-render to HTML to speed up first contentful paint|\n|Jank-Free Rendering|x|Large diffs are segmented across frames for silky-smooth transitions|\n|Effects|x|Run effects after a component has been committed to render|\n|Portals|\\*|Render nodes outside of the traditional tree structure|\n|Cooperative Scheduling|\\*|Prioritize important events over non-important events|\n|Server Components|\\*|Hybrid components for SPA and Server|\n|Bundle Splitting|i|Efficiently and asynchronously load the app|\n|Lazy Components|i|Dynamically load the new components as the page is loaded|\n|1st class global state|x|redux/recoil/mobx on top of context|\n|Runs natively|x|runs as a portable binary w/o a runtime (Node)|\n|Subtree Memoization|x|skip diffing static element subtrees|\n|High-efficiency templates|x|rsx! calls are translated to templates on the DOM's side|\n|Compile-time correct|x|Throw errors on invalid template layouts|\n|Heuristic Engine|x|track component memory usage to minimize future allocations|\n|Fine-grained reactivity|i|Skip diffing for fine-grain updates|\n\n* x = implemented and working\n* \\* = actively being worked on\n* i = not yet implemented or being worked on\n\n## Roadmap\n\nThese Features are planned for the future of Dioxus:\n\n### Core\n\n* [x] Release of Dioxus Core\n* [x] Upgrade documentation to include more theory and be more comprehensive\n* [x] Support for HTML-side templates for lightning-fast dom manipulation\n* [ ] Support for multiple renderers for same virtualdom (subtrees)\n* [ ] Support for ThreadSafe (Send + Sync)\n* [ ] Support for Portals\n\n### SSR\n\n* [x] SSR Support + Hydration\n* [ ] Integrated suspense support for SSR\n\n### Desktop\n\n* [ ] Declarative window management\n* [ ] Templates for building/bundling\n* [ ] Access to Canvas/WebGL context natively\n\n### Mobile\n\n* [ ] Mobile standard library\n * [ ] GPS\n * [ ] Camera\n * [ ] filesystem\n * [ ] Biometrics\n * [ ] WiFi\n * [ ] Bluetooth\n * [ ] Notifications\n * [ ] Clipboard\n* [ ] Animations\n\n### Bundling (CLI)\n\n* [x] Translation from HTML into RSX\n* [x] Dev server\n* [x] Live reload\n* [x] Translation from JSX into RSX\n* [ ] Hot module replacement\n* [ ] Code splitting\n* [ ] Asset macros\n* [ ] Css pipeline\n* [ ] Image pipeline\n\n### Essential hooks\n\n* [x] Router\n* [x] Global state management\n* [ ] Resize observer\n\n## Work in Progress\n\n### Build Tool\n\nWe are currently working on our own build tool called [Dioxus CLI](https://github.com/DioxusLabs/dioxus/tree/master/packages/cli) which will support:\n\n* an interactive TUI\n* on-the-fly reconfiguration\n* hot CSS reloading\n* two-way data binding between browser and source code\n* an interpreter for `rsx!`\n* ability to publish to github/netlify/vercel\n* bundling for iOS/Desktop/etc\n\n### Server Component Support\n\nWhile 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.\n\n### Native rendering\n\nWe are currently working on a native renderer for Dioxus using WGPU called [Blitz](https://github.com/DioxusLabs/blitz/). This will allow you to build apps that are rendered natively for iOS, Android, and Desktop." + } + 62usize => { + "# Working with External State\n\nThis guide will help you integrate your Dioxus application with some external state like a different thread or a websocket connection.\n\n## Working with non-reactive State\n\n[Coroutines](../../../reference/use_coroutine.md) are great tool for dealing with non-reactive (state you don't render directly) state within your application.\n\nYou can store your state inside the coroutine async block and communicate with the coroutine with messages from any child components.\n\n````rust@use_coroutine.rs\n// import futures::StreamExt to use the next() method\nuse futures::StreamExt;\nlet response_state = use_state(cx, || None);\nlet tx = use_coroutine(cx, |mut rx| {\n to_owned![response_state];\n async move {\n // Define your state before the loop\n let mut state = reqwest::Client::new();\n let mut cache: HashMap = HashMap::new();\n loop {\n // Loop and wait for the next message\n if let Some(request) = rx.next().await {\n // Resolve the message\n let response = if let Some(response) = cache.get(&request) {\n response.clone()\n } else {\n let response = state\n .get(&request)\n .send()\n .await\n .unwrap()\n .text()\n .await\n .unwrap();\n cache.insert(request, response.clone());\n response\n };\n response_state.set(Some(response));\n } else {\n break;\n }\n }\n }\n});\n// Send a message to the coroutine\ntx.send(\"https://example.com\".to_string());\n// Get the current state of the coroutine\nlet response = response_state.get();\n````\n\n## Making Reactive State External\n\nIf you have some reactive state (state that is rendered), that you want to modify from another thread, you can use the [use_rw](https://github.com/DioxusLabs/dioxus-std/blob/master/src/utils/rw/use_rw.rs) hook in the [dioxus-std](https://github.com/DioxusLabs/dioxus-std) crate.\n\nThe use_rw hook works like the use_ref hook, but it is Send + Sync which makes it possible to move the hook into another thread." + } + 19usize => { + "# Hooks and component state\n\nSo far, our components have had no state like a normal Rust function. 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 opened a drop-down and render different things accordingly.\n\nHooks allow us to create state in our components. Hooks are Rust functions that take a reference to [`ScopeState`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html) (in a component, you can pass `cx`), and provide you with functionality and state.\n\nDioxus provides many built-in hooks, but if those hooks don't fit your specific use case, you also can [create your own hook](../cookbook/state/custom_hooks/index.md)\n\n## use_state hook\n\n[`use_state`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_state.html) is one of the simplest hooks.\n\n* You provide a closure that determines the initial value: `let mut count = use_state(cx, || 0);`\n* `use_state` gives you the current value, and a way to update it by setting it to something else\n* When the value updates, `use_state` makes the component re-render (along with any other component that references it), and then provides you with the new value.\n\nFor example, you might have seen the counter example, in which state (a number) is tracked using the `use_state` hook:\n\n````rust, no_run@hooks_counter.rs\npub fn App(cx: Scope) -> Element {\n // count will be initialized to 0 the first time the component is rendered\n let mut count = use_state(cx, || 0);\n\n cx.render(rsx!(\n h1 { \"High-Five counter: {count}\" }\n button {\n onclick: move |_| {\n // changing the count will cause the component to re-render\n count += 1\n },\n \"Up high!\"\n }\n button {\n onclick: move |_| {\n // changing the count will cause the component to re-render\n count -= 1\n },\n \"Down low!\"\n }\n ))\n}\n````\n\n````inject-dioxus\nDemoFrame {\n hooks_counter::App {}\n}\n````\n\nEvery 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 – describe what you want in terms of the state, and Dioxus will take care of the rest!\n\n > \n > `use_state` returns your value wrapped in a smart pointer of type [`UseState`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseState.html). This is why you can both read the value and update it, even within an event handler.\n\nYou can use multiple hooks in the same component if you want:\n\n````rust, no_run@hooks_counter_two_state.rs\npub fn App(cx: Scope) -> Element {\n let mut count_a = use_state(cx, || 0);\n let mut count_b = use_state(cx, || 0);\n\n cx.render(rsx!(\n h1 { \"Counter_a: {count_a}\" }\n button { onclick: move |_| count_a += 1, \"a++\" }\n button { onclick: move |_| count_a -= 1, \"a--\" }\n h1 { \"Counter_b: {count_b}\" }\n button { onclick: move |_| count_b += 1, \"b++\" }\n button { onclick: move |_| count_b -= 1, \"b--\" }\n ))\n}\n````\n\n````inject-dioxus\nDemoFrame {\n hooks_counter_two_state::App {}\n}\n````\n\n### Out-of-date UseState\n\nThe value `UseState` dereferences to is only set when the use_state hook is called every render. This means that if you move the state into a future, or you write to the state and then immediately read the state, it may return an out-of-date value.\n\n````rust@hooks_out_of_date.rs\npub fn App(cx: Scope) -> Element {\n // count will be initialized to 0 the first time the component is rendered\n let mut count = use_state(cx, || 0);\n let first_count_read = use_state(cx, || 0);\n\n // Increase the count\n if *count == 0 {\n count += 1;\n first_count_read.set(**count);\n }\n\n cx.render(rsx!(\n // This uses the deref value\n h1 { \"High-Five counter: {first_count_read}\" }\n ))\n}\n````\n\n````inject-dioxus\nDemoFrame {\n // original: hooks_out_of_date::App {}\n __interactive_04::hooks_out_of_date {}\n}\n````\n\nInstead of using deref to get the inner value from UseState, you can use the [`current`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseState.html#method.current) function. This function will always return the current value of the state.\n\n````rust@hooks_out_of_date.rs\npub fn App(cx: Scope) -> Element {\n let mut count = use_state(cx, || 0);\n let first_count_read = use_state(cx, || 0);\n\n // Increase the count\n if *count == 0 {\n count += 1;\n first_count_read.set(*count.current());\n }\n\n cx.render(rsx!(\n // Use .current to get the real current value\n h1 { \"High-Five counter: {first_count_read}\" }\n ))\n}\n````\n\n````inject-dioxus\nDemoFrame {\n // original: hooks_out_of_date::fixed::App {}\n __interactive_04::hooks_out_of_date_fixed {}\n}\n````\n\n## Rules of hooks\n\nThe 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.\n\nBut 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?\n\n````rust, no_run@hooks_counter_two_state.rs\nlet mut count_a = use_state(cx, || 0);\nlet mut count_b = use_state(cx, || 0);\n````\n\nThis is only possible because the two hooks are always called in the same order, so Dioxus knows which is which. Because the order you call hooks matters, you must follow certain rules when using hooks:\n\n1. Hooks may be only used in components or other hooks (we'll get to that later).\n1. On every call to a component function.\n1. The same hooks must be called (except in the case of early returns, as explained later in the [Error Handling chapter](../cookbook/error_handling.md)).\n1. In the same order.\n1. Hook names should start with `use_` so you don't accidentally confuse them with regular\n functions (`use_state()`, `use_ref()`, `use_future()`, etc...).\n\nThese rules mean that there are certain things you can't do with hooks:\n\n### No hooks in conditionals\n\n````rust, no_run@hooks_bad.rs\n// ❌ don't call hooks in conditionals!\n// We must ensure that the same hooks will be called every time\n// But `if` statements only run if the conditional is true!\n// So we might violate rule 2.\nif you_are_happy && you_know_it {\n let something = use_state(cx, || \"hands\");\n println!(\"clap your {something}\")\n}\n\n// ✅ instead, *always* call use_state\n// You can put other stuff in the conditional though\nlet something = use_state(cx, || \"hands\");\nif you_are_happy && you_know_it {\n println!(\"clap your {something}\")\n}\n````\n\n### No hooks in closures\n\n````rust, no_run@hooks_bad.rs\n// ❌ don't call hooks inside closures!\n// We can't guarantee that the closure, if used, will be called in the same order every time\nlet _a = || {\n let b = use_state(cx, || 0);\n b.get()\n};\n\n// ✅ instead, move hook `b` outside\nlet b = use_state(cx, || 0);\nlet _a = || b.get();\n````\n\n### No hooks in loops\n\n````rust, no_run@hooks_bad.rs\n// `names` is a Vec<&str>\n\n// ❌ Do not use hooks in loops!\n// In this case, if the length of the Vec changes, we break rule 2\nfor _name in &names {\n let is_selected = use_state(cx, || false);\n println!(\"selected: {is_selected}\");\n}\n\n// ✅ Instead, use a hashmap with use_ref\nlet selection_map = use_ref(cx, HashMap::<&str, bool>::new);\n\nfor name in &names {\n let is_selected = selection_map.read()[name];\n println!(\"selected: {is_selected}\");\n}\n````\n\n## use_ref hook\n\n`use_state` is great for tracking simple values. However, in the [`UseState` API](https://docs.rs/dioxus/latest/dioxus/hooks/struct.UseState.html), you may notice 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 component's state?\n\nFor example, suppose we want to maintain a `Vec` of values. If we stored it with `use_state`, the\nonly way to add a new value to the list would be to copy the existing `Vec`, add our value to it,\nand then replace the existing `Vec` in the state with it. This is expensive! We want to modify the\nexisting `Vec` instead.\n\nThankfully, 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.\n\nHere's a simple example that keeps a list of events in a `use_ref`. We can acquire write access to the state with `.with_mut()`, and then just `.push` a new value to the state:\n\n````rust, no_run@hooks_use_ref.rs\npub fn App(cx: Scope) -> Element {\n let list = use_ref(cx, Vec::new);\n\n cx.render(rsx!(\n p { \"Current list: {list.read():?}\" }\n button {\n onclick: move |event| {\n list.with_mut(|list| list.push(event));\n },\n \"Click me!\"\n }\n ))\n}\n````\n\n````inject-dioxus\nDemoFrame {\n // original: hooks_use_ref::App {}\n __interactive_04::hooks_use_ref {}\n}\n````\n\n > \n > The return values of `use_state` and `use_ref` (\n > \u{a0}[`UseState`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseState.html) and\n > \u{a0}[`UseRef`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseRef.html), respectively) are in\n > \u{a0}some ways similar to [`Cell`](https://doc.rust-lang.org/std/cell/) and\n > \u{a0}[`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) – they provide interior\n > \u{a0}mutability. However, these Dioxus wrappers also ensure that the component gets re-rendered\n > \u{a0}whenever you change the state.\n\n## Additional resources\n\n* [dioxus_hooks API docs](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/)\n* [dioxus_hooks source code](https://github.com/DioxusLabs/dioxus/tree/master/packages/hooks)" + } + 49usize => { + "# Programmatic Navigation\n\nSometimes we want our application to navigate to another page without having the\nuser click on a link. This is called programmatic navigation.\n\n## Using a Navigator\n\nWe can get a navigator with the \\[`use_navigator`\\] hook. This hook returns a \\[`Navigator`\\].\n\nWe can use the \\[`Navigator`\\] to trigger four different kinds of navigation:\n\n* `push` will navigate to the target. It works like a regular anchor tag.\n* `replace` works like `push`, except that it replaces the current history entry\n instead of adding a new one. This means the prior page cannot be restored with the browser's back button.\n* `Go back` works like the browser's back button.\n* `Go forward` works like the browser's forward button.\n\n````rust@navigator.rs\n#[component]\nfn Home(cx: Scope) -> Element {\n let nav = use_navigator(cx);\n\n // push\n nav.push(Route::PageNotFound { route: vec![] });\n\n // replace\n nav.replace(Route::Home {});\n\n // go back\n nav.go_back();\n\n // go forward\n nav.go_forward();\n\n render! {\n h1 { \"Welcome to the Dioxus Blog!\" }\n }\n}\n````\n\nYou might have noticed that, like \\[`Link`\\], the \\[`Navigator`\\]s `push` and\n`replace` functions take a \\[`NavigationTarget`\\]. This means we can use either\n\\[`Internal`\\], or \\[`External`\\] targets.\n\n## External Navigation Targets\n\nUnlike a \\[`Link`\\], the \\[`Navigator`\\] cannot rely on the browser (or webview) to\nhandle navigation to external targets via a generated anchor element.\n\nThis means, that under certain conditions, navigation to external targets can\nfail." + } + 4usize => { + "# Liveview\n\nLiveview allows apps to *run* on the server and *render* in the browser. It uses WebSockets to communicate between the server and the browser.\n\nExamples:\n\n* [Axum Example](https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/axum.rs)\n* [Salvo Example](https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/salvo.rs)\n* [Warp Example](https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/warp.rs)\n\n## Support\n\nLiveview is currently limited in capability when compared to the Web platform. Liveview apps run on the server in 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.\n\n## Setup\n\nFor this guide, we're going to show how to use Dioxus Liveview with [Axum](https://docs.rs/axum/latest/axum/).\n\nMake sure you have Rust and Cargo installed, and then create a new project:\n\n````shell\ncargo new --bin demo\ncd demo\n````\n\nAdd Dioxus and the liveview renderer with the Axum feature as dependencies:\n\n````shell\ncargo add dioxus\ncargo add dioxus-liveview --features axum\n````\n\nNext, add all the Axum dependencies. This will be different if you're using a different Web Framework\n\n````\ncargo add tokio --features full\ncargo add axum\n````\n\nYour dependencies should look roughly like this:\n\n````toml\n[dependencies]\naxum = \"0.4.5\"\ndioxus = { version = \"*\" }\ndioxus-liveview = { version = \"*\", features = [\"axum\"] }\ntokio = { version = \"1.15.0\", features = [\"full\"] }\n````\n\nNow, set up your Axum app to respond on an endpoint.\n\n````rust@hello_world_liveview.rs\nuse axum::{extract::ws::WebSocketUpgrade, response::Html, routing::get, Router};\nuse dioxus::prelude::*;\n\n#[tokio::main]\nasync fn main() {\n let addr: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into();\n\n let view = dioxus_liveview::LiveViewPool::new();\n\n let app = Router::new()\n // The root route contains the glue code to connect to the WebSocket\n .route(\n \"/\",\n get(move || async move {\n Html(format!(\n r#\"\n \n \n Dioxus LiveView with Axum \n
\n {glue}\n \n \"#,\n // Create the glue code to connect to the WebSocket on the \"/ws\" route\n glue = dioxus_liveview::interpreter_glue(&format!(\"ws://{addr}/ws\"))\n ))\n }),\n )\n // The WebSocket route is what Dioxus uses to communicate with the browser\n .route(\n \"/ws\",\n get(move |ws: WebSocketUpgrade| async move {\n ws.on_upgrade(move |socket| async move {\n // When the WebSocket is upgraded, launch the LiveView with the app component\n _ = view.launch(dioxus_liveview::axum_socket(socket), app).await;\n })\n }),\n );\n\n println!(\"Listening on http://{addr}\");\n\n axum::Server::bind(&addr.to_string().parse().unwrap())\n .serve(app.into_make_service())\n .await\n .unwrap();\n}\n````\n\nAnd then add our app component:\n\n````rust@hello_world_liveview.rs\nfn app(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n \"Hello, world!\"\n }\n })\n}\n````\n\nAnd that's it!\n\n## Hot Reload\n\n1. Hot reloading allows much faster iteration times inside of rsx calls by interpreting them and streaming the edits.\n1. It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program.\n\n### Hot Reload Setup\n\nInstall [dioxus-cli](https://github.com/DioxusLabs/dioxus/tree/master/packages/cli).\n\n### Usage\n\n1. Run:\n\n````bash\ndx serve --hot-reload --platform desktop\n````\n\n2. Change some code within `rsx` or `render` macro\n2. Save and watch the style change without recompiling\n\n### Limitations\n\n1. The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will require a full recompile to capture the expression.\n1. Components, Iterators, and some attributes can contain arbitrary rust code and will trigger a full recompile when changed." + } + 76usize => { + "# Walkthrough of the Hello World Example Internals\n\nThis walkthrough will take you through the internals of the Hello World example program. It will explain how major parts of Dioxus internals interact with each other to take the readme example from a source file to a running application. This guide should serve as a high-level overview of the internals of Dioxus. It is not meant to be a comprehensive guide.\n\nThe core crate roughly works like this:\n\n![](https://mermaid.ink/img/pako:eNqNk01v2zAMhv8KocsuTQ876lCgWAb0sGDD0mMAg7PoWogsBvpwWhT976MlJ3OKbKtOEvmIfEWRr6plQ0qrmDDR2uJTwGE1ft55kBXIGwqNHQYyVvywWt3BA3rjKGj4gs5BX0-V_1n4QtUthW_Mh6WzWgryg537OpJPsQJ_zsX9PrmG0fBwWxM2NIH1nmdRFuxTn4C7K4mn9djTpYAjWsnTcQBaSJiWxIcULEVILCIiu5Egyf3RhpTRwfr75tOC73LKggGmQkUcBLcDVUJyFoF_qcEkoxEVzZHDvjIXpnOhtm1PJp8rvcGw37Z8oPu4FlkvhVvbrivGypyP_3dWXRo2WdrAsp-fN391Qd5n1BBnSU0-GDy9sHyGo678xcOyOU7fMHcMHINNtcgIPfP-Wr2WAu6NeeRzGTS0z7fxgEd_7T3_Zi8b5kp1T1IxvvgWfjlu9x-SexHqo1VTN2qgMKA1MoavU6CdkkaSBlJatoY6zC7t1M6_CYo58VZUKZ1CphtVo8yDq3SHLopVJiZx2NTRLhP-9htxEk8q?type=png)\n\n## The Source File\n\nWe start will a hello world program. This program renders a desktop app with the text \"Hello World\" in a webview.\n\n````rust, no_run@readme.rs\nuse dioxus::prelude::*;\n\npub fn App(cx: Scope) -> Element {\n let mut count = use_state(cx, || 0);\n\n cx.render(rsx! {\n h1 { \"High-Five counter: {count}\" }\n button { onclick: move |_| count += 1, \"Up high!\" }\n button { onclick: move |_| count -= 1, \"Down low!\" }\n })\n}\n\n````\n\n[![](https://mermaid.ink/img/pako:eNqNkT1vwyAQhv8KvSlR48HphtQtqjK0S6tuSBGBS0CxwcJHk8rxfy_YVqxKVdR3ug_u4YXrQHmNwOFQ-bMyMhB7fReOJbVxfwyyMSy0l7GSpW1ARda727ksUy5MuSyKgvBC5ULA1h5N8WK_kCkfHWHgrBuiXsBynrvdsY9E3u1iM_eyvFOVVadMnELOap-o1911JLPHZ1b-YqLTc3LjTt7WifTZMJPsPdx1ov3Z_ellfcdL8R8vmTy5eUqsTUpZ-vzZzjAEK6gx1NLqtJwuNwSQwRoF8BRqGU4ChOvTORnJf3w7BZxCxBXERkvCjZXpQTXwg6zaVEVtyYe3cdvD0vsf4bucgw?type=png)](https://mermaid.live/edit#pako:eNqNkT1vwyAQhv8KvSlR48HphtQtqjK0S6tuSBGBS0CxwcJHk8rxfy_YVqxKVdR3ug_u4YXrQHmNwOFQ-bMyMhB7fReOJbVxfwyyMSy0l7GSpW1ARda727ksUy5MuSyKgvBC5ULA1h5N8WK_kCkfHWHgrBuiXsBynrvdsY9E3u1iM_eyvFOVVadMnELOap-o1911JLPHZ1b-YqLTc3LjTt7WifTZMJPsPdx1ov3Z_ellfcdL8R8vmTy5eUqsTUpZ-vzZzjAEK6gx1NLqtJwuNwSQwRoF8BRqGU4ChOvTORnJf3w7BZxCxBXERkvCjZXpQTXwg6zaVEVtyYe3cdvD0vsf4bucgw)\n\n## The rsx! Macro\n\nBefore the Rust compiler runs the program, it will expand all [macros](https://doc.rust-lang.org/reference/procedural-macros.html). Here is what the hello world example looks like expanded:\n\n````rust, no_run@readme_expanded.rs\nuse dioxus::prelude::*;\n\nfn main() {\n dioxus_desktop::launch(app);\n}\n\nfn app(cx: Scope) -> Element {\n let mut count = use_state(cx, || 0);\n\n cx.render(\n // rsx expands to LazyNodes::new\n ::dioxus::core::LazyNodes::new(\n move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode {\n // The template is every static part of the rsx\n static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {\n // This is the source location of the rsx that generated this template. This is used to make hot rsx reloading work. Hot rsx reloading just replaces the template with a new one generated from the rsx by the CLI.\n name: \"examples\\\\readme.rs:14:15:250\",\n // The root nodes are the top level nodes of the rsx\n roots: &[\n // The h1 node\n ::dioxus::core::TemplateNode::Element {\n // Find the built in h1 tag in the dioxus_elements crate exported by the dioxus html crate\n tag: dioxus_elements::h1::TAG_NAME,\n namespace: dioxus_elements::h1::NAME_SPACE,\n attrs: &[],\n // The children of the h1 node\n children: &[\n // The dynamic count text node\n // Any nodes that are dynamic have a dynamic placeholder with a unique index\n ::dioxus::core::TemplateNode::DynamicText {\n // This index is used to find what element in `dynamic_nodes` to use instead of the placeholder\n id: 0usize,\n },\n ],\n },\n // The up high button node\n ::dioxus::core::TemplateNode::Element {\n tag: dioxus_elements::button::TAG_NAME,\n namespace: dioxus_elements::button::NAME_SPACE,\n attrs: &[\n // The dynamic onclick listener attribute\n // Any attributes that are dynamic have a dynamic placeholder with a unique index.\n ::dioxus::core::TemplateAttribute::Dynamic {\n // Similar to dynamic nodes, dynamic attributes have a unique index used to find the attribute in `dynamic_attrs` to use instead of the placeholder\n id: 0usize,\n },\n ],\n children: &[::dioxus::core::TemplateNode::Text { text: \"Up high!\" }],\n },\n // The down low button node\n ::dioxus::core::TemplateNode::Element {\n tag: dioxus_elements::button::TAG_NAME,\n namespace: dioxus_elements::button::NAME_SPACE,\n attrs: &[\n // The dynamic onclick listener attribute\n ::dioxus::core::TemplateAttribute::Dynamic { id: 1usize },\n ],\n children: &[::dioxus::core::TemplateNode::Text { text: \"Down low!\" }],\n },\n ],\n // Node paths is a list of paths to every dynamic node in the rsx\n node_paths: &[\n // The first node path is the path to the dynamic node with an id of 0 (the count text node)\n &[\n // Go to the index 0 root node\n 0u8, //\n // Go to the first child of the root node\n 0u8,\n ],\n ],\n // Attr paths is a list of paths to every dynamic attribute in the rsx\n attr_paths: &[\n // The first attr path is the path to the dynamic attribute with an id of 0 (the up high button onclick listener)\n &[\n // Go to the index 1 root node\n 1u8,\n ],\n // The second attr path is the path to the dynamic attribute with an id of 1 (the down low button onclick listener)\n &[\n // Go to the index 2 root node\n 2u8,\n ],\n ],\n };\n // The VNode is a reference to the template with the dynamic parts of the rsx\n ::dioxus::core::VNode {\n parent: None,\n key: None,\n // The static template this node will use. The template is stored in a Cell so it can be replaced with a new template when hot rsx reloading is enabled\n template: std::cell::Cell::new(TEMPLATE),\n root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(\n 3,\n __cx.bump(),\n )\n .into(),\n dynamic_nodes: __cx.bump().alloc([\n // The dynamic count text node (dynamic node id 0)\n __cx.text_node(format_args!(\"High-Five counter: {0}\", count)),\n ]),\n dynamic_attrs: __cx.bump().alloc([\n // The dynamic up high button onclick listener (dynamic attribute id 0)\n dioxus_elements::events::onclick(__cx, move |_| count += 1),\n // The dynamic down low button onclick listener (dynamic attribute id 1)\n dioxus_elements::events::onclick(__cx, move |_| count -= 1),\n ]),\n }\n },\n ),\n )\n}\n\n````\n\nThe rsx macro separates the static parts of the rsx (the template) and the dynamic parts (the [dynamic_nodes](https://docs.rs/dioxus-core/0.3.2/dioxus_core/prelude/struct.VNode.html#structfield.dynamic_nodes) and [dynamic_attributes](https://docs.rs/dioxus-core/0.3.2/dioxus_core/prelude/struct.VNode.html#structfield.dynamic_attrs)).\n\nThe static template only contains the parts of the rsx that cannot change at runtime with holes for the dynamic parts:\n\n[![](https://mermaid.ink/img/pako:eNqdksFuwjAMhl8l8wkkKtFx65njdtm0E0GVSQKJoEmVOgKEeHecUrXStO0wn5Lf9u8vcm6ggjZQwf4UzspiJPH2Ib3g6NLuELG1oiMkp0TsLs9EDu2iUeSCH8tz2HJmy3lRFPrqsXGq9mxeLzcbCU6LZSUGXWRdwnY7tY7Tdoko-Dq1U64fODgiUfzJMeuOe7_ZGq-ny2jNhGQu9DqT8NUK6w72RcL8dxgdzv4PnHLAKf-Fk80HoBUDrfkqeBkTUd8EC2hMbNBpXtYtJySQNQ0PqPioMR4lSH_nOkwUPq9eQUUxmQWkViOZtUN-UwPVHk8dq0Y7CvH9uf3-E9wfrmuk1A?type=png)](https://mermaid.live/edit#pako:eNqdksFuwjAMhl8l8wkkKtFx65njdtm0E0GVSQKJoEmVOgKEeHecUrXStO0wn5Lf9u8vcm6ggjZQwf4UzspiJPH2Ib3g6NLuELG1oiMkp0TsLs9EDu2iUeSCH8tz2HJmy3lRFPrqsXGq9mxeLzcbCU6LZSUGXWRdwnY7tY7Tdoko-Dq1U64fODgiUfzJMeuOe7_ZGq-ny2jNhGQu9DqT8NUK6w72RcL8dxgdzv4PnHLAKf-Fk80HoBUDrfkqeBkTUd8EC2hMbNBpXtYtJySQNQ0PqPioMR4lSH_nOkwUPq9eQUUxmQWkViOZtUN-UwPVHk8dq0Y7CvH9uf3-E9wfrmuk1A)\n\nThe dynamic_nodes and dynamic_attributes are the parts of the rsx that can change at runtime:\n\n[![](https://mermaid.ink/img/pako:eNp1UcFOwzAM_RXLVzZpvUbighDiABfgtkxTlnirtSaZUgc0df130hZEEcwny35-79nu0EZHqHDfxA9bmyTw9KIDlGjz7pDMqQZ3DsazhVCQ7dQbwnEiKxwDvN3NqhN4O4C3q_VaIztYKXjkQ7184HcCG3MQSgq6Mes1bjbTPAV3RdqIJN5l-V__2_Fcf5iY68dgG7ZHBT4WD5ftZfIBN7dQ_Tj4w1B9MVTXGZa_GMYdcIGekjfsymW7oaFRavKkUZXUmXTUqENfcCZLfD0Hi0pSpgXmkzNC92zKATyqvWnaUiXHEtPz9KrxY_0nzYOPmA?type=png)](https://mermaid.live/edit#pako:eNp1UcFOwzAM_RXLVzZpvUbighDiABfgtkxTlnirtSaZUgc0df130hZEEcwny35-79nu0EZHqHDfxA9bmyTw9KIDlGjz7pDMqQZ3DsazhVCQ7dQbwnEiKxwDvN3NqhN4O4C3q_VaIztYKXjkQ7184HcCG3MQSgq6Mes1bjbTPAV3RdqIJN5l-V__2_Fcf5iY68dgG7ZHBT4WD5ftZfIBN7dQ_Tj4w1B9MVTXGZa_GMYdcIGekjfsymW7oaFRavKkUZXUmXTUqENfcCZLfD0Hi0pSpgXmkzNC92zKATyqvWnaUiXHEtPz9KrxY_0nzYOPmA)\n\n## Launching the App\n\nThe app is launched by calling the `launch` function with the root component. Internally, this function will create a new web view using [wry](https://docs.rs/wry/latest/wry/) and create a virtual dom with the root component (`fn app()` in the readme example). This guide will not explain the renderer in-depth, but you can read more about it in the [custom renderer](../cookbook/custom_renderer.md) section.\n\n## The Virtual DOM\n\nBefore we dive into the initial render in the virtual DOM, we need to discuss what the virtual DOM is. The virtual DOM is a representation of the DOM that is used to diff the current DOM from the new DOM. This diff is then used to create a list of mutations that need to be applied to the DOM to bring it into sync with the virtual DOM.\n\nThe Virtual DOM roughly looks like this:\n\n````rust, no_run\npub struct VirtualDom {\n // All the templates that have been created or set during hot reloading\n pub(crate) templates: FxHashMap>>,\n\n // A slab of all the scopes that have been created\n pub(crate) scopes: ScopeSlab,\n\n // All scopes that have been marked as dirty\n pub(crate) dirty_scopes: BTreeSet,\n\n // Every element is actually a dual reference - one to the template and the other to the dynamic node in that template\n pub(crate) elements: Slab,\n\n // This receiver is used to receive messages from hooks about what scopes need to be marked as dirty\n pub(crate) rx: futures_channel::mpsc::UnboundedReceiver,\n\n // The changes queued up to be sent to the renderer\n pub(crate) mutations: Mutations<'static>,\n}\n````\n\n > \n > What is a [slab](https://docs.rs/slab/latest/slab/)?\n > \n > A slab acts like a hashmap with integer keys if you don't care about the value of the keys. It is internally backed by a dense vector which makes it more efficient than a hashmap. When you insert a value into a slab, it returns an integer key that you can use to retrieve the value later.\n\n > \n > How does Dioxus use slabs?\n > \n > Dioxus uses \"synchronized slabs\" to communicate between the renderer and the VDOM. When a node is created in the Virtual DOM, an (elementId, mutation) pair is passed to the renderer to identify that node, which the renderer will then render in actual DOM. These ids are also used by the Virtual Dom to reference that node in future mutations, like setting an attribute on a node or removing a node. When the renderer sends an event to the Virtual Dom, it sends the ElementId of the node that the event was triggered on. The Virtual DOM uses this id to find that node in the slab and then run the necessary event handlers.\n\nThe virtual DOM is a tree of scopes. A new `Scope` is created for every component when it is first rendered and recycled when the component is unmounted.\n\nScopes serve three main purposes:\n\n1. They store the state of hooks used by the component\n1. They store the state for the context API (for example: using\n [use_shared_state_provider](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_shared_state_provider.html)).\n1. They store the current and previous versions of the `VNode` that was rendered, so they can be\n diffed to generate the set of mutations needed to re-render it.\n\n### The Initial Render\n\nThe root scope is created and rebuilt:\n\n1. The root component is run\n1. The root component returns a `VNode`\n1. Mutations for this `VNode` are created and added to the mutation list (this may involve creating new child components)\n1. The `VNode` is stored in the root's `Scope`.\n\nAfter the root's `Scope` is built, all generated mutations are sent to the renderer, which applies them to the DOM.\n\nAfter the initial render, the root `Scope` looks like this:\n\n[![](https://mermaid.ink/img/pako:eNqtVE1P4zAQ_SuzPrWikRpWXCLtBRDisItWsOxhCaqM7RKricdyJrQV8N93QtvQNCkfEnOynydv3nxkHoVCbUQipjnOVSYDwc_L1AFbWd3dB-kzuEQkuFLoDUwDFkCZAek9nGDh0RlHK__atA1GkUUHf45f0YbppAqB_aOzIAvz-t7-chN_Y-1bw1WSJKsglIu2w9tktWXxIIuHURT5XCqTYa5NmDguw2R8c5MKq2GcgF46WTB_jafi9rZL0yi5q4jQTSrf9altO4okCn1Ratwyz55Qxuku2ITlTMgs6HCQimsPmb3PvqVi-L5gjXP3QcnxWnL8JZLrwGvR31n0KV-Bx6-r-oVkT_-3G1S-NQLbk9i8rj7udP2cixed2QcDCitHJiQw7ub3EVlNecrPjudG2-6soFO5VbMECmR9T5OnlUY4-AFxfw9aTFst3McU9TK1Otm6NEn_DubBYlX2_dglLXOz48FgwJmJ5lZTlhz6xWgNaFnyDgpymcARHO0W2a9J_l5w2wYXvHuGPcqaQ-rESBQmFNJq3nCPNZoK3l4sUSR81DLMUpG6Z_aTFeHV0imRUKjMSFReSzKnVnKGhUimMi8ZNdoShl-rlfmyOUfCS_cPcePz_B_Wl4pc?type=png)](https://mermaid.live/edit#pako:eNqtVE1P4zAQ_SuzPrWikRpWXCLtBRDisItWsOxhCaqM7RKricdyJrQV8N93QtvQNCkfEnOynydv3nxkHoVCbUQipjnOVSYDwc_L1AFbWd3dB-kzuEQkuFLoDUwDFkCZAek9nGDh0RlHK__atA1GkUUHf45f0YbppAqB_aOzIAvz-t7-chN_Y-1bw1WSJKsglIu2w9tktWXxIIuHURT5XCqTYa5NmDguw2R8c5MKq2GcgF46WTB_jafi9rZL0yi5q4jQTSrf9altO4okCn1Ratwyz55Qxuku2ITlTMgs6HCQimsPmb3PvqVi-L5gjXP3QcnxWnL8JZLrwGvR31n0KV-Bx6-r-oVkT_-3G1S-NQLbk9i8rj7udP2cixed2QcDCitHJiQw7ub3EVlNecrPjudG2-6soFO5VbMECmR9T5OnlUY4-AFxfw9aTFst3McU9TK1Otm6NEn_DubBYlX2_dglLXOz48FgwJmJ5lZTlhz6xWgNaFnyDgpymcARHO0W2a9J_l5w2wYXvHuGPcqaQ-rESBQmFNJq3nCPNZoK3l4sUSR81DLMUpG6Z_aTFeHV0imRUKjMSFReSzKnVnKGhUimMi8ZNdoShl-rlfmyOUfCS_cPcePz_B_Wl4pc)\n\n### Waiting for Events\n\nThe Virtual DOM will only ever re-render a `Scope` if it is marked as dirty. Each hook is responsible for marking the `Scope` as dirty if the state has changed. Hooks can mark a scope as dirty by sending a message to the Virtual Dom's channel. You can see the [implementations](https://github.com/DioxusLabs/dioxus/tree/master/packages/hooks) for the hooks dioxus includes by default on how this is done. Calling `needs_update()` on a hook will also cause it to mark its scope as dirty.\n\nThere are generally two ways a scope is marked as dirty:\n\n1. The renderer triggers an event: An event listener on this event may be called, which may mark a\n component as dirty, if processing the event resulted in any generated any mutations.\n1. The renderer calls\n [`wait_for_work`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.VirtualDom.html#method.wait_for_work):\n This polls dioxus internal future queue. One of these futures may mark a component as dirty.\n\nOnce at least one `Scope` is marked as dirty, the renderer can call [`render_with_deadline`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.VirtualDom.html#method.render_with_deadline) to diff the dirty scopes.\n\n### Diffing Scopes\n\nWhen a user clicks the \"up high\" button, the root `Scope` will be marked as dirty by the `use_state` hook. The desktop renderer will then call `render_with_deadline`, which will diff the root `Scope`.\n\nTo start the diffing process, the component function is run. After the root component is run it, the root `Scope` will look like this:\n\n[![](https://mermaid.ink/img/pako:eNrFVlFP2zAQ_iuen0BrpCaIl0i8AEJ72KQJtpcRFBnbJVYTn-U4tBXw33dpG5M2CetoBfdkny_ffb67fPIT5SAkjekkhxnPmHXk-3WiCVpZ3T9YZjJyDeDIDQcjycRCQVwmCTOGXEBhQEvtVvG1CWUldwo0-XX-6vVIF5W1GB9cWVbI1_PNL5v8jW3uPFbpmFOc2HK-GfA2WG1ZeJSFx0EQmJxxmUEupE01liEd394mVAkyjolYaFYgfu1P6N1dF8Yzua-cA51WphtTWzsLc872Zan9CnEGUkktuk6fFm_i5NxFRwn9bUimHrIvCT3-N2EBM70j5XBNOTwI5TrxmvQJkr7ELcHx67Jeggz0v92g8q0RaE-iP1193On6NyxecKUeJeFQaSdtTMLu_Xah5ctT_u94Nty2ZwU0zxWfxqQA5PecPq84kq9nfRw7SK0WDiEFZ4O37d34S_-08lFBVfb92KVb5HIrAp0WpjKYKeGyODLz0dohWIkaZNkiJqfkdLvIH6oRaTSoEmm0n06k0a5K0ZdpL61Io0Yt0nfpxc7UQ0_9cJrhyZ8syX-6brS706Mc489Vjja7fbWj3cxDqIdfJJqOaCFtwZTAV8hT7U0ovjBQRmiMS8HsNKGJfsE4Vjm4WWhOY2crOaKVEczJS8WwgAWNJywv0SuFcmB_rJ41y9fNiBqm_wA0MS9_AUuAiy0?type=png)](https://mermaid.live/edit#pako:eNrFVlFP2zAQ_iuen0BrpCaIl0i8AEJ72KQJtpcRFBnbJVYTn-U4tBXw33dpG5M2CetoBfdkny_ffb67fPIT5SAkjekkhxnPmHXk-3WiCVpZ3T9YZjJyDeDIDQcjycRCQVwmCTOGXEBhQEvtVvG1CWUldwo0-XX-6vVIF5W1GB9cWVbI1_PNL5v8jW3uPFbpmFOc2HK-GfA2WG1ZeJSFx0EQmJxxmUEupE01liEd394mVAkyjolYaFYgfu1P6N1dF8Yzua-cA51WphtTWzsLc872Zan9CnEGUkktuk6fFm_i5NxFRwn9bUimHrIvCT3-N2EBM70j5XBNOTwI5TrxmvQJkr7ELcHx67Jeggz0v92g8q0RaE-iP1193On6NyxecKUeJeFQaSdtTMLu_Xah5ctT_u94Nty2ZwU0zxWfxqQA5PecPq84kq9nfRw7SK0WDiEFZ4O37d34S_-08lFBVfb92KVb5HIrAp0WpjKYKeGyODLz0dohWIkaZNkiJqfkdLvIH6oRaTSoEmm0n06k0a5K0ZdpL61Io0Yt0nfpxc7UQ0_9cJrhyZ8syX-6brS706Mc489Vjja7fbWj3cxDqIdfJJqOaCFtwZTAV8hT7U0ovjBQRmiMS8HsNKGJfsE4Vjm4WWhOY2crOaKVEczJS8WwgAWNJywv0SuFcmB_rJ41y9fNiBqm_wA0MS9_AUuAiy0)\n\nNext, the Virtual DOM will compare the new VNode with the previous VNode and only update the parts of the tree that have changed. Because of this approach, when a component is re-rendered only the parts of the tree that have changed will be updated in the DOM by the renderer.\n\nThe diffing algorithm goes through the list of dynamic attributes and nodes and compares them to the previous VNode. If the attribute or node has changed, a mutation that describes the change is added to the mutation list.\n\nHere is what the diffing algorithm looks like for the root `Scope` (red lines indicate that a mutation was generated, and green lines indicate that no mutation was generated)\n\n[![](https://mermaid.ink/img/pako:eNrFlFFPwjAQx7_KpT7Kko2Elya8qCE-aGLAJ5khpe1Yw9Zbug4k4He3OJjbGPig0T5t17tf_nf777aEo5CEkijBNY-ZsfAwDjW4kxfzhWFZDGNECxOOmYTIYAo2lsCyDG4xzVBLbcv8_RHKSG4V6orSIN0Wxrh8b2RYKr_uTyubd1W92GiWKg7aac6bOU3G803HbVk82xfP_Ok0JEqAT-FeLWJvpFYSOBbaSkMhCMnra5MgtfhWFrPWqHlhL2urT6atbU-oa0PNE8WXFFJ0-nazXakRroddGk9IwYEUnCd5w7Pddr5UTT8ZuVJY5F0fM7ebRLYyXNDgUnprJWxM-9lb7xAQLHe-M2xDYQCD9pD_2hez_kVn-P_rjLq6n3qjYv2iO5qz9DyvPdyv1ETp5eTTJ_7BGvQq8v1TVtl5jXUcRRcrqFh-dI4VtFlBN6t_ynLNkh5JpUmZEm5rbvfhkLiN6H4BQt2jYGYZklC_uzxWWJxsNCfUmkL2SJEJZuWdYs4cKaERS3IXlUJZNI_lGv7cxj2SMf2CeMx5_wBcbK19?type=png)](https://mermaid.live/edit#pako:eNrFlFFPwjAQx7_KpT7Kko2Elya8qCE-aGLAJ5khpe1Yw9Zbug4k4He3OJjbGPig0T5t17tf_nf777aEo5CEkijBNY-ZsfAwDjW4kxfzhWFZDGNECxOOmYTIYAo2lsCyDG4xzVBLbcv8_RHKSG4V6orSIN0Wxrh8b2RYKr_uTyubd1W92GiWKg7aac6bOU3G803HbVk82xfP_Ok0JEqAT-FeLWJvpFYSOBbaSkMhCMnra5MgtfhWFrPWqHlhL2urT6atbU-oa0PNE8WXFFJ0-nazXakRroddGk9IwYEUnCd5w7Pddr5UTT8ZuVJY5F0fM7ebRLYyXNDgUnprJWxM-9lb7xAQLHe-M2xDYQCD9pD_2hez_kVn-P_rjLq6n3qjYv2iO5qz9DyvPdyv1ETp5eTTJ_7BGvQq8v1TVtl5jXUcRRcrqFh-dI4VtFlBN6t_ynLNkh5JpUmZEm5rbvfhkLiN6H4BQt2jYGYZklC_uzxWWJxsNCfUmkL2SJEJZuWdYs4cKaERS3IXlUJZNI_lGv7cxj2SMf2CeMx5_wBcbK19)\n\n## Conclusion\n\nThis is only a brief overview of how the Virtual Dom works. There are several aspects not yet covered in this guide including:\n\n* How the Virtual DOM handles async-components\n* Keyed diffing\n* Using [bump allocation](https://github.com/fitzgen/bumpalo) to efficiently allocate VNodes.\n\nIf you need more information about the Virtual Dom, you can read the code of the [core](https://github.com/DioxusLabs/dioxus/tree/master/packages/core) crate or reach out to us on [Discord](https://discord.gg/XgGxMSkvUM)." + } + 52usize => { + "# Static Generation\n\n## Getting the Sitemap\n\nThe \\[`Routable`\\] trait includes an associated \\[`SITE_MAP`\\] constant that contains the map of all of the routes in the enum.\n\nBy default, the sitemap is a tree of (static or dynamic) RouteTypes, but it can be flattened into a list of individual routes with the `.flatten()` method.\n\n## Generating a Sitemap\n\nTo statically render pages, we need to flatten the route tree and generate a file for each route that contains only static segments:\n\n````rust@static_generation.rs\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\nuse dioxus_ssr::incremental::{DefaultRenderer, IncrementalRendererConfig};\n\n#[tokio::main]\nasync fn main() {\n let mut renderer = IncrementalRendererConfig::new()\n .static_dir(\"./static\")\n .build();\n\n println!(\n \"SITE MAP:\\n{}\",\n Route::SITE_MAP\n .iter()\n .flat_map(|route| route.flatten().into_iter())\n .map(|route| {\n route\n .iter()\n .map(|segment| segment.to_string())\n .collect::>()\n .join(\"\")\n })\n .collect::>()\n .join(\"\\n\")\n );\n\n pre_cache_static_routes::(\n &mut renderer,\n &DefaultRenderer {\n before_body: r#\"\n \n \n \n \n Dioxus Application\n \n \"#\n .to_string(),\n after_body: r#\"\n \"#\n .to_string(),\n },\n )\n .await\n .unwrap();\n}\n\n#[component]\nfn Blog(cx: Scope) -> Element {\n render! {\n div {\n \"Blog\"\n }\n }\n}\n\n#[component]\nfn Post(cx: Scope, id: usize) -> Element {\n render! {\n div {\n \"PostId: {id}\"\n }\n }\n}\n\n#[component]\nfn PostHome(cx: Scope) -> Element {\n render! {\n div {\n \"Post\"\n }\n }\n}\n\n#[component]\nfn Home(cx: Scope) -> Element {\n render! {\n div {\n \"Home\"\n }\n }\n}\n\n#[rustfmt::skip]\n#[derive(Clone, Debug, PartialEq, Routable)]\nenum Route {\n #[nest(\"/blog\")]\n #[route(\"/\")]\n Blog {},\n #[route(\"/post/index\")]\n PostHome {},\n #[route(\"/post/:id\")]\n Post {\n id: usize,\n },\n #[end_nest]\n #[route(\"/\")]\n Home {},\n}\n\n````\n\n## Example\n\n* [examples/static-hydrated](https://github.com/DioxusLabs/dioxus/tree/master/packages%2Ffullstack%2Fexamples%2Fstatic-hydrated)" + } + 8usize => { + "# Terminal UI\n\nYou can build a text-based interface that will run in the terminal using Dioxus.\n\n![Hello World screenshot](https://github.com/DioxusLabs/rink/raw/master/examples/example.png)\n\n > \n > 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.\n\n## Support\n\nTUI support is currently quite experimental. But, if you're willing to venture into the realm of the unknown, this guide will get you started.\n\n* It uses flexbox for the layout\n* It only supports a subset of the attributes and elements\n* Regular widgets will not work in the tui render, but the tui renderer has its own widget components that start with a capital letter. See the [widgets example](https://github.com/DioxusLabs/dioxus/blob/master/packages/dioxus-tui/examples/widgets.rs)\n* 1px is one character line height. Your regular CSS px does not translate\n* If your app panics, your terminal is wrecked. This will be fixed eventually\n\n## Getting Set up\n\nStart by making a new package and adding Dioxus and the TUI renderer as dependencies.\n\n````shell\ncargo new --bin demo\ncd demo\ncargo add dioxus\ncargo add dioxus-tui\n````\n\nThen, edit your `main.rs` with the basic template.\n\n````rust@hello_world_tui.rs\n#![allow(non_snake_case)]\n// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types\nuse dioxus::prelude::*;\n\nfn main() {\n // launch the app in the terminal\n dioxus_tui::launch(App);\n}\n\n// create a component that renders a div with the text \"Hello, world!\"\nfn App(cx: Scope) -> Element {\n cx.render(rsx! {\n div {\n \"Hello, world!\"\n }\n })\n}\n\n````\n\nTo run our app:\n\n````shell\ncargo run\n````\n\nPress \"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.\n\n````rust@hello_world_tui_no_ctrl_c.rs\n// todo remove deprecated\n#![allow(non_snake_case, deprecated)]\n\nuse dioxus::events::{KeyCode, KeyboardEvent};\nuse dioxus::prelude::*;\nuse dioxus_tui::TuiContext;\n\nfn main() {\n dioxus_tui::launch_cfg(\n App,\n dioxus_tui::Config::new()\n .without_ctrl_c_quit()\n // Some older terminals only support 16 colors or ANSI colors\n // If your terminal is one of these, change this to BaseColors or ANSI\n .with_rendering_mode(dioxus_tui::RenderingMode::Rgb),\n );\n}\n\nfn App(cx: Scope) -> Element {\n let tui_ctx: TuiContext = cx.consume_context().unwrap();\n\n cx.render(rsx! {\n div {\n width: \"100%\",\n height: \"10px\",\n background_color: \"red\",\n justify_content: \"center\",\n align_items: \"center\",\n onkeydown: move |k: KeyboardEvent| if let KeyCode::Q = k.key_code {\n tui_ctx.quit();\n },\n\n \"Hello world!\"\n }\n })\n}\n\n````\n\n## Hot Reload\n\n1. Hot reloading allows much faster iteration times inside of rsx calls by interpreting them and streaming the edits.\n1. It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program.\n\n### Setup\n\nInstall [dioxus-cli](https://github.com/DioxusLabs/dioxus/tree/master/packages/cli).\n\n### Usage\n\n1. Run:\n\n````bash\ndx serve --hot-reload --platform desktop\n````\n\n2. Change some code within a rsx or render macro\n2. Save and watch the style change without recompiling\n\n### Limitations\n\n1. The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will require a full recompile to capture the expression.\n1. Components, Iterators, and some attributes can contain arbitrary rust code and will trigger a full recompile when changed." + } + 35usize => { + "# Authentication\n\nYou can use [extractors](./extractors.md) to integrate auth with your Fullstack application.\n\nYou can create a custom extractors that extracts the auth session from the request. From that auth session, you can check if the user has the required privileges before returning the private data.\n\nA [full auth example](https://github.com/dioxuslabs/dioxus/blob/master/packages/fullstack/examples/axum-auth/src/main.rs) with the complete implementation is available in the fullstack examples." + } + 79usize => { + "# 0.3 Migration Guide\n\nThis guide will outline the API changes between the `0.3` and `0.4` releases. The two major breaking changes in this release are how hot reloading works on desktop platforms and how the router works:\n\n* [Hot reload](hot_reload.md)\n* [Router](router.md)" + } + 33usize => { + "# Extractors\n\nServer functions are an ergonomic way to call a function on the server. Server function work by registering an endpoint on the server and using requests on the client. Most of the time, you shouldn't need to worry about how server functions operate, but there are some times when you need to get some value from the request other than the data passed in the server function.\n\nFor example, requests contain information about the user's browser (called the [user agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent)). We can use an extractor to retrieve that information.\n\nYou can use the `extract` method within a server function to extract something from the request. You can extract any type that implements `FromServerContext` (or when axum is enabled, you can use axum extractors directly):\n\n````rust@server_function_extract.rs\n#[server]\npub async fn log_user_agent() -> Result<(), ServerFnError> {\n let axum::TypedHeader(user_agent): axum::TypedHeader =\n extract().await?;\n log::info!(\"{:?}\", user_agent);\n Ok(())\n}\n````" + } + 74usize => { + "# Contributing\n\nDevelopment 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 (but first check if someone hasn't [done it already](https://github.com/DioxusLabs/dioxus/issues)).\n\n[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.\n\n## Improving Docs\n\nIf 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.\n\n## Working on the Ecosystem\n\nPart 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 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. Once you are done, add your library to the [awesome dioxus](https://github.com/DioxusLabs/awesome-dioxus) list or share it in the `#I-made-a-thing` channel on [Discord](https://discord.gg/XgGxMSkvUM).\n\n## Bugs & Features\n\nIf you've fixed [an open issue](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!\n\nAll pull requests (including those made by a team member) must be approved by at least one other team member.\nLarger, more nuanced decisions about design, architecture, breaking changes, trade-offs, etc. are made by team consensus.\n\n## Before you contribute\n\nYou might be surprised that a lot of checks fail when making your first PR.\nThat's why you should first run these commands before contributing, and it will save you *lots* of time, because the\nGitHub CI is much slower at executing all of these than your PC.\n\n* Format code with [rustfmt](https://github.com/rust-lang/rustfmt):\n\n````sh\ncargo fmt --all\n````\n\n* Check all code [cargo check](https://doc.rust-lang.org/cargo/commands/cargo-check.html):\n\n````sh\ncargo check --workspace --examples --tests\n````\n\n* Check if [Clippy](https://doc.rust-lang.org/clippy/) generates any warnings. Please fix these!\n\n````sh\ncargo clippy --workspace --examples --tests -- -D warnings\n````\n\n* Test all code with [cargo-test](https://doc.rust-lang.org/cargo/commands/cargo-test.html):\n\n````sh\ncargo test --all --tests\n````\n\n* More tests, this time with [cargo-make](https://sagiegurari.github.io/cargo-make/). Here are all steps, including installation:\n\n````sh\ncargo install --force cargo-make\ncargo make tests\n````\n\n* Test unsafe crates with [MIRI](https://github.com/rust-lang/miri). Currently, this is used for the two MIRI tests in `dioxus-core` and `dioxus-native-core`:\n\n````sh\ncargo miri test --package dioxus-core --test miri_stress\ncargo miri test --package dioxus-native-core --test miri_native\n````\n\n* Test with Playwright. This tests the UI itself, right in a browser. Here are all steps, including installation:\n **Disclaimer: This might inexplicably fail on your machine without it being your fault.** Make that PR anyway!\n\n````sh\ncd playwright-tests\nnpm ci\nnpm install -D @playwright/test\nnpx playwright install --with-deps\nnpx playwright test\n````\n\n## How to test dioxus with local crate\n\nIf you are developing a feature, you should test it in your local setup before raising a PR. This process makes sure you are aware of your code functionality before being reviewed by peers.\n\n* Fork the following github repo (DioxusLabs/dioxus):\n\n`https://github.com/DioxusLabs/dioxus`\n\n* Create a new or use an existing rust crate (ignore this step if you will use an existing rust crate):\n This is where we will be testing the features of the forked\n\n````sh\ncargo new --bin demo\n````\n\n* Add the dioxus dependencies for your rust crate (new/existing) in cargo.toml:\n\n````toml\ndioxus = { path = \"/dioxus/packages/dioxus/\" }\n\ndioxus-web = { path = \"/dioxus/packages/web/\" }\n````\n\nThis above example is for dioxus-web. To know about the dependencies for different renderer visit [here](https://dioxuslabs.com/learn/0.4/getting_started).\n\n* Run and test your feature\n\n````sh\ndx serve\n````\n\nIf this is your first time with dioxus, please read [the guide](https://dioxuslabs.com/learn/0.4/guide) to get familiar with dioxus." + } + 68usize => { + "# Optimizing\n\n*Note: This is written primarily for the web, but the main optimizations will work on other platforms too.*\n\nYou might have noticed that Dioxus binaries are pretty big.\nThe WASM binary of a [TodoMVC app](https://github.com/tigerros/dioxus-todo-app) weighs in at 2.36mb!\nDon't worry; we can get it down to a much more manageable 234kb.\nThis will get obviously lower over time.\nFor example, the new [event system](https://github.com/DioxusLabs/dioxus/pull/1402) will reduce the binary size of a hello world app to less than 100kb (with unstable features).\n\nWe will also discuss ways to optimize your app for increased speed.\n\nHowever, certain optimizations will sacrifice speed for decreased binary size or the other way around.\nThat's what you need to figure out yourself. Does your app perform performance-intensive tasks, such as graphical processing or tons of DOM manipulations?\nYou could go for increased speed. In most cases, though, decreased binary size is the better choice, especially because Dioxus WASM binaries are quite large.\n\nTo test binary sizes, we will use [this](https://github.com/tigerros/dioxus-todo-app) repository as a sample app.\nThe `no-optimizations` package will serve as the base, which weighs 2.36mb as of right now.\n\nAdditional resources:\n\n* [WASM book - Shrinking `.wasm` code size](https://rustwasm.github.io/docs/book/reference/code-size.html)\n* [min-sized-rust](https://github.com/johnthagen/min-sized-rust)\n\n## Building in release mode\n\nThis is the best way to optimize. In fact, the 2.36mb figure at the start of the guide is with release mode.\nIn debug mode, it's actually a whopping 32mb! It also increases the speed of your app.\n\nThankfully, no matter what tool you're using to build your app, it will probably have a `--release` flag to do this.\n\nUsing the [Dioxus CLI](https://dioxuslabs.com/learn/0.4/CLI) or [Trunk](https://trunkrs.dev/):\n\n* Dioxus CLI: `dx build --release`\n* Trunk: `trunk build --release`\n\n## UPX\n\nIf you're not targeting web, you can use the [UPX](https://github.com/upx/upx) CLI tool to compress your executables.\n\nSetup:\n\n* Download a [release](https://github.com/upx/upx/releases) and extract the directory inside to a sensible location.\n* Add the executable located in the directory to your path variable.\n\nYou can run `upx --help` to get the CLI options, but you should also view `upx-doc.html` for more detailed information.\nIt's included in the extracted directory.\n\nAn example command might be: `upx --best -o target/release/compressed.exe target/release/your-executable.exe`.\n\n## Build configuration\n\n*Note: Settings defined in `.cargo/config.toml` will override settings in `Cargo.toml`.*\n\nOther than the `--release` flag, this is the easiest way to optimize your projects, and also the most effective way,\nat least in terms of reducing binary size.\n\n### Stable\n\nThis configuration is 100% stable and decreases the binary size from 2.36mb to 310kb.\nAdd this to your `.cargo/config.toml`:\n\n````toml\n[profile.release]\nopt-level = \"z\"\ndebug = false\nlto = true\ncodegen-units = 1\npanic = \"abort\"\nstrip = true\nincremental = false\n````\n\nLinks to the documentation of each value:\n\n* [`opt-level`](https://doc.rust-lang.org/rustc/codegen-options/index.html#opt-level)\n* [`debug`](https://doc.rust-lang.org/rustc/codegen-options/index.html#debuginfo)\n* [`lto`](https://doc.rust-lang.org/rustc/codegen-options/index.html#lto)\n* [`codegen-units`](https://doc.rust-lang.org/rustc/codegen-options/index.html#codegen-units)\n* [`panic`](https://doc.rust-lang.org/rustc/codegen-options/index.html#panic)\n* [`strip`](https://doc.rust-lang.org/rustc/codegen-options/index.html#strip)\n* [`incremental`](https://doc.rust-lang.org/rustc/codegen-options/index.html#incremental)\n\n### Unstable\n\nThis configuration contains some unstable features, but it should work just fine.\nIt decreases the binary size from 310kb to 234kb.\nAdd this to your `.cargo/config.toml`:\n\n````toml\n[unstable]\nbuild-std = [\"std\", \"panic_abort\", \"core\", \"alloc\"]\nbuild-std-features = [\"panic_immediate_abort\"]\n\n[build]\nrustflags = [\n \"-Clto\",\n \"-Zvirtual-function-elimination\",\n \"-Zlocation-detail=none\"\n]\n\n# Same as in the Stable section\n[profile.release]\nopt-level = \"z\"\ndebug = false\nlto = true\ncodegen-units = 1\npanic = \"abort\"\nstrip = true\nincremental = false\n````\n\n*Note: The omitted space in each flag (e.g., `-Clto`) is intentional. It is not a typo.*\n\nThe values in `[profile.release]` are documented in the [Stable](#stable) section. Links to the documentation of each value:\n\n* [`[build.rustflags]`](https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags)\n* [`-C lto`](https://doc.rust-lang.org/rustc/codegen-options/index.html#lto)\n* [`-Z virtual-function-elimination`](https://doc.rust-lang.org/stable/unstable-book/compiler-flags/virtual-function-elimination.html)\n* [`-Z location-detail`](https://doc.rust-lang.org/stable/unstable-book/compiler-flags/location-detail.html)\n\n## wasm-opt\n\n*Note: In the future, `wasm-opt` will be supported natively through the [Dioxus CLI](https://crates.io/crates/dioxus-cli).*\n\n`wasm-opt` is a tool from the [binaryen](https://github.com/WebAssembly/binaryen) library that optimizes your WASM files.\nTo use it, install a [binaryen release](https://github.com/WebAssembly/binaryen/releases) and run this command from the package directory:\n\n````\nwasm-opt dist/assets/dioxus/APP_NAME_bg.wasm -o dist/assets/dioxus/APP_NAME_bg.wasm -Oz\n````\n\nThe `-Oz` flag specifies that `wasm-opt` should optimize for size. For speed, use `-O4`.\n\n## Improving Dioxus code\n\nLet's talk about how you can improve your Dioxus code to be more performant.\n\nIt's important to minimize the number of dynamic parts in your `rsx`, like conditional rendering.\nWhen Dioxus is rendering your component, it will skip parts that are the same as the last render.\nThat means that if you keep dynamic rendering to a minimum, your app will speed up, and quite a bit if it's not just hello world.\nTo see an example of this, check out [Dynamic Rendering](../reference/dynamic_rendering.md).\n\nAlso check out [Anti-patterns](antipatterns.md) for patterns that you should avoid.\nObviously, not all of them are just about performance, but some of them are.\n\n## Bundling and minifying the output JS and HTML\n\nThis will be added in [dioxus/#1369](https://github.com/DioxusLabs/dioxus/pull/1369)." + } + 65usize => { + "# Examples\n\nThere's a *lot* of these, so if you're having trouble implementing something, or you just want to see cool things\nthat you can do with Dioxus, make sure to give these a look!\n\nEach of the examples in the main repository also has a permalink attached, in case the main one doesn't work.\nHowever, permalinks lead to an older \"archived\" version, so if you're reading this a long time in the future in a galaxy far, far away...\nThe examples in the permalinks might not work.\n\n* [Main list](https://github.com/DioxusLabs/dioxus/tree/master/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/examples) - This is the largest list.\n* Package-specific examples from the [main repository](https://github.com/DioxusLabs/dioxus/). To learn more about these packages, search them up on [crates.io](https://crates.io/), or navigate from the examples to the root of the package.\n * [dioxus-web](https://github.com/DioxusLabs/dioxus/tree/master/packages/web/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/web/examples)\n * [dioxus-fullstack](https://github.com/DioxusLabs/dioxus/tree/master/packages/fullstack/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/fullstack/examples)\n * [dioxus-liveview](https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/liveview/examples)\n * [dioxus-router](https://github.com/DioxusLabs/dioxus/tree/master/packages/router/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/router/examples)\n * [dioxus-tui](https://github.com/DioxusLabs/dioxus/tree/master/packages/dioxus-tui/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/dioxus-tui/examples)\n * [plasmo (\"rink\" is the old name, it will be updated)](https://github.com/DioxusLabs/dioxus/tree/master/packages/rink/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/rink/examples)\n * [rsx-rosetta](https://github.com/DioxusLabs/dioxus/tree/master/packages/rsx-rosetta/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/rsx-rosetta/examples)\n * [native-core](https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/native-core/examples)\n * [signals](https://github.com/DioxusLabs/dioxus/tree/master/packages/signals/examples) - [permalink](https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/signals/examples) - This is unreleased, but it's a very exciting project, so stay tuned!\n* The [example-projects](https://github.com/DioxusLabs/example-projects) repository. It might be deprecated/removed in the future though. See [\\#25](https://github.com/DioxusLabs/example-projects/issues/25)." + } + 31usize => { + "# Fullstack development\n\nDioxus Fullstack contains helpers for:\n\n* Incremental, static, and server side rendering\n* Hydrating your application on the Client\n* Communicating between a server and a client\n\nThis guide will teach you everything you need to know about how to use the utilities in Dioxus fullstack to create amazing fullstack applications.\n\n > \n > In addition to this guide, you can find more examples of full-stack apps and information about how to integrate with other frameworks and desktop renderers in the [dioxus-fullstack examples directory](https://github.com/DioxusLabs/dioxus/tree/master/packages/fullstack/examples)." + } + 9usize => { + "In this guide, you'll learn to use Dioxus to build user interfaces that run anywhere. We will recreate the hackernews homepage in Dioxus:\n\n````inject-dioxus\nDemoFrame {\n hackernews_complete::App {}\n}\n````\n\nThis guide serves a very brief overview of Dioxus. Throughout the guide, there will be links to the [reference](../reference/index.md) with more details about specific concepts.\n\nFirst, lets setup our dependencies. In addition to the dependencies you added in the [getting started](../getting_started/index.md) guide for your platform, we need to set up a few more dependencies to work with the hacker news API:\n\n````sh\ncargo add chrono --features serde\ncargo add futures\ncargo add reqwest --features json\ncargo add serde --features derive\ncargo add serde_json\ncargo add async_recursion\n````" + } + 24usize => { + "# UseFuture\n\n[`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.\n\nFor example, we can make an API request (using [reqwest](https://docs.rs/reqwest/latest/reqwest/index.html)) inside `use_future`:\n\n````rust@use_future.rs\nlet future = use_future(cx, (), |_| async move {\n reqwest::get(\"https://dog.ceo/api/breeds/image/random\")\n .await\n .unwrap()\n .json::()\n .await\n});\n````\n\nThe code inside `use_future` will be submitted to the Dioxus scheduler once the component has rendered.\n\nWe 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.\n\nWe can then render that result:\n\n````rust@use_future.rs\ncx.render(match future.value() {\n Some(Ok(response)) => rsx! {\n button {\n onclick: move |_| future.restart(),\n \"Click to fetch another doggo\"\n }\n div {\n img {\n max_width: \"500px\",\n max_height: \"500px\",\n src: \"{response.image_url}\",\n }\n }\n },\n Some(Err(_)) => rsx! { div { \"Loading dogs failed\" } },\n None => rsx! { div { \"Loading dogs...\" } },\n})\n````\n\n````inject-dioxus\nDemoFrame {\n // original: use_future::App {}\n __interactive_04::use_future_ {}\n}\n````\n\n## Restarting the Future\n\nThe `UseFuture` handle provides a `restart` method. It can be used to execute the future again, producing a new value.\n\n## Dependencies\n\nOften, you will need to run the future again every time some value (e.g. a prop) changes. Rather than calling `restart` 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:\n\n````rust, no_run@use_future.rs\nlet future = use_future(cx, (breed,), |(breed,)| async move {\n reqwest::get(format!(\"https://dog.ceo/api/breed/{breed}/images/random\"))\n .await\n .unwrap()\n .json::()\n .await\n});\n````" + } + 22usize => { + "# Dynamic Rendering\n\nSometimes 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!\n\n## Conditional Rendering\n\nTo render different elements based on a condition, you could use an `if-else` statement:\n\n````rust, no_run@conditional_rendering.rs\nif *is_logged_in {\n cx.render(rsx! {\n \"Welcome!\"\n button {\n onclick: move |_| on_log_out.call(()),\n \"Log Out\",\n }\n })\n} else {\n cx.render(rsx! {\n button {\n onclick: move |_| on_log_in.call(()),\n \"Log In\",\n }\n })\n}\n````\n\n````inject-dioxus\nDemoFrame {\n conditional_rendering::App {}\n}\n````\n\n > \n > You could also use `match` statements, or any Rust function to conditionally render different things.\n\n### Improving the `if-else` Example\n\nYou may have noticed some repeated code in the `if-else` example above. Repeating code like this is both bad for maintainability and performance. Dioxus will skip diffing static elements like the button, but when switching between multiple `rsx` calls it cannot perform this optimization. For this example either approach is fine, but for components with large parts that are reused between conditionals, it can be more of an issue.\n\nWe can improve this example by splitting up the dynamic parts and inserting them where they are needed.\n\n````rust, no_run@conditional_rendering.rs\ncx.render(rsx! {\n // We only render the welcome message if we are logged in\n // You can use if statements in the middle of a render block to conditionally render elements\n if *is_logged_in {\n // Notice the body of this if statement is rsx code, not an expression\n \"Welcome!\"\n }\n button {\n // depending on the value of `is_logged_in`, we will call a different event handler\n onclick: move |_| if *is_logged_in {\n on_log_in.call(())\n }\n else{\n on_log_out.call(())\n },\n if *is_logged_in {\n // if we are logged in, the button should say \"Log Out\"\n \"Log Out\"\n } else {\n // if we are not logged in, the button should say \"Log In\"\n \"Log In\"\n }\n }\n})\n````\n\n````inject-dioxus\nDemoFrame {\n conditional_rendering::LogInImprovedApp {}\n}\n````\n\n### Inspecting `Element` props\n\nSince `Element` is a `Option`, components accepting `Element` as a prop can inspect its contents, and render different things based on that. Example:\n\n````rust, no_run@component_children_inspect.rs\nfn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {\n match cx.props.children {\n Some(VNode { dynamic_nodes, .. }) => {\n todo!(\"render some stuff\")\n }\n _ => {\n todo!(\"render some other stuff\")\n }\n }\n}\n````\n\nYou 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.\n\n## Rendering Nothing\n\nTo render nothing, you can return `None` from a component. This is useful if you want to conditionally hide something:\n\n````rust, no_run@conditional_rendering.rs\nif *is_logged_in {\n return None;\n}\n\ncx.render(rsx! {\n a {\n \"You must be logged in to comment\"\n }\n})\n````\n\n````inject-dioxus\nDemoFrame {\n conditional_rendering::LogInWarningApp {}\n}\n````\n\nThis works because the `Element` type is just an alias for `Option`\n\n > \n > 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.\n\n## Rendering Lists\n\nOften, you'll want to render a collection of components. For example, you might want to render a list of all comments on a post.\n\nFor this, Dioxus accepts iterators that produce `Element`s. So we need to:\n\n* Get an iterator over all of our items (e.g., if you have a `Vec` of comments, iterate over it with `iter()`)\n* `.map` the iterator to convert each item into a `LazyNode` using `rsx!(...)`\n * Add a unique `key` attribute to each iterator item\n* Include this iterator in the final RSX (or use it inline)\n\nExample: suppose you have a list of comments you want to render. Then, you can render them like this:\n\n````rust, no_run@rendering_lists.rs\nlet comment_field = use_state(cx, String::new);\nlet mut next_id = use_state(cx, || 0);\nlet comments = use_ref(cx, Vec::::new);\n\nlet comments_lock = comments.read();\nlet comments_rendered = comments_lock.iter().map(|comment| {\n rsx!(CommentComponent {\n key: \"{comment.id}\",\n comment: comment.clone(),\n })\n});\n\ncx.render(rsx!(\n form {\n onsubmit: move |_| {\n comments.write().push(Comment {\n content: comment_field.get().clone(),\n id: *next_id.get(),\n });\n next_id += 1;\n\n comment_field.set(String::new());\n },\n input {\n value: \"{comment_field}\",\n oninput: move |event| comment_field.set(event.value.clone()),\n }\n input {\n r#type: \"submit\",\n }\n },\n comments_rendered,\n))\n````\n\n````inject-dioxus\nDemoFrame {\n rendering_lists::App {}\n}\n````\n\n### Inline for loops\n\nBecause of how common it is to render a list of items, Dioxus provides a shorthand for this. Instead of using `.iter`, `.map`, and `rsx`, you can use a `for` loop with a body of rsx code:\n\n````rust, no_run@rendering_lists.rs\nlet comment_field = use_state(cx, String::new);\nlet mut next_id = use_state(cx, || 0);\nlet comments = use_ref(cx, Vec::::new);\n\ncx.render(rsx!(\n form {\n onsubmit: move |_| {\n comments.write().push(Comment {\n content: comment_field.get().clone(),\n id: *next_id.get(),\n });\n next_id += 1;\n\n comment_field.set(String::new());\n },\n input {\n value: \"{comment_field}\",\n oninput: move |event| comment_field.set(event.value.clone()),\n }\n input {\n r#type: \"submit\",\n }\n },\n for comment in &*comments.read() {\n // Notice the body of this for loop is rsx code, not an expression\n CommentComponent {\n key: \"{comment.id}\",\n comment: comment.clone(),\n }\n }\n))\n````\n\n````inject-dioxus\nDemoFrame {\n rendering_lists::AppForLoop {}\n}\n````\n\n### The key Attribute\n\nEvery time you re-render your list, Dioxus needs to keep track of which items go where to determine what updates need to be made to the UI.\n\nFor 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!\n\nTo 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 matter where you get the key from, as long as it meets the requirements:\n\n* Keys must be unique in a list\n* The same item should always get associated with the same key\n* 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\n\nYou might be tempted to use an item's index in the list as its key. 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.\n\n > \n > 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." + } + 21usize => { + "# Sharing State\n\nOften, multiple components need to access the same state. Depending on your needs, there are several ways to implement this.\n\n## Lifting State\n\nOne 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.\n\nSuppose 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).\n\n > \n > 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, maintainable, and performant (this is even more important for larger, complex apps).\n\nWe start with a `Meme` component, responsible for rendering a meme with a given caption:\n\n````rust, no_run@meme_editor.rs\n#[component]\nfn Meme<'a>(cx: Scope<'a>, caption: &'a str) -> Element<'a> {\n let container_style = r#\"\n position: relative;\n width: fit-content;\n \"#;\n\n let caption_container_style = r#\"\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 16px 8px;\n \"#;\n\n let caption_style = r\"\n font-size: 32px;\n margin: 0;\n color: white;\n text-align: center;\n \";\n\n cx.render(rsx!(\n div {\n style: \"{container_style}\",\n img {\n src: \"https://i.imgflip.com/2zh47r.jpg\",\n height: \"500px\",\n },\n div {\n style: \"{caption_container_style}\",\n p {\n style: \"{caption_style}\",\n \"{caption}\"\n }\n }\n }\n ))\n}\n````\n\n > \n > 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!\n\nWe 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:\n\n````rust, no_run@meme_editor.rs\n#[component]\nfn CaptionEditor<'a>(\n cx: Scope<'a>,\n caption: &'a str,\n on_input: EventHandler<'a, FormEvent>,\n) -> Element<'a> {\n let input_style = r\"\n border: none;\n background: cornflowerblue;\n padding: 8px 16px;\n margin: 0;\n border-radius: 4px;\n color: white;\n \";\n\n cx.render(rsx!(input {\n style: \"{input_style}\",\n value: \"{caption}\",\n oninput: move |event| on_input.call(event),\n }))\n}\n````\n\nFinally, a third component will render the other two as children. It will be responsible for keeping the state and passing down the relevant props.\n\n````rust, no_run@meme_editor.rs\nfn MemeEditor(cx: Scope) -> Element {\n let container_style = r\"\n display: flex;\n flex-direction: column;\n gap: 16px;\n margin: 0 auto;\n width: fit-content;\n \";\n\n let caption = use_state(cx, || \"me waiting for my rust code to compile\".to_string());\n\n cx.render(rsx! {\n div {\n style: \"{container_style}\",\n h1 { \"Meme Editor\" },\n Meme {\n caption: caption,\n },\n CaptionEditor {\n caption: caption,\n on_input: move |event: FormEvent| {caption.set(event.value.clone());},\n },\n }\n })\n}\n````\n\n![Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: \"me waiting for a language feature\"](/assets/static/meme_editor_screenshot.png)\n\n## Using Shared State\n\nSometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient.\n\nSuppose 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.\n\n > \n > 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!\n\nNow, 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!\n\nDioxus offers a better solution than this \"prop drilling\" – providing context. The [`use_shared_state_provider`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_shared_state_provider.html) hook is similar to `use_ref`, but it makes it available through [`use_shared_state`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_shared_state.html) for all children components.\n\nFirst, we have to create a struct for our dark mode configuration:\n\n````rust, no_run@meme_editor_dark_mode.rs\nstruct DarkMode(bool);\n````\n\nNow, in a top-level component (like `App`), we can provide the `DarkMode` context to all children components:\n\n````rust, no_run@meme_editor_dark_mode.rs\nuse_shared_state_provider(cx, || DarkMode(false));\n````\n\nAs a result, any child component of `App` (direct or not), can access the `DarkMode` context.\n\n````rust, no_run@meme_editor_dark_mode.rs\nlet dark_mode_context = use_shared_state::(cx);\n````\n\n > \n > `use_shared_state` 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`.\n\nFor 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):\n\n````rust, no_run@meme_editor_dark_mode.rs\npub fn DarkModeToggle(cx: Scope) -> Element {\n let dark_mode = use_shared_state::(cx).unwrap();\n\n let style = if dark_mode.read().0 {\n \"color:white\"\n } else {\n \"\"\n };\n\n cx.render(rsx!(label {\n style: \"{style}\",\n \"Dark Mode\",\n input {\n r#type: \"checkbox\",\n oninput: move |event| {\n let is_enabled = event.value == \"true\";\n dark_mode.write().0 = is_enabled;\n },\n },\n }))\n}\n````" + } + 10usize => { + "# Your First Component\n\nThis chapter will teach you how to create a [Component](../reference/components.md) that displays a link to a post on hackernews.\n\nFirst, let's define how to display a post. Dioxus is a *declarative* framework. This means that instead of telling Dioxus what to do (e.g. to \"create an element\" or \"set the color to red\") we simply *declare* how we want the UI to look.\n\nTo declare what you want your UI to look like, you will need to use the `rsx` macro. Let's modify the rsx macro in the `App` function from the [getting started](../getting_started/index.md) to show information about our story:\n\n````rust@hackernews_post.rs\npub fn App(cx: Scope) -> Element {\n render! {\n \"story\"\n }\n}\n````\n\nIf you run your application you should see something like this:\n\n````inject-dioxus\nDemoFrame {\n\thackernews_post::story_v1::App {}\n}\n````\n\n > \n > RSX mirrors HTML. Because of this you will need to know some html to use Dioxus.\n > \n > Here are some resources to help get you started learning HTML:\n > \n > * [MDN HTML Guide](https://developer.mozilla.org/en-US/docs/Learn/HTML)\n > * [W3 Schools HTML Tutorial](https://www.w3schools.com/html/default.asp)\n > \n > In addition to HTML, Dioxus uses CSS to style applications. You can either use traditional CSS (what this guide uses) or use a tool like [tailwind CSS](https://tailwindcss.com/docs/installation):\n > \n > * [MDN Traditional CSS Guide](https://developer.mozilla.org/en-US/docs/Learn/HTML)\n > * [W3 Schools Traditional CSS Tutorial](https://www.w3schools.com/css/default.asp)\n > * [Tailwind tutorial](https://tailwindcss.com/docs/installation) (used with the [Tailwind setup example](https://github.com/DioxusLabs/dioxus/tree/master/examples/tailwind))\n > \n > If you have existing html code, you can use the [translate](../CLI/translate.md) command to convert it to RSX. Or if you prefer to write html, you can use the [html! macro](https://github.com/DioxusLabs/dioxus-html-macro) to write html directly in your code.\n\n## Dynamic Text\n\nLet's expand our `App` component to include the story title, author, score, time posted, and number of comments. We can insert dynamic text in the render macro by inserting variables inside `{}`s (this works similarly to the formatting in the [println!](https://doc.rust-lang.org/std/macro.println.html) macro):\n\n````rust@hackernews_post.rs\npub fn App(cx: Scope) -> Element {\n let title = \"title\";\n let by = \"author\";\n let score = 0;\n let time = chrono::Utc::now();\n let comments = \"comments\";\n\n render! {\n \"{title} by {by} ({score}) {time} {comments}\"\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\thackernews_post::story_v2::App {}\n}\n````\n\n## Creating Elements\n\nNext, let's wrap our post description in a [`div`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div). You can create HTML elements in Dioxus by putting a `{` after the element name and a `}` after the last child of the element:\n\n````rust@hackernews_post.rs\npub fn App(cx: Scope) -> Element {\n let title = \"title\";\n let by = \"author\";\n let score = 0;\n let time = chrono::Utc::now();\n let comments = \"comments\";\n\n render! {\n div {\n \"{title} by {by} ({score}) {time} {comments}\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\thackernews_post::story_v3::App {}\n}\n````\n\n > \n > You can read more about elements in the [rsx reference](../reference/rsx.md).\n\n## Setting Attributes\n\nNext, let's add some padding around our post listing with an attribute.\n\nAttributes (and [listeners](../reference/event_handlers.md)) modify the behavior or appearance of the element they are attached to. They are specified inside the `{}` brackets before any children, using the `name: value` syntax. You can format the text in the attribute as you would with a text node:\n\n````rust@hackernews_post.rs\npub fn App(cx: Scope) -> Element {\n let title = \"title\";\n let by = \"author\";\n let score = 0;\n let time = chrono::Utc::now();\n let comments = \"comments\";\n\n render! {\n div {\n padding: \"0.5rem\",\n position: \"relative\",\n \"{title} by {by} ({score}) {time} {comments}\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\thackernews_post::story_v4::App {}\n}\n````\n\n > \n > Note: All attributes defined in [`dioxus-html`](https://docs.rs/dioxus-html/latest/dioxus_html/) follow the snake_case naming convention. They transform their `snake_case` names to HTML's `camelCase` attributes.\n\n > \n > Note: Styles can be used directly outside of the `style:` attribute. In the above example, `color: \"red\"` is turned into `style=\"color: red\"`.\n\n > \n > You can read more about elements in the [attribute reference](../reference/rsx.md)\n\n## Creating a Component\n\nJust 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, you should break down the functionality of an app in logical parts called components.\n\nA component is a Rust function, named in UpperCamelCase, that takes a `Scope` parameter and returns an `Element` describing the UI it wants to render. In fact, our `App` function is a component!\n\nLet's pull our story description into a new component:\n\n````rust@hackernews_post.rs\nfn StoryListing(cx: Scope) -> Element {\n let title = \"title\";\n let by = \"author\";\n let score = 0;\n let time = chrono::Utc::now();\n let comments = \"comments\";\n\n render! {\n div {\n padding: \"0.5rem\",\n position: \"relative\",\n \"{title} by {by} ({score}) {time} {comments}\"\n }\n }\n}\n````\n\nWe can render our component like we would an element by putting `{}`s after the component name. Let's modify our `App` component to render our new StoryListing component:\n\n````rust@hackernews_post.rs\npub fn App(cx: Scope) -> Element {\n render! {\n StoryListing {\n\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\thackernews_post::story_v5::App {}\n}\n````\n\n > \n > You can read more about elements in the [component reference](../reference/components.md)\n\n## Creating Props\n\nJust like you can pass arguments to a function or attributes to an element, you can pass props to a component that customize its behavior!\n\nWe can define arguments that components can take when they are rendered (called `Props`) by adding the `#[component]` macro before our function definition and adding extra function arguments.\n\nCurrently, our `StoryListing` component always renders the same story. We can modify it to accept a story to render as a prop.\n\nWe will also define what a post is and include information for how to transform our post to and from a different format using [serde](https://serde.rs). This will be used with the hackernews API in a later chapter:\n\n````rust@hackernews_post.rs\nuse chrono::{DateTime, Utc};\nuse serde::{Deserialize, Serialize};\n\n// Define the Hackernews types\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryPageData {\n #[serde(flatten)]\n pub item: StoryItem,\n #[serde(default)]\n pub comments: Vec,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct Comment {\n pub id: i64,\n /// there will be no by field if the comment was deleted\n #[serde(default)]\n pub by: String,\n #[serde(default)]\n pub text: String,\n #[serde(with = \"chrono::serde::ts_seconds\")]\n pub time: DateTime,\n #[serde(default)]\n pub kids: Vec,\n #[serde(default)]\n pub sub_comments: Vec,\n pub r#type: String,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryItem {\n pub id: i64,\n pub title: String,\n pub url: Option,\n pub text: Option,\n #[serde(default)]\n pub by: String,\n #[serde(default)]\n pub score: i64,\n #[serde(default)]\n pub descendants: i64,\n #[serde(with = \"chrono::serde::ts_seconds\")]\n pub time: DateTime,\n #[serde(default)]\n pub kids: Vec,\n pub r#type: String,\n}\n\n#[component]\nfn StoryListing(cx: Scope, story: StoryItem) -> Element {\n let StoryItem {\n title,\n by,\n score,\n time,\n kids,\n ..\n } = story;\n\n let comments = kids.len();\n\n render! {\n div {\n padding: \"0.5rem\",\n position: \"relative\",\n \"{title} by {by} ({score}) {time} {comments}\"\n }\n }\n}\n````\n\nNow, let's modify the `App` component to pass the story to our `StoryListing` component like we would set an attribute on an element:\n\n````rust@hackernews_post.rs\npub fn App(cx: Scope) -> Element {\n render! {\n StoryListing {\n story: StoryItem {\n id: 0,\n title: \"hello hackernews\".to_string(),\n url: None,\n text: None,\n by: \"Author\".to_string(),\n score: 0,\n descendants: 0,\n time: chrono::Utc::now(),\n kids: vec![],\n r#type: \"\".to_string(),\n }\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\thackernews_post::story_v6::App {}\n}\n````\n\n > \n > You can read more about Props in the [Props reference](../reference/component_props.md)\n\n## Cleaning Up Our Interface\n\nFinally, by combining elements and attributes, we can make our post listing much more appealing:\n\nFull code up to this point:\n\n````rust@hackernews_post.rs\nuse dioxus::prelude::*;\n\n// Define the Hackernews types\nuse chrono::{DateTime, Utc};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryPageData {\n #[serde(flatten)]\n pub item: StoryItem,\n #[serde(default)]\n pub comments: Vec,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct Comment {\n pub id: i64,\n /// there will be no by field if the comment was deleted\n #[serde(default)]\n pub by: String,\n #[serde(default)]\n pub text: String,\n #[serde(with = \"chrono::serde::ts_seconds\")]\n pub time: DateTime,\n #[serde(default)]\n pub kids: Vec,\n #[serde(default)]\n pub sub_comments: Vec,\n pub r#type: String,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryItem {\n pub id: i64,\n pub title: String,\n pub url: Option,\n pub text: Option,\n #[serde(default)]\n pub by: String,\n #[serde(default)]\n pub score: i64,\n #[serde(default)]\n pub descendants: i64,\n #[serde(with = \"chrono::serde::ts_seconds\")]\n pub time: DateTime,\n #[serde(default)]\n pub kids: Vec,\n pub r#type: String,\n}\n\npub fn App(cx: Scope) -> Element {\n render! {\n StoryListing {\n story: StoryItem {\n id: 0,\n title: \"hello hackernews\".to_string(),\n url: None,\n text: None,\n by: \"Author\".to_string(),\n score: 0,\n descendants: 0,\n time: Utc::now(),\n kids: vec![],\n r#type: \"\".to_string(),\n }\n }\n }\n}\n\n#[component]\nfn StoryListing(cx: Scope, story: StoryItem) -> Element {\n let StoryItem {\n title,\n url,\n by,\n score,\n time,\n kids,\n id,\n ..\n } = story;\n\n let url = url.as_deref().unwrap_or_default();\n let hostname = url\n .trim_start_matches(\"https://\")\n .trim_start_matches(\"http://\")\n .trim_start_matches(\"www.\");\n let score = format!(\"{score} {}\", if *score == 1 { \" point\" } else { \" points\" });\n let comments = format!(\n \"{} {}\",\n kids.len(),\n if kids.len() == 1 {\n \" comment\"\n } else {\n \" comments\"\n }\n );\n let time = time.format(\"%D %l:%M %p\");\n\n cx.render(rsx! {\n div {\n padding: \"0.5rem\",\n position: \"relative\",\n div {\n font_size: \"1.5rem\",\n a {\n href: url,\n \"{title}\"\n }\n a {\n color: \"gray\",\n href: \"https://news.ycombinator.com/from?site={hostname}\",\n text_decoration: \"none\",\n \" ({hostname})\"\n }\n }\n div {\n display: \"flex\",\n flex_direction: \"row\",\n color: \"gray\",\n div {\n \"{score}\"\n }\n div {\n padding_left: \"0.5rem\",\n \"by {by}\"\n }\n div {\n padding_left: \"0.5rem\",\n \"{time}\"\n }\n div {\n padding_left: \"0.5rem\",\n \"{comments}\"\n }\n }\n }\n })\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\thackernews_post::story_final::App {}\n}\n````" + } + 57usize => { + "# Error handling\n\nA selling point of Rust for web development is the reliability of always knowing where errors can occur and being forced to handle them\n\nHowever, we haven't talked about error handling at all in this guide! In this chapter, we'll cover some strategies in handling errors to ensure your app never crashes.\n\n## The simplest – returning None\n\nAstute 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:\n\n````rust\nfn App(cx: Scope) -> Element {\n\tNone\n}\n````\n\nThis lets us add in some syntactic sugar for operations we think *shouldn't* fail, but we're still not confident enough to \"unwrap\" on.\n\n > \n > The nature of `Option` might change in the future as the `try` trait gets upgraded.\n\n````rust\nfn App(cx: Scope) -> Element {\n\t// immediately return \"None\"\n\tlet name = cx.use_hook(|_| Some(\"hi\"))?;\n}\n````\n\n## Early return on result\n\nBecause Rust can't accept both Options and Results with the existing try infrastructure, you'll need to manually handle Results. This can be done by converting them into Options or by explicitly handling them.\n\n````rust\nfn App(cx: Scope) -> Element {\n\t// Convert Result to Option\n\tlet name = cx.use_hook(|_| \"1.234\").parse().ok()?;\n\n\n\t// Early return\n\tlet count = cx.use_hook(|_| \"1.234\");\n\tlet val = match count.parse() {\n\t\tOk(val) => val\n\t\tErr(err) => return cx.render(rsx!{ \"Parsing failed\" })\n\t};\n}\n````\n\nNotice that while hooks in Dioxus do not like being called in conditionals or loops, they *are* okay with early returns. Returning an error state early is a completely valid way of handling errors.\n\n## Match results\n\nThe next \"best\" way of handling errors in Dioxus is to match on the error locally. This is the most robust way of handling errors, though it doesn't scale to architectures beyond a single component.\n\nTo do this, we simply have an error state built into our component:\n\n````rust\nlet err = use_state(cx, || None);\n````\n\nWhenever we perform an action that generates an error, we'll set that error state. We can then match on the error in a number of ways (early return, return Element, etc).\n\n````rust\nfn Commandline(cx: Scope) -> Element {\n\tlet error = use_state(cx, || None);\n\n\tcx.render(match *error {\n\t\tSome(error) => rsx!(\n\t\t\th1 { \"An error occurred\" }\n\t\t)\n\t\tNone => rsx!(\n\t\t\tinput {\n\t\t\t\toninput: move |_| error.set(Some(\"bad thing happened!\")),\n\t\t\t}\n\t\t)\n\t})\n}\n````\n\n## Passing error states through components\n\nIf you're dealing with a handful of components with minimal nesting, you can just pass the error handle into child components.\n\n````rust\nfn Commandline(cx: Scope) -> Element {\n\tlet error = use_state(cx, || None);\n\n\tif let Some(error) = **error {\n\t\treturn cx.render(rsx!{ \"An error occurred\" });\n\t}\n\n\tcx.render(rsx!{\n\t\tChild { error: error.clone() }\n\t\tChild { error: error.clone() }\n\t\tChild { error: error.clone() }\n\t\tChild { error: error.clone() }\n\t})\n}\n````\n\nMuch like before, our child components can manually set the error during their own actions. The advantage to this pattern is that we can easily isolate error states to a few components at a time, making our app more predictable and robust.\n\n## Going global\n\nA strategy for handling cascaded errors in larger apps is through signaling an error using global state. This particular pattern involves creating an \"error\" context, and then setting it wherever relevant. This particular method is not as \"sophisticated\" as React's error boundary, but it is more fitting for Rust.\n\nTo get started, consider using a built-in hook like `use_context` and `use_context_provider` or Fermi. Of course, it's pretty easy to roll your own hook too.\n\nAt the \"top\" of our architecture, we're going to want to explicitly declare a value that could be an error.\n\n````rust\nenum InputError {\n\tNone,\n\tTooLong,\n\tTooShort,\n}\n\nstatic INPUT_ERROR: Atom = |_| InputError::None;\n````\n\nThen, in our top level component, we want to explicitly handle the possible error state for this part of the tree.\n\n````rust\nfn TopLevel(cx: Scope) -> Element {\n\tlet error = use_read(cx, INPUT_ERROR);\n\n\tmatch error {\n\t\tTooLong => return cx.render(rsx!{ \"FAILED: Too long!\" }),\n\t\tTooShort => return cx.render(rsx!{ \"FAILED: Too Short!\" }),\n\t\t_ => {}\n\t}\n}\n````\n\nNow, whenever a downstream component has an error in its actions, it can simply just set its own error state:\n\n````rust\nfn Commandline(cx: Scope) -> Element {\n\tlet set_error = use_set(cx, INPUT_ERROR);\n\n\tcx.render(rsx!{\n\t\tinput {\n\t\t\toninput: move |evt| {\n\t\t\t\tif evt.value.len() > 20 {\n\t\t\t\t\tset_error(InputError::TooLong);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n````\n\nThis approach to error handling is best in apps that have \"well defined\" error states. Consider using a crate like `thiserror` or `anyhow` to simplify the generation of the error types.\n\nThis pattern is widely popular in many contexts and is particularly helpful whenever your code generates a non-recoverable error. You can gracefully capture these \"global\" error states without panicking or mucking up state." + } + 80usize => { + "# Router\n\nThe router has been entirely rewritten in the `0.4` release to provide type safe routes. This guide serves to help you upgrade your project to the new router. For more information on the router, see the [router guide](../router/index.md).\n\n## Defining Your Router\n\nPreviously, you defined your route with components:\n\n````rust\nrsx! {\n Router:: {\n Route { to: \"/home\", Home {} }\n Route { to: \"/blog\", Blog {} }\n // BlogPost has a dynamic id\n Route { to: \"/blog/:id\", BlogPost {} }\n }\n}\n````\n\nNow you must define your routes as an enum of possible routes:\n\n````rust\nuse dioxus_router::prelude::*;\nuse dioxus::prelude::*;\n\n#[derive(Routable, PartialEq, Debug, Clone)]\nenum Route {\n #[route(\"/home\")]\n // This route will render the Home component with the HomeProps props. (make sure you have the props imported)\n // You can modify the props by passing extra arguments to the macro. For example, if you want the Home variant to render a component called Homepage, you could use:\n // #[route(\"/home\", Homepage)]\n Home {},\n #[route(\"/blog\")]\n Blog {},\n // BlogPost has a dynamic id\n #[route(\"/blog/:id\")]\n BlogPost {\n id: usize\n }\n}\n\n#[component]\nfn Home(cx: Scope) -> Element {\n todo!()\n}\n\n#[component]\nfn Blog(cx: Scope) -> Element {\n todo!()\n}\n\n#[component]\nfn BlogPost(cx: Scope, id: usize) -> Element {\n // Note that you have access to id here in a type safe way without calling any extra functions!\n todo!()\n}\n````\n\n## Linking to routes\n\nNow that routes are enums, you should use the enum as the route in Links. If you try to link to a route that does not exist, you will get a compiler error.\n\n````rust\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\nfn Component(cx: Scope) -> Element {\n render! {\n Link {\n to: Route::BlogPost { id: 123 },\n \"blog post\"\n }\n }\n}\n````\n\n## External Links\n\nTo link to external routes, you can use a string:\n\n````rust\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\nfn Component(cx: Scope) -> Element {\n render! {\n Link {\n to: \"https://google.com\",\n \"google\"\n }\n }\n}\n````\n\n## use_router\n\nThe `use_router` hook has been split into two separate hooks: the `use_route` hook and the `use_navigator` hook.\n\n### use_route\n\nThe new use_route hook lets you read the current route:\n\n````rust\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n#[derive(Clone, Routable)]\nenum Route {\n #[route(\"/\")]\n Index {},\n}\n\nfn App(cx: Scope) -> Element {\n render! {\n h1 { \"App\" }\n Router:: {}\n }\n}\n\n#[component]\nfn Index(cx: Scope) -> Element {\n // Read from (and subscribe to the current route)\n let path = use_route(&cx).unwrap();\n render! {\n h2 { \"Current Path\" }\n p { \"{path}\" }\n }\n}\n````\n\n### use_navigator\n\n`use_navigator` lets you change the route programmatically:\n\n````rust@navigator.rs\n#[component]\nfn Home(cx: Scope) -> Element {\n let nav = use_navigator(cx);\n\n // push\n nav.push(Route::PageNotFound { route: vec![] });\n\n // replace\n nav.replace(Route::Home {});\n\n // go back\n nav.go_back();\n\n // go forward\n nav.go_forward();\n\n render! {\n h1 { \"Welcome to the Dioxus Blog!\" }\n }\n}\n````\n\nYou can read more about programmatic navigation in the [Router Book](../router/reference/navigation/programmatic.md).\n\n### New features\n\nIn addition to these changes, there have been many new features added to the router:\n\n* [static generation support](../router/reference/static-generation.md)\n* [Layouts](../router/reference/layouts.md)\n* [Nesting](../router/reference/routes/nested.md)" + } + 46usize => { + "# Nested Routes\n\nWhen developing bigger applications we often want to nest routes within each\nother. As an example, we might want to organize a settings menu using this\npattern:\n\n````plain\n└ Settings\n ├ General Settings (displayed when opening the settings)\n ├ Change Password\n └ Privacy Settings\n````\n\nWe might want to map this structure to these paths and components:\n\n````plain\n/settings\t\t -> Settings { GeneralSettings }\n/settings/password -> Settings { PWSettings }\n/settings/privacy -> Settings { PrivacySettings }\n````\n\nNested routes allow us to do this without repeating /settings in every route.\n\n## Nesting\n\nTo nest routes, we use the `#[nest(\"path\")]` and `#[end_nest]` attributes.\n\nThe path in nest must not:\n\n1. Contain a [Catch All Segment](index.md#catch-all-segments)\n1. Contain a [Query Segment](index.md#query-segments)\n\nIf you define a dynamic segment in a nest, it will be available to all child routes and layouts.\n\nTo finish a nest, we use the `#[end_nest]` attribute or the end of the enum.\n\n````rust@nest.rs\n#[derive(Routable, Clone)]\n// Skipping formatting allows you to indent nests\n#[rustfmt::skip]\nenum Route {\n // Start the /blog nest\n #[nest(\"/blog\")]\n // You can nest as many times as you want\n #[nest(\"/:id\")]\n #[route(\"/post\")]\n PostId {\n // You must include parent dynamic segments in child variants\n id: usize,\n },\n // End nests manually with #[end_nest]\n #[end_nest]\n #[route(\"/:id\")]\n // The absolute route of BlogPost is /blog/:name\n BlogPost {\n id: usize,\n },\n // Or nests are ended automatically at the end of the enum\n}\n\n#[component]\nfn BlogPost(cx: Scope, id: usize) -> Element {\n todo!()\n}\n\n#[component]\nfn PostId(cx: Scope, id: usize) -> Element {\n todo!()\n}\n````" + } + 67usize => { + "# Custom Renderer\n\nDioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer.\n\nGreat news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require processing `Mutations` and sending `UserEvents`.\n\n## The specifics:\n\nImplementing the renderer is fairly straightforward. The renderer needs to:\n\n1. Handle the stream of edits generated by updates to the virtual DOM\n1. Register listeners and pass events into the virtual DOM's event system\n\nEssentially, your renderer needs to process edits and generate events to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen.\n\nInternally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves.\n\nFor reference, check out the [javascript interpreter](https://github.com/DioxusLabs/dioxus/tree/master/packages/interpreter) or [tui renderer](https://github.com/DioxusLabs/dioxus/tree/master/packages/dioxus-tui) as a starting point for your custom renderer.\n\n## Templates\n\nDioxus is built around the concept of [Templates](https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html). Templates describe a UI tree known at compile time with dynamic parts filled at runtime. This is useful internally to make skip diffing static nodes, but it is also useful for the renderer to reuse parts of the UI tree. This can be useful for things like a list of items. Each item could contain some static parts and some dynamic parts. The renderer can use the template to create a static part of the UI once, clone it for each element in the list, and then fill in the dynamic parts.\n\n## Mutations\n\nThe `Mutation` type is a serialized enum that represents an operation that should be applied to update the UI. The variants roughly follow this set:\n\n````rust\nenum Mutation {\n\tAppendChildren,\n\tAssignId,\n\tCreatePlaceholder,\n\tCreateTextNode,\n\tHydrateText,\n\tLoadTemplate,\n\tReplaceWith,\n\tReplacePlaceholder,\n\tInsertAfter,\n\tInsertBefore,\n\tSetAttribute,\n\tSetText,\n\tNewEventListener,\n\tRemoveEventListener,\n\tRemove,\n\tPushRoot,\n}\n````\n\nThe Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia.org/wiki/Stack_machine) where the [LoadTemplate](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate), [CreatePlaceholder](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.CreatePlaceholder), and [CreateTextNode](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.CreateTextNode) mutations pushes a new \"real\" DOM node onto the stack and [AppendChildren](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.AppendChildren), [InsertAfter](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.InsertAfter), [InsertBefore](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.InsertBefore), [ReplacePlaceholder](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.ReplacePlaceholder), and [ReplaceWith](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.ReplaceWith) all remove nodes from the stack.\n\n## Node storage\n\nDioxus saves and loads elements with IDs. Inside the VirtualDOM, this is just tracked as as a u64.\n\nWhenever a `CreateElement` edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when id is used in a mutation. Dioxus reclaims the IDs of elements when removed. To stay in sync with Dioxus you can use a sparse Vec (Vec\\\\>) with possibly unoccupied items. You can use the ids as indexes into the Vec for elements, and grow the Vec when an id does not exist.\n\n### An Example\n\nFor the sake of understanding, let's consider this example – a very simple UI declaration:\n\n````rust\nrsx!( h1 {\"count: {x}\"} )\n````\n\n#### Building Templates\n\nThe above rsx will create a template that contains one static h1 tag and a placeholder for a dynamic text node. The template contains the static parts of the UI, and ids for the dynamic parts along with the paths to access them.\n\nThe template will look something like this:\n\n````rust\nTemplate {\n\t// Some id that is unique for the entire project\n\tname: \"main.rs:1:1:0\",\n\t// The root nodes of the template\n\troots: &[\n\t\tTemplateNode::Element {\n\t\t\ttag: \"h1\",\n\t\t\tnamespace: None,\n\t\t\tattrs: &[],\n\t\t\tchildren: &[\n\t\t\t\tTemplateNode::DynamicText {\n\t\t\t\t\tid: 0\n\t\t\t\t},\n\t\t\t],\n\t\t}\n\t],\n\t// the path to each of the dynamic nodes\n\tnode_paths: &[\n\t\t// the path to dynamic node with a id of 0\n\t\t&[\n\t\t\t// on the first root node\n\t\t\t0,\n\t\t\t// the first child of the root node\n\t\t\t0,\n\t\t]\n\t],\n\t// the path to each of the dynamic attributes\n\tattr_paths: &'a [&'a [u8]],\n}\n````\n\n > \n > For more detailed docs about the structure of templates see the [Template api docs](https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html)\n\nThis template will be sent to the renderer in the [list of templates](https://docs.rs/dioxus-core/latest/dioxus_core/struct.Mutations.html#structfield.templates) supplied with the mutations the first time it is used. Any time the renderer encounters a [LoadTemplate](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate) mutation after this, it should clone the template and store it in the given id.\n\nFor dynamic nodes and dynamic text nodes, a placeholder node should be created and inserted into the UI so that the node can be modified later.\n\nIn HTML renderers, this template could look like this:\n\n````html\n

\"\"

\n````\n\n#### Applying Mutations\n\nAfter the renderer has created all of the new templates, it can begin to process the mutations.\n\nWhen the renderer starts, it should contain the Root node on the stack and store the Root node with an id of 0. The Root node is the top-level node of the UI. In HTML, this is the `
` element.\n\n````rust\ninstructions: []\nstack: [\n\tRootNode,\n]\nnodes: [\n\tRootNode,\n]\n````\n\nThe first mutation is a `LoadTemplate` mutation. This tells the renderer to load a root from the template with the given id. The renderer will then push the root node of the template onto the stack and store it with an id for later. In this case, the root node is an h1 element.\n\n````rust\ninstructions: [\n\tLoadTemplate {\n\t\t// the id of the template\n\t\tname: \"main.rs:1:1:0\",\n\t\t// the index of the root node in the template\n\t\tindex: 0,\n\t\t// the id to store\n\t\tid: ElementId(1),\n\t}\n]\nstack: [\n\tRootNode,\n\t

\"\"

,\n]\nnodes: [\n\tRootNode,\n\t

\"\"

,\n]\n````\n\nNext, Dioxus will create the dynamic text node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the Mutation `HydrateText`. When the renderer receives this instruction, it will navigate to the placeholder text node in the template and replace it with the new text.\n\n````rust\ninstructions: [\n\tLoadTemplate {\n\t\tname: \"main.rs:1:1:0\",\n\t\tindex: 0,\n\t\tid: ElementId(1),\n\t},\n\tHydrateText {\n\t\t// the id to store the text node\n\t\tid: ElementId(2),\n\t\t// the text to set\n\t\ttext: \"count: 0\",\n\t}\n]\nstack: [\n\tRootNode,\n\t

\"count: 0\"

,\n]\nnodes: [\n\tRootNode,\n\t

\"count: 0\"

,\n\t\"count: 0\",\n]\n````\n\nRemember, the h1 node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the h1 node to the Root. It depends on the situation, but in this case, we use `AppendChildren`. This pops the text node off the stack, leaving the Root element as the next element on the stack.\n\n````rust\ninstructions: [\n\tLoadTemplate {\n\t\tname: \"main.rs:1:1:0\",\n\t\tindex: 0,\n\t\tid: ElementId(1),\n\t},\n\tHydrateText {\n\t\tid: ElementId(2),\n\t\ttext: \"count: 0\",\n\t},\n\tAppendChildren {\n\t\t// the id of the parent node\n\t\tid: ElementId(0),\n\t\t// the number of nodes to pop off the stack and append\n\t\tm: 1\n\t}\n]\nstack: [\n\tRootNode,\n]\nnodes: [\n\tRootNode,\n\t

\"count: 0\"

,\n\t\"count: 0\",\n]\n````\n\nOver time, our stack looked like this:\n\n````rust\n[Root]\n[Root,

\"\"

]\n[Root,

\"count: 0\"

]\n[Root]\n````\n\nConveniently, this approach completely separates the Virtual DOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits make Dioxus independent of platform specifics.\n\nDioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing.\n\nThis little demo serves to show exactly how a Renderer would need to process a mutation stream to build UIs.\n\n## Event loop\n\nLike most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important for your custom renderer can handle those too.\n\nThe code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is:\n\n````rust, ignore\npub async fn run(&mut self) -> dioxus_core::error::Result<()> {\n\t// Push the body element onto the WebsysDom's stack machine\n\tlet mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom());\n\twebsys_dom.stack.push(root_node);\n\n\t// Rebuild or hydrate the virtualdom\n\tlet mutations = self.internal_dom.rebuild();\n\twebsys_dom.apply_mutations(mutations);\n\n\t// Wait for updates from the real dom and progress the virtual dom\n\tloop {\n\t\tlet user_input_future = websys_dom.wait_for_event();\n\t\tlet internal_event_future = self.internal_dom.wait_for_work();\n\n\t\tmatch select(user_input_future, internal_event_future).await {\n\t\t\tEither::Left((_, _)) => {\n\t\t\t\tlet mutations = self.internal_dom.work_with_deadline(|| false);\n\t\t\t\twebsys_dom.apply_mutations(mutations);\n\t\t\t},\n\t\t\tEither::Right((event, _)) => websys_dom.handle_event(event),\n\t\t}\n\n\t\t// render\n\t}\n}\n````\n\nIt's important to decode what the real events are for your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `UserEvent` type. Right now, the virtual event system is modeled almost entirely around the HTML spec, but we are interested in slimming it down.\n\n````rust, ignore\nfn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {\n\tmatch event.type_().as_str() {\n\t\t\"keydown\" => {\n\t\t\tlet event: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();\n\t\t\tUserEvent::KeyboardEvent(UserEvent {\n\t\t\t\tscope_id: None,\n\t\t\t\tpriority: EventPriority::Medium,\n\t\t\t\tname: \"keydown\",\n\t\t\t\t// This should be whatever element is focused\n\t\t\t\telement: Some(ElementId(0)),\n\t\t\t\tdata: Arc::new(KeyboardData{\n\t\t\t\t\tchar_code: event.char_code(),\n\t\t\t\t\tkey: event.key(),\n\t\t\t\t\tkey_code: event.key_code(),\n\t\t\t\t\talt_key: event.alt_key(),\n\t\t\t\t\tctrl_key: event.ctrl_key(),\n\t\t\t\t\tmeta_key: event.meta_key(),\n\t\t\t\t\tshift_key: event.shift_key(),\n\t\t\t\t\tlocation: event.location(),\n\t\t\t\t\trepeat: event.repeat(),\n\t\t\t\t\twhich: event.which(),\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\t\t_ => todo!()\n\t}\n}\n````\n\n## Custom raw elements\n\nIf you need to go as far as relying on custom elements/attributes 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. You can drop in your elements any time you want, with little hassle. However, you must be sure your renderer can handle the new namespace.\n\nFor more examples and information on how to create custom namespaces, see the [`dioxus_html` crate](https://github.com/DioxusLabs/dioxus/blob/master/packages/html/README.md#how-to-extend-it).\n\n# Native Core\n\nIf you are creating a renderer in rust, the [native-core](https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core) crate provides some utilities to implement a renderer. It provides an abstraction over Mutations and Templates and contains helpers that can handle the layout and text editing for you.\n\n## The RealDom\n\nThe `RealDom` is a higher-level abstraction over updating the Dom. It uses an entity component system to manage the state of nodes. This system allows you to modify insert and modify arbitrary components on nodes. On top of this, the RealDom provides a way to manage a tree of nodes, and the State trait provides a way to automatically add and update these components when the tree is modified. It also provides a way to apply `Mutations` to the RealDom.\n\n### Example\n\nLet's build a toy renderer with borders, size, and text color.\nBefore we start let's take a look at an example element we can render:\n\n````rust\ncx.render(rsx!{\n\tdiv{\n\t\tcolor: \"red\",\n\t\tp{\n\t\t\tborder: \"1px solid black\",\n\t\t\t\"hello world\"\n\t\t}\n\t}\n})\n````\n\nIn this tree, the color depends on the parent's color. The layout depends on the children's layout, the current text, and the text size. The border depends on only the current node.\n\nIn the following diagram arrows represent dataflow:\n\n[![](https://mermaid.ink/img/pako:eNqllV1vgjAUhv8K6W4wkQVa2QdLdrHsdlfukmSptEhjoaSWqTH-9xVwONAKst70g5739JzzlO5BJAgFAYi52EQJlsr6fAszS7d1sVhKnCdWJDJFt6peLVs5-9owohK7HFrVcFJ_pxnpmK8VVvRkTJikkWIiaxy1dhP23bUwW1WW5WbPrrqJ4ziR4EJ6dtVN2ls5y1ZztePUcrWZFCvqVEcPPDffvlyS1XoLIQnVgnVvVPR6FU9Zc-6dV453ojjOPbuetRJ57gIeXQR3cez7rjtteZyZQ2j5MqmjqwE0ZW0VKx9RKtgpFewp1aw3sXXFy6TWgiYlv8mfq1scD8ofbBCAfQg8_AMBOAyBxzEIwA4CxgQ99QbQkjnD2KT7_CfxGF8_9WXQEsq5sDZCcjICOXRCri4h6r3NA38Q6Jdi1EOx5w3DGDYYI6MUvJFjM3VoGHUeGoMd6mBnDmh2E3fo7O4Yhf0x4OkBmIKUyhQzol_GfbkcApXQlIYg0EOC5SoEYXbQ-3ChxHyXRSBQsqBTUOREx_7OsAY3BUGM-VqvUsKUkB_1U6vf05gtweEHTk4_HQ?type=png)](https://mermaid.live/edit#pako:eNqllV1vgjAUhv8K6W4wkQVa2QdLdrHsdlfukmSptEhjoaSWqTH-9xVwONAKst70g5739JzzlO5BJAgFAYi52EQJlsr6fAszS7d1sVhKnCdWJDJFt6peLVs5-9owohK7HFrVcFJ_pxnpmK8VVvRkTJikkWIiaxy1dhP23bUwW1WW5WbPrrqJ4ziR4EJ6dtVN2ls5y1ZztePUcrWZFCvqVEcPPDffvlyS1XoLIQnVgnVvVPR6FU9Zc-6dV453ojjOPbuetRJ57gIeXQR3cez7rjtteZyZQ2j5MqmjqwE0ZW0VKx9RKtgpFewp1aw3sXXFy6TWgiYlv8mfq1scD8ofbBCAfQg8_AMBOAyBxzEIwA4CxgQ99QbQkjnD2KT7_CfxGF8_9WXQEsq5sDZCcjICOXRCri4h6r3NA38Q6Jdi1EOx5w3DGDYYI6MUvJFjM3VoGHUeGoMd6mBnDmh2E3fo7O4Yhf0x4OkBmIKUyhQzol_GfbkcApXQlIYg0EOC5SoEYXbQ-3ChxHyXRSBQsqBTUOREx_7OsAY3BUGM-VqvUsKUkB_1U6vf05gtweEHTk4_HQ)\n\nTo help in building a Dom, native-core provides the State trait and a RealDom struct. The State trait provides a way to describe how states in a node depend on other states in its relatives. By describing how to update a single node from its relations, native-core will derive a way to update the states of all nodes for you. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom.\n\nNative Core cannot create all of the required methods for the State trait, but it can derive some of them. To implement the State trait, you must implement the following methods and let the `#[partial_derive_state]` macro handle the rest:\n\n````rust, ignore@custom_renderer.rs\n// All states must derive Component (https://docs.rs/shipyard/latest/shipyard/derive.Component.html)\n// They also must implement Default or provide a custom implementation of create in the State trait\n#[derive(Default, Component)]\nstruct MyState;\n\n/// Derive some of the boilerplate for the State implementation\n#[partial_derive_state]\nimpl State for MyState {\n // The states of the parent nodes this state depends on\n type ParentDependencies = ();\n\n // The states of the child nodes this state depends on\n type ChildDependencies = (Self,);\n\n // The states of the current node this state depends on\n type NodeDependencies = ();\n\n // The parts of the current text, element, or placeholder node in the tree that this state depends on\n const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();\n\n // How to update the state of the current node based on the state of the parent nodes, child nodes, and the current node\n // Returns true if the node was updated and false if the node was not updated\n fn update<'a>(\n &mut self,\n // The view of the current node limited to the parts this state depends on\n _node_view: NodeView<()>,\n // The state of the current node that this state depends on\n _node: ::ElementBorrowed<'a>,\n // The state of the parent nodes that this state depends on\n _parent: Option<::ElementBorrowed<'a>>,\n // The state of the child nodes that this state depends on\n _children: Vec<::ElementBorrowed<'a>>,\n // The context of the current node used to pass global state into the tree\n _context: &SendAnyMap,\n ) -> bool {\n todo!()\n }\n\n // partial_derive_state will generate a default implementation of all the other methods\n}\n````\n\nLets take a look at how to implement the State trait for a simple renderer.\n\n````rust@custom_renderer.rs\nstruct FontSize(f64);\n\n// All states need to derive Component\n#[derive(Default, Debug, Copy, Clone, Component)]\nstruct Size(f64, f64);\n\n/// Derive some of the boilerplate for the State implementation\n#[partial_derive_state]\nimpl State for Size {\n type ParentDependencies = ();\n\n // The size of the current node depends on the size of its children\n type ChildDependencies = (Self,);\n\n type NodeDependencies = ();\n\n // Size only cares about the width, height, and text parts of the current node\n const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()\n // Get access to the width and height attributes\n .with_attrs(AttributeMaskBuilder::Some(&[\"width\", \"height\"]))\n // Get access to the text of the node\n .with_text();\n\n fn update<'a>(\n &mut self,\n node_view: NodeView<()>,\n _node: ::ElementBorrowed<'a>,\n _parent: Option<::ElementBorrowed<'a>>,\n children: Vec<::ElementBorrowed<'a>>,\n context: &SendAnyMap,\n ) -> bool {\n let font_size = context.get::().unwrap().0;\n let mut width;\n let mut height;\n if let Some(text) = node_view.text() {\n // if the node has text, use the text to size our object\n width = text.len() as f64 * font_size;\n height = font_size;\n } else {\n // otherwise, the size is the maximum size of the children\n width = children\n .iter()\n .map(|(item,)| item.0)\n .reduce(|accum, item| if accum >= item { accum } else { item })\n .unwrap_or(0.0);\n\n height = children\n .iter()\n .map(|(item,)| item.1)\n .reduce(|accum, item| if accum >= item { accum } else { item })\n .unwrap_or(0.0);\n }\n // if the node contains a width or height attribute it overrides the other size\n for a in node_view.attributes().into_iter().flatten() {\n match &*a.attribute.name {\n \"width\" => width = a.value.as_float().unwrap(),\n \"height\" => height = a.value.as_float().unwrap(),\n // because Size only depends on the width and height, no other attributes will be passed to the member\n _ => panic!(),\n }\n }\n // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed\n let changed = (width != self.0) || (height != self.1);\n *self = Self(width, height);\n changed\n }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]\nstruct TextColor {\n r: u8,\n g: u8,\n b: u8,\n}\n\n#[partial_derive_state]\nimpl State for TextColor {\n // TextColor depends on the TextColor part of the parent\n type ParentDependencies = (Self,);\n\n type ChildDependencies = ();\n\n type NodeDependencies = ();\n\n // TextColor only cares about the color attribute of the current node\n const NODE_MASK: NodeMaskBuilder<'static> =\n // Get access to the color attribute\n NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&[\"color\"]));\n\n fn update<'a>(\n &mut self,\n node_view: NodeView<()>,\n _node: ::ElementBorrowed<'a>,\n parent: Option<::ElementBorrowed<'a>>,\n _children: Vec<::ElementBorrowed<'a>>,\n _context: &SendAnyMap,\n ) -> bool {\n // TextColor only depends on the color tag, so getting the first tag is equivalent to looking through all tags\n let new = match node_view\n .attributes()\n .and_then(|mut attrs| attrs.next())\n .and_then(|attr| attr.value.as_text())\n {\n // if there is a color tag, translate it\n Some(\"red\") => TextColor { r: 255, g: 0, b: 0 },\n Some(\"green\") => TextColor { r: 0, g: 255, b: 0 },\n Some(\"blue\") => TextColor { r: 0, g: 0, b: 255 },\n Some(color) => panic!(\"unknown color {}\", \"red\"),\n // otherwise check if the node has a parent and inherit that color\n None => match parent {\n Some((parent,)) => *parent,\n None => Self::default(),\n },\n };\n // check if the member has changed\n let changed = new != *self;\n *self = new;\n changed\n }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]\nstruct Border(bool);\n\n#[partial_derive_state]\nimpl State for Border {\n // TextColor depends on the TextColor part of the parent\n type ParentDependencies = (Self,);\n\n type ChildDependencies = ();\n\n type NodeDependencies = ();\n\n // Border does not depended on any other member in the current node\n const NODE_MASK: NodeMaskBuilder<'static> =\n // Get access to the border attribute\n NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&[\"border\"]));\n\n fn update<'a>(\n &mut self,\n node_view: NodeView<()>,\n _node: ::ElementBorrowed<'a>,\n _parent: Option<::ElementBorrowed<'a>>,\n _children: Vec<::ElementBorrowed<'a>>,\n _context: &SendAnyMap,\n ) -> bool {\n // check if the node contains a border attribute\n let new = Self(\n node_view\n .attributes()\n .and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == \"border\"))\n .is_some(),\n );\n // check if the member has changed\n let changed = new != *self;\n *self = new;\n changed\n }\n}\n````\n\nNow that we have our state, we can put it to use in our RealDom. We can update the RealDom with apply_mutations to update the structure of the dom (adding, removing, and changing properties of nodes) and then update_state to update the States for each of the nodes that changed.\n\n````rust@custom_renderer.rs\nfn main() -> Result<(), Box> {\n fn app(cx: Scope) -> Element {\n let count = use_state(cx, || 0);\n\n use_future(cx, (count,), |(count,)| async move {\n loop {\n tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n count.set(*count + 1);\n }\n });\n\n cx.render(rsx! {\n div{\n color: \"red\",\n \"{count}\"\n }\n })\n }\n\n // create the vdom, the real_dom, and the binding layer between them\n let mut vdom = VirtualDom::new(app);\n let mut rdom: RealDom = RealDom::new([\n Border::to_type_erased(),\n TextColor::to_type_erased(),\n Size::to_type_erased(),\n ]);\n let mut dioxus_intigration_state = DioxusState::create(&mut rdom);\n\n let mutations = vdom.rebuild();\n // update the structure of the real_dom tree\n dioxus_intigration_state.apply_mutations(&mut rdom, mutations);\n let mut ctx = SendAnyMap::new();\n // set the font size to 3.3\n ctx.insert(FontSize(3.3));\n // update the State for nodes in the real_dom tree\n let _to_rerender = rdom.update_state(ctx);\n\n // we need to run the vdom in a async runtime\n tokio::runtime::Builder::new_current_thread()\n .enable_all()\n .build()?\n .block_on(async {\n loop {\n // wait for the vdom to update\n vdom.wait_for_work().await;\n\n // get the mutations from the vdom\n let mutations = vdom.render_immediate();\n\n // update the structure of the real_dom tree\n dioxus_intigration_state.apply_mutations(&mut rdom, mutations);\n\n // update the state of the real_dom tree\n let mut ctx = SendAnyMap::new();\n // set the font size to 3.3\n ctx.insert(FontSize(3.3));\n let _to_rerender = rdom.update_state(ctx);\n\n // render...\n rdom.traverse_depth_first(|node| {\n let indent = \" \".repeat(node.height() as usize);\n let color = *node.get::().unwrap();\n let size = *node.get::().unwrap();\n let border = *node.get::().unwrap();\n let id = node.id();\n let node = node.node_type();\n let node_type = &*node;\n println!(\"{indent}{id:?} {color:?} {size:?} {border:?} {node_type:?}\");\n });\n }\n })\n}\n````\n\n## Layout\n\nFor most platforms, the layout of the Elements will stay the same. The [layout_attributes](https://docs.rs/dioxus-native-core/latest/dioxus_native_core/layout_attributes/index.html) module provides a way to apply HTML attributes a [Taffy](https://docs.rs/taffy/latest/taffy/index.html) layout style.\n\n## Text Editing\n\nTo make it easier to implement text editing in rust renderers, `native-core` also contains a renderer-agnostic cursor system. The cursor can handle text editing, selection, and movement with common keyboard shortcuts integrated.\n\n````rust@custom_renderer.rs\nfn text_editing() {\n let mut cursor = Cursor::default();\n let mut text = String::new();\n\n // handle keyboard input with a max text length of 10\n cursor.handle_input(\n &Code::ArrowRight,\n &Key::ArrowRight,\n &Modifiers::empty(),\n &mut text,\n 10,\n );\n\n // manually select text between characters 0-5 on the first line (this could be from dragging with a mouse)\n cursor.start = Pos::new(0, 0);\n cursor.end = Some(Pos::new(5, 0));\n\n // delete the selected text and move the cursor to the start of the selection\n cursor.delete_selection(&mut text);\n}\n````\n\n## Conclusion\n\nThat should be it! You should have nearly all the knowledge required on how to implement your renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderers, video game UI, and even augmented reality! If you're interested in contributing to any of these projects, don't be afraid to reach out or join the [community](https://discord.gg/XgGxMSkvUM)." + } + 56usize => { + "# Antipatterns\n\nThis example shows what not to do and provides a reason why a given pattern is considered an \"AntiPattern\". Most anti-patterns are considered wrong for performance or code re-usability reasons.\n\n## Unnecessarily Nested Fragments\n\nFragments 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.\n\nOnly 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.\n\n````rust@anti_patterns.rs\n// ❌ Don't unnecessarily nest fragments\nlet _ = cx.render(rsx!(\n Fragment {\n Fragment {\n Fragment {\n Fragment {\n Fragment {\n div { \"Finally have a real node!\" }\n }\n }\n }\n }\n }\n));\n\n// ✅ Render shallow structures\ncx.render(rsx!(\n div { \"Finally have a real node!\" }\n))\n````\n\n## Incorrect Iterator Keys\n\nAs described in the [dynamic rendering chapter](../reference/dynamic_rendering.md#the-key-attribute), 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 will never change.\n\n````rust@anti_patterns.rs\nlet data: &HashMap<_, _> = &cx.props.data;\n\n// ❌ No keys\ncx.render(rsx! {\n ul {\n data.values().map(|value| rsx!(\n li { \"List item: {value}\" }\n ))\n }\n});\n\n// ❌ Using index as keys\ncx.render(rsx! {\n ul {\n cx.props.data.values().enumerate().map(|(index, value)| rsx!(\n li { key: \"{index}\", \"List item: {value}\" }\n ))\n }\n});\n\n// ✅ Using unique IDs as keys:\ncx.render(rsx! {\n ul {\n cx.props.data.iter().map(|(key, value)| rsx!(\n li { key: \"{key}\", \"List item: {value}\" }\n ))\n }\n})\n````\n\n## Avoid Interior Mutability in Props\n\nWhile it is technically acceptable to have a `Mutex` or a `RwLock` in the props, they will be difficult to use.\n\nSuppose 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.\n\n## Avoid Updating State During Render\n\nEvery time you update the state, Dioxus needs to re-render the component – this is inefficient! Consider refactoring your code to avoid this.\n\nAlso, if you unconditionally update the state during render, it will be re-rendered in an infinite loop." + } + _ => panic!("Invalid page ID:"), + } + } + pub fn sections(&self) -> &'static [use_mdbook::mdbook_shared::Section] { + &self.page().sections + } + pub fn page(&self) -> &'static use_mdbook::mdbook_shared::Page { + LAZY_BOOK.get_page(self) + } + pub fn page_id(&self) -> use_mdbook::mdbook_shared::PageId { + match self { + BookRoute::Index { .. } => use_mdbook::mdbook_shared::PageId(0usize), + BookRoute::GettingStartedIndex { .. } => use_mdbook::mdbook_shared::PageId(1usize), + BookRoute::GettingStartedChoosingAWebRenderer { .. } => { + use_mdbook::mdbook_shared::PageId(2usize) + } + BookRoute::GettingStartedWasm { .. } => use_mdbook::mdbook_shared::PageId(3usize), + BookRoute::GettingStartedLiveview { .. } => use_mdbook::mdbook_shared::PageId(4usize), + BookRoute::GettingStartedFullstack { .. } => use_mdbook::mdbook_shared::PageId(5usize), + BookRoute::GettingStartedDesktop { .. } => use_mdbook::mdbook_shared::PageId(6usize), + BookRoute::GettingStartedMobile { .. } => use_mdbook::mdbook_shared::PageId(7usize), + BookRoute::GettingStartedTui { .. } => use_mdbook::mdbook_shared::PageId(8usize), + BookRoute::GuideIndex { .. } => use_mdbook::mdbook_shared::PageId(9usize), + BookRoute::GuideYourFirstComponent { .. } => use_mdbook::mdbook_shared::PageId(10usize), + BookRoute::GuideState { .. } => use_mdbook::mdbook_shared::PageId(11usize), + BookRoute::GuideDataFetching { .. } => use_mdbook::mdbook_shared::PageId(12usize), + BookRoute::GuideFullCode { .. } => use_mdbook::mdbook_shared::PageId(13usize), + BookRoute::ReferenceIndex { .. } => use_mdbook::mdbook_shared::PageId(14usize), + BookRoute::ReferenceRsx { .. } => use_mdbook::mdbook_shared::PageId(15usize), + BookRoute::ReferenceComponents { .. } => use_mdbook::mdbook_shared::PageId(16usize), + BookRoute::ReferenceComponentProps { .. } => use_mdbook::mdbook_shared::PageId(17usize), + BookRoute::ReferenceEventHandlers { .. } => use_mdbook::mdbook_shared::PageId(18usize), + BookRoute::ReferenceHooks { .. } => use_mdbook::mdbook_shared::PageId(19usize), + BookRoute::ReferenceUserInput { .. } => use_mdbook::mdbook_shared::PageId(20usize), + BookRoute::ReferenceContext { .. } => use_mdbook::mdbook_shared::PageId(21usize), + BookRoute::ReferenceDynamicRendering { .. } => { + use_mdbook::mdbook_shared::PageId(22usize) + } + BookRoute::ReferenceRouter { .. } => use_mdbook::mdbook_shared::PageId(23usize), + BookRoute::ReferenceUseFuture { .. } => use_mdbook::mdbook_shared::PageId(24usize), + BookRoute::ReferenceUseCoroutine { .. } => use_mdbook::mdbook_shared::PageId(25usize), + BookRoute::ReferenceSpawn { .. } => use_mdbook::mdbook_shared::PageId(26usize), + BookRoute::ReferenceDesktopIndex { .. } => use_mdbook::mdbook_shared::PageId(27usize), + BookRoute::ReferenceWebIndex { .. } => use_mdbook::mdbook_shared::PageId(28usize), + BookRoute::ReferenceSsr { .. } => use_mdbook::mdbook_shared::PageId(29usize), + BookRoute::ReferenceLiveview { .. } => use_mdbook::mdbook_shared::PageId(30usize), + BookRoute::ReferenceFullstackIndex { .. } => use_mdbook::mdbook_shared::PageId(31usize), + BookRoute::ReferenceFullstackServerFunctions { .. } => { + use_mdbook::mdbook_shared::PageId(32usize) + } + BookRoute::ReferenceFullstackExtractors { .. } => { + use_mdbook::mdbook_shared::PageId(33usize) + } + BookRoute::ReferenceFullstackMiddleware { .. } => { + use_mdbook::mdbook_shared::PageId(34usize) + } + BookRoute::ReferenceFullstackAuthentication { .. } => { + use_mdbook::mdbook_shared::PageId(35usize) + } + BookRoute::ReferenceFullstackRouting { .. } => { + use_mdbook::mdbook_shared::PageId(36usize) + } + BookRoute::RouterIndex { .. } => use_mdbook::mdbook_shared::PageId(37usize), + BookRoute::RouterExampleIndex { .. } => use_mdbook::mdbook_shared::PageId(38usize), + BookRoute::RouterExampleFirstRoute { .. } => use_mdbook::mdbook_shared::PageId(39usize), + BookRoute::RouterExampleBuildingANest { .. } => { + use_mdbook::mdbook_shared::PageId(40usize) + } + BookRoute::RouterExampleNavigationTargets { .. } => { + use_mdbook::mdbook_shared::PageId(41usize) + } + BookRoute::RouterExampleRedirectionPerfection { .. } => { + use_mdbook::mdbook_shared::PageId(42usize) + } + BookRoute::RouterExampleFullCode { .. } => use_mdbook::mdbook_shared::PageId(43usize), + BookRoute::RouterReferenceIndex { .. } => use_mdbook::mdbook_shared::PageId(44usize), + BookRoute::RouterReferenceRoutesIndex { .. } => { + use_mdbook::mdbook_shared::PageId(45usize) + } + BookRoute::RouterReferenceRoutesNested { .. } => { + use_mdbook::mdbook_shared::PageId(46usize) + } + BookRoute::RouterReferenceLayouts { .. } => use_mdbook::mdbook_shared::PageId(47usize), + BookRoute::RouterReferenceNavigationIndex { .. } => { + use_mdbook::mdbook_shared::PageId(48usize) + } + BookRoute::RouterReferenceNavigationProgrammatic { .. } => { + use_mdbook::mdbook_shared::PageId(49usize) + } + BookRoute::RouterReferenceHistoryProviders { .. } => { + use_mdbook::mdbook_shared::PageId(50usize) + } + BookRoute::RouterReferenceHistoryButtons { .. } => { + use_mdbook::mdbook_shared::PageId(51usize) + } + BookRoute::RouterReferenceStaticGeneration { .. } => { + use_mdbook::mdbook_shared::PageId(52usize) + } + BookRoute::RouterReferenceRoutingUpdateCallback { .. } => { + use_mdbook::mdbook_shared::PageId(53usize) + } + BookRoute::CookbookIndex { .. } => use_mdbook::mdbook_shared::PageId(54usize), + BookRoute::CookbookPublishing { .. } => use_mdbook::mdbook_shared::PageId(55usize), + BookRoute::CookbookAntipatterns { .. } => use_mdbook::mdbook_shared::PageId(56usize), + BookRoute::CookbookErrorHandling { .. } => use_mdbook::mdbook_shared::PageId(57usize), + BookRoute::CookbookIntegrationsIndex { .. } => { + use_mdbook::mdbook_shared::PageId(58usize) + } + BookRoute::CookbookIntegrationsLogging { .. } => { + use_mdbook::mdbook_shared::PageId(59usize) + } + BookRoute::CookbookIntegrationsInternationalization { .. } => { + use_mdbook::mdbook_shared::PageId(60usize) + } + BookRoute::CookbookStateIndex { .. } => use_mdbook::mdbook_shared::PageId(61usize), + BookRoute::CookbookStateExternalIndex { .. } => { + use_mdbook::mdbook_shared::PageId(62usize) + } + BookRoute::CookbookStateCustomHooksIndex { .. } => { + use_mdbook::mdbook_shared::PageId(63usize) + } + BookRoute::CookbookTesting { .. } => use_mdbook::mdbook_shared::PageId(64usize), + BookRoute::CookbookExamples { .. } => use_mdbook::mdbook_shared::PageId(65usize), + BookRoute::CookbookTailwind { .. } => use_mdbook::mdbook_shared::PageId(66usize), + BookRoute::CookbookCustomRenderer { .. } => use_mdbook::mdbook_shared::PageId(67usize), + BookRoute::CookbookOptimizing { .. } => use_mdbook::mdbook_shared::PageId(68usize), + BookRoute::CliIndex { .. } => use_mdbook::mdbook_shared::PageId(69usize), + BookRoute::CliInstallation { .. } => use_mdbook::mdbook_shared::PageId(70usize), + BookRoute::CliCreating { .. } => use_mdbook::mdbook_shared::PageId(71usize), + BookRoute::CliConfigure { .. } => use_mdbook::mdbook_shared::PageId(72usize), + BookRoute::CliTranslate { .. } => use_mdbook::mdbook_shared::PageId(73usize), + BookRoute::ContributingIndex { .. } => use_mdbook::mdbook_shared::PageId(74usize), + BookRoute::ContributingProjectStructure { .. } => { + use_mdbook::mdbook_shared::PageId(75usize) + } + BookRoute::ContributingWalkthroughReadme { .. } => { + use_mdbook::mdbook_shared::PageId(76usize) + } + BookRoute::ContributingGuidingPrinciples { .. } => { + use_mdbook::mdbook_shared::PageId(77usize) + } + BookRoute::ContributingRoadmap { .. } => use_mdbook::mdbook_shared::PageId(78usize), + BookRoute::MigrationIndex { .. } => use_mdbook::mdbook_shared::PageId(79usize), + BookRoute::MigrationRouter { .. } => use_mdbook::mdbook_shared::PageId(80usize), + BookRoute::MigrationHotReload { .. } => use_mdbook::mdbook_shared::PageId(81usize), + } + } +} +impl Default for BookRoute { + fn default() -> Self { + BookRoute::Index { + section: IndexSection::Empty, + } + } +} +pub static LAZY_BOOK: use_mdbook::Lazy> = + use_mdbook::Lazy::new(|| { + let mut page_id_mapping = ::std::collections::HashMap::new(); + let mut pages = Vec::new(); + pages.push((0usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Introduction".to_string(), + url: BookRoute::Index { + section: IndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Introduction".to_string(), + id: "introduction".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Features".to_string(), + id: "features".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Multiplatform".to_string(), + id: "multiplatform".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Stability".to_string(), + id: "stability".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(0usize), + } + })); + page_id_mapping.insert( + BookRoute::Index { + section: IndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(0usize), + ); + pages.push((1usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Getting Started".to_string(), + url: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Getting Started".to_string(), + id: "getting-started".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Prerequisites".to_string(), + id: "prerequisites".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "An Editor".to_string(), + id: "an-editor".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Rust".to_string(), + id: "rust".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup Guides".to_string(), + id: "setup-guides".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(1usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(1usize), + ); + pages.push((2usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Choosing A Web Renderer".to_string(), + url: BookRoute::GettingStartedChoosingAWebRenderer { + section: GettingStartedChoosingAWebRendererSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Choosing a web renderer".to_string(), + id: "choosing-a-web-renderer".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dioxus Liveview".to_string(), + id: "dioxus-liveview".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dioxus Web".to_string(), + id: "dioxus-web".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dioxus Fullstack".to_string(), + id: "dioxus-fullstack".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(2usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedChoosingAWebRenderer { + section: GettingStartedChoosingAWebRendererSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(2usize), + ); + pages.push((3usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Web".to_string(), + url: BookRoute::GettingStartedWasm { + section: GettingStartedWasmSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Web".to_string(), + id: "web".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Support".to_string(), + id: "support".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Tooling".to_string(), + id: "tooling".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating a Project".to_string(), + id: "creating-a-project".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Hot Reload".to_string(), + id: "hot-reload".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup".to_string(), + id: "setup".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Usage".to_string(), + id: "usage".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Limitations".to_string(), + id: "limitations".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(3usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedWasm { + section: GettingStartedWasmSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(3usize), + ); + pages.push((4usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Liveview".to_string(), + url: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Liveview".to_string(), + id: "liveview".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Support".to_string(), + id: "support".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup".to_string(), + id: "setup".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Hot Reload".to_string(), + id: "hot-reload".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Hot Reload Setup".to_string(), + id: "hot-reload-setup".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Usage".to_string(), + id: "usage".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Limitations".to_string(), + id: "limitations".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(4usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(4usize), + ); + pages.push((5usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Fullstack".to_string(), + url: BookRoute::GettingStartedFullstack { + section: GettingStartedFullstackSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Fullstack".to_string(), + id: "fullstack".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Getting Started".to_string(), + id: "getting-started".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup".to_string(), + id: "setup".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Hot Reload".to_string(), + id: "hot-reload".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Usage".to_string(), + id: "usage".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Limitations".to_string(), + id: "limitations".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(5usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedFullstack { + section: GettingStartedFullstackSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(5usize), + ); + pages.push((6usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Desktop".to_string(), + url: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Desktop overview".to_string(), + id: "desktop-overview".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Examples".to_string(), + id: "examples".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Getting started".to_string(), + id: "getting-started".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Platform-specific dependencies".to_string(), + id: "platform-specific-dependencies".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Windows".to_string(), + id: "windows".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Linux".to_string(), + id: "linux".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "MacOS".to_string(), + id: "macos".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating a Project".to_string(), + id: "creating-a-project".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Hot Reload".to_string(), + id: "hot-reload".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup".to_string(), + id: "setup".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Usage".to_string(), + id: "usage".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Limitations".to_string(), + id: "limitations".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(6usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(6usize), + ); + pages.push((7usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Mobile".to_string(), + url: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Mobile App".to_string(), + id: "mobile-app".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Support".to_string(), + id: "support".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Getting Set up".to_string(), + id: "getting-set-up".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setting up dependencies".to_string(), + id: "setting-up-dependencies".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Android Dependencies".to_string(), + id: "android-dependencies".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "IOS Dependencies".to_string(), + id: "ios-dependencies".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setting up your project".to_string(), + id: "setting-up-your-project".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Running".to_string(), + id: "running".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "IOS".to_string(), + id: "ios".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Android".to_string(), + id: "android".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(7usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(7usize), + ); + pages.push((8usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Terminal UI".to_string(), + url: BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Terminal UI".to_string(), + id: "terminal-ui".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Support".to_string(), + id: "support".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Getting Set up".to_string(), + id: "getting-set-up".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Hot Reload".to_string(), + id: "hot-reload".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup".to_string(), + id: "setup".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Usage".to_string(), + id: "usage".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Limitations".to_string(), + id: "limitations".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(8usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(8usize), + ); + pages.push((9usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Guide".to_string(), + url: BookRoute::GuideIndex { + section: GuideIndexSection::Empty, + }, + segments: vec![], + sections: vec![], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(9usize), + } + })); + page_id_mapping.insert( + BookRoute::GuideIndex { + section: GuideIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(9usize), + ); + pages.push((10usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Your First Component".to_string(), + url: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Your First Component".to_string(), + id: "your-first-component".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dynamic Text".to_string(), + id: "dynamic-text".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating Elements".to_string(), + id: "creating-elements".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setting Attributes".to_string(), + id: "setting-attributes".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating a Component".to_string(), + id: "creating-a-component".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating Props".to_string(), + id: "creating-props".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Cleaning Up Our Interface".to_string(), + id: "cleaning-up-our-interface".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(10usize), + } + })); + page_id_mapping.insert( + BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(10usize), + ); + pages.push((11usize, { + ::use_mdbook::mdbook_shared::Page { + title: "State".to_string(), + url: BookRoute::GuideState { + section: GuideStateSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Interactivity".to_string(), + id: "interactivity".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating a Preview".to_string(), + id: "creating-a-preview".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Event Handlers".to_string(), + id: "event-handlers".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "State".to_string(), + id: "state".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The Rules of Hooks".to_string(), + id: "the-rules-of-hooks".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No Hooks in Conditionals".to_string(), + id: "no-hooks-in-conditionals".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No Hooks in Closures".to_string(), + id: "no-hooks-in-closures".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No Hooks in Loops".to_string(), + id: "no-hooks-in-loops".to_string(), + level: 4usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(11usize), + } + })); + page_id_mapping.insert( + BookRoute::GuideState { + section: GuideStateSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(11usize), + ); + pages.push((12usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Data Fetching".to_string(), + url: BookRoute::GuideDataFetching { + section: GuideDataFetchingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Fetching Data".to_string(), + id: "fetching-data".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Defining the API".to_string(), + id: "defining-the-api".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Working with Async".to_string(), + id: "working-with-async".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Lazily Fetching Data".to_string(), + id: "lazily-fetching-data".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(12usize), + } + })); + page_id_mapping.insert( + BookRoute::GuideDataFetching { + section: GuideDataFetchingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(12usize), + ); + pages.push((13usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Full Code".to_string(), + url: BookRoute::GuideFullCode { + section: GuideFullCodeSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Conclusion".to_string(), + id: "conclusion".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Challenges".to_string(), + id: "challenges".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The full code for the hacker news project".to_string(), + id: "the-full-code-for-the-hacker-news-project".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(13usize), + } + })); + page_id_mapping.insert( + BookRoute::GuideFullCode { + section: GuideFullCodeSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(13usize), + ); + pages.push((14usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Reference".to_string(), + url: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Dioxus Reference".to_string(), + id: "dioxus-reference".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Rendering".to_string(), + id: "rendering".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "State".to_string(), + id: "state".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Platforms".to_string(), + id: "platforms".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(14usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(14usize), + ); + pages.push((15usize, { + ::use_mdbook::mdbook_shared::Page { + title: "RSX".to_string(), + url: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Describing the UI".to_string(), + id: "describing-the-ui".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "RSX Features".to_string(), + id: "rsx-features".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Attributes".to_string(), + id: "attributes".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom Attributes".to_string(), + id: "custom-attributes".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Special Attributes".to_string(), + id: "special-attributes".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The HTML Escape Hatch".to_string(), + id: "the-html-escape-hatch".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Boolean Attributes".to_string(), + id: "boolean-attributes".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Interpolation".to_string(), + id: "interpolation".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Children".to_string(), + id: "children".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Fragments".to_string(), + id: "fragments".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Expressions".to_string(), + id: "expressions".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Loops".to_string(), + id: "loops".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "If statements".to_string(), + id: "if-statements".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(15usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(15usize), + ); + pages.push((16usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Components".to_string(), + url: BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Components".to_string(), + id: "components".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(16usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(16usize), + ); + pages.push((17usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Props".to_string(), + url: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Component Props".to_string(), + id: "component-props".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "derive(Props)".to_string(), + id: "deriveprops".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Owned Props".to_string(), + id: "owned-props".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Borrowed Props".to_string(), + id: "borrowed-props".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Prop Options".to_string(), + id: "prop-options".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Optional Props".to_string(), + id: "optional-props".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Explicitly Required Options".to_string(), + id: "explicitly-required-options".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Default Props".to_string(), + id: "default-props".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Automatic Conversion with .into".to_string(), + id: "automatic-conversion-with-into".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The component macro".to_string(), + id: "the-component-macro".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Component Children".to_string(), + id: "component-children".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The children field".to_string(), + id: "the-children-field".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(17usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(17usize), + ); + pages.push((18usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Event Handlers".to_string(), + url: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Event Handlers".to_string(), + id: "event-handlers".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The Event object".to_string(), + id: "the-event-object".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Event propagation".to_string(), + id: "event-propagation".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Prevent Default".to_string(), + id: "prevent-default".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Handler Props".to_string(), + id: "handler-props".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom Data".to_string(), + id: "custom-data".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(18usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(18usize), + ); + pages.push((19usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Hooks".to_string(), + url: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Hooks and component state".to_string(), + id: "hooks-and-component-state".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "use_state hook".to_string(), + id: "use-state-hook".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Out-of-date UseState".to_string(), + id: "out-of-date-usestate".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Rules of hooks".to_string(), + id: "rules-of-hooks".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No hooks in conditionals".to_string(), + id: "no-hooks-in-conditionals".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No hooks in closures".to_string(), + id: "no-hooks-in-closures".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No hooks in loops".to_string(), + id: "no-hooks-in-loops".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "use_ref hook".to_string(), + id: "use-ref-hook".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Additional resources".to_string(), + id: "additional-resources".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(19usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceHooks { + section: ReferenceHooksSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(19usize), + ); + pages.push((20usize, { + ::use_mdbook::mdbook_shared::Page { + title: "User Input".to_string(), + url: BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "User Input".to_string(), + id: "user-input".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Controlled Inputs".to_string(), + id: "controlled-inputs".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Uncontrolled Inputs".to_string(), + id: "uncontrolled-inputs".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Handling files".to_string(), + id: "handling-files".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(20usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(20usize), + ); + pages.push((21usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Context".to_string(), + url: BookRoute::ReferenceContext { + section: ReferenceContextSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Sharing State".to_string(), + id: "sharing-state".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Lifting State".to_string(), + id: "lifting-state".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Using Shared State".to_string(), + id: "using-shared-state".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(21usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceContext { + section: ReferenceContextSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(21usize), + ); + pages.push((22usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Dynamic Rendering".to_string(), + url: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Dynamic Rendering".to_string(), + id: "dynamic-rendering".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conditional Rendering".to_string(), + id: "conditional-rendering".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Improving the if-else Example".to_string(), + id: "improving-the-if-else-example".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Inspecting Element props".to_string(), + id: "inspecting-element-props".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Rendering Nothing".to_string(), + id: "rendering-nothing".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Rendering Lists".to_string(), + id: "rendering-lists".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Inline for loops".to_string(), + id: "inline-for-loops".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The key Attribute".to_string(), + id: "the-key-attribute".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(22usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(22usize), + ); + pages.push((23usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Routing".to_string(), + url: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Router".to_string(), + id: "router".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "What is it?".to_string(), + id: "what-is-it".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Using the router".to_string(), + id: "using-the-router".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Links".to_string(), + id: "links".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "More reading".to_string(), + id: "more-reading".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(23usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceRouter { + section: ReferenceRouterSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(23usize), + ); + pages.push((24usize, { + ::use_mdbook::mdbook_shared::Page { + title: "UseFuture".to_string(), + url: BookRoute::ReferenceUseFuture { + section: ReferenceUseFutureSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "UseFuture".to_string(), + id: "usefuture".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Restarting the Future".to_string(), + id: "restarting-the-future".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dependencies".to_string(), + id: "dependencies".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(24usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceUseFuture { + section: ReferenceUseFutureSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(24usize), + ); + pages.push((25usize, { + ::use_mdbook::mdbook_shared::Page { + title: "UseCoroutine".to_string(), + url: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Coroutines".to_string(), + id: "coroutines".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "use_coroutine".to_string(), + id: "use-coroutine".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Yielding Values".to_string(), + id: "yielding-values".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Sending Values".to_string(), + id: "sending-values".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Automatic injection into the Context API".to_string(), + id: "automatic-injection-into-the-context-api".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(25usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(25usize), + ); + pages.push((26usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Spawn".to_string(), + url: BookRoute::ReferenceSpawn { + section: ReferenceSpawnSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Spawning Futures".to_string(), + id: "spawning-futures".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Spawning Tokio Tasks".to_string(), + id: "spawning-tokio-tasks".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(26usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceSpawn { + section: ReferenceSpawnSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(26usize), + ); + pages.push((27usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Desktop".to_string(), + url: BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Desktop".to_string(), + id: "desktop".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Running Javascript".to_string(), + id: "running-javascript".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom Assets".to_string(), + id: "custom-assets".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Integrating with Wry".to_string(), + id: "integrating-with-wry".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(27usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(27usize), + ); + pages.push((28usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Web".to_string(), + url: BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Web".to_string(), + id: "web".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Running Javascript".to_string(), + id: "running-javascript".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Customizing Index Template".to_string(), + id: "customizing-index-template".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(28usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(28usize), + ); + pages.push((29usize, { + ::use_mdbook::mdbook_shared::Page { + title: "SSR".to_string(), + url: BookRoute::ReferenceSsr { + section: ReferenceSsrSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Server-Side Rendering".to_string(), + id: "server-side-rendering".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup".to_string(), + id: "setup".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Multithreaded Support".to_string(), + id: "multithreaded-support".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(29usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceSsr { + section: ReferenceSsrSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(29usize), + ); + pages.push((30usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Liveview".to_string(), + url: BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Liveview".to_string(), + id: "liveview".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Router Integration".to_string(), + id: "router-integration".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Managing Latency".to_string(), + id: "managing-latency".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(30usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(30usize), + ); + pages.push((31usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Fullstack".to_string(), + url: BookRoute::ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Fullstack development".to_string(), + id: "fullstack-development".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(31usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(31usize), + ); + pages.push((32usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Server Functions".to_string(), + url: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Communicating with the server".to_string(), + id: "communicating-with-the-server".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Cached data fetching".to_string(), + id: "cached-data-fetching".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Running the client with dioxus-desktop".to_string(), + id: "running-the-client-with-dioxus-desktop".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Client code".to_string(), + id: "client-code".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Server code".to_string(), + id: "server-code".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(32usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(32usize), + ); + pages.push((33usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Extractors".to_string(), + url: BookRoute::ReferenceFullstackExtractors { + section: ReferenceFullstackExtractorsSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Extractors".to_string(), + id: "extractors".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(33usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceFullstackExtractors { + section: ReferenceFullstackExtractorsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(33usize), + ); + pages.push((34usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Middleware".to_string(), + url: BookRoute::ReferenceFullstackMiddleware { + section: ReferenceFullstackMiddlewareSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Middleware".to_string(), + id: "middleware".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(34usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceFullstackMiddleware { + section: ReferenceFullstackMiddlewareSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(34usize), + ); + pages.push((35usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Authentication".to_string(), + url: BookRoute::ReferenceFullstackAuthentication { + section: ReferenceFullstackAuthenticationSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Authentication".to_string(), + id: "authentication".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(35usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceFullstackAuthentication { + section: ReferenceFullstackAuthenticationSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(35usize), + ); + pages.push((36usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Routing".to_string(), + url: BookRoute::ReferenceFullstackRouting { + section: ReferenceFullstackRoutingSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Routing".to_string(), + id: "routing".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(36usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceFullstackRouting { + section: ReferenceFullstackRoutingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(36usize), + ); + pages.push((37usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Router".to_string(), + url: BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Introduction".to_string(), + id: "introduction".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(37usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(37usize), + ); + pages.push((38usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Example Project".to_string(), + url: BookRoute::RouterExampleIndex { + section: RouterExampleIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Overview".to_string(), + id: "overview".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "You'll learn how to".to_string(), + id: "youll-learn-how-to".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(38usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterExampleIndex { + section: RouterExampleIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(38usize), + ); + pages.push((39usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Creating Our First Route".to_string(), + url: BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Creating Our First Route".to_string(), + id: "creating-our-first-route".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Fundamentals".to_string(), + id: "fundamentals".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating Routes".to_string(), + id: "creating-routes".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Fallback Route".to_string(), + id: "fallback-route".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conclusion".to_string(), + id: "conclusion".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(39usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(39usize), + ); + pages.push((40usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Building a Nest".to_string(), + url: BookRoute::RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Building a Nest".to_string(), + id: "building-a-nest".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Site Navigation".to_string(), + id: "site-navigation".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "URL Parameters and Nested Routes".to_string(), + id: "url-parameters-and-nested-routes".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conclusion".to_string(), + id: "conclusion".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(40usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(40usize), + ); + pages.push((41usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Navigation Targets".to_string(), + url: BookRoute::RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Navigation Targets".to_string(), + id: "navigation-targets".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "What is a navigation target?".to_string(), + id: "what-is-a-navigation-target".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "External navigation".to_string(), + id: "external-navigation".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(41usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(41usize), + ); + pages.push((42usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Redirection Perfection".to_string(), + url: BookRoute::RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Redirection Perfection".to_string(), + id: "redirection-perfection".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating Redirects".to_string(), + id: "creating-redirects".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conclusion".to_string(), + id: "conclusion".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Challenges".to_string(), + id: "challenges".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(42usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(42usize), + ); + pages.push((43usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Full Code".to_string(), + url: BookRoute::RouterExampleFullCode { + section: RouterExampleFullCodeSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Full Code".to_string(), + id: "full-code".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(43usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterExampleFullCode { + section: RouterExampleFullCodeSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(43usize), + ); + pages.push((44usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Reference".to_string(), + url: BookRoute::RouterReferenceIndex { + section: RouterReferenceIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Adding the router to your application".to_string(), + id: "adding-the-router-to-your-application".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(44usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceIndex { + section: RouterReferenceIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(44usize), + ); + pages.push((45usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Defining Routes".to_string(), + url: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Defining Routes".to_string(), + id: "defining-routes".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Route Segments".to_string(), + id: "route-segments".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Static segments".to_string(), + id: "static-segments".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dynamic Segments".to_string(), + id: "dynamic-segments".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Catch All Segments".to_string(), + id: "catch-all-segments".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Query Segments".to_string(), + id: "query-segments".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(45usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(45usize), + ); + pages.push((46usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Nested Routes".to_string(), + url: BookRoute::RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Nested Routes".to_string(), + id: "nested-routes".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Nesting".to_string(), + id: "nesting".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(46usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(46usize), + ); + pages.push((47usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Layouts".to_string(), + url: BookRoute::RouterReferenceLayouts { + section: RouterReferenceLayoutsSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Layouts".to_string(), + id: "layouts".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Layouts with dynamic segments".to_string(), + id: "layouts-with-dynamic-segments".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(47usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceLayouts { + section: RouterReferenceLayoutsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(47usize), + ); + pages.push((48usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Navigation".to_string(), + url: BookRoute::RouterReferenceNavigationIndex { + section: RouterReferenceNavigationIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Links & Navigation".to_string(), + id: "links--navigation".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(48usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceNavigationIndex { + section: RouterReferenceNavigationIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(48usize), + ); + pages.push((49usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Programmatic Navigation".to_string(), + url: BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Programmatic Navigation".to_string(), + id: "programmatic-navigation".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Using a Navigator".to_string(), + id: "using-a-navigator".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "External Navigation Targets".to_string(), + id: "external-navigation-targets".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(49usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(49usize), + ); + pages.push((50usize, { + ::use_mdbook::mdbook_shared::Page { + title: "History Providers".to_string(), + url: BookRoute::RouterReferenceHistoryProviders { + section: RouterReferenceHistoryProvidersSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "History Providers".to_string(), + id: "history-providers".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(50usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceHistoryProviders { + section: RouterReferenceHistoryProvidersSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(50usize), + ); + pages.push((51usize, { + ::use_mdbook::mdbook_shared::Page { + title: "History Buttons".to_string(), + url: BookRoute::RouterReferenceHistoryButtons { + section: RouterReferenceHistoryButtonsSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "History Buttons".to_string(), + id: "history-buttons".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(51usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceHistoryButtons { + section: RouterReferenceHistoryButtonsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(51usize), + ); + pages.push((52usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Static Generation".to_string(), + url: BookRoute::RouterReferenceStaticGeneration { + section: RouterReferenceStaticGenerationSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Static Generation".to_string(), + id: "static-generation".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Getting the Sitemap".to_string(), + id: "getting-the-sitemap".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Generating a Sitemap".to_string(), + id: "generating-a-sitemap".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Example".to_string(), + id: "example".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(52usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceStaticGeneration { + section: RouterReferenceStaticGenerationSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(52usize), + ); + pages.push((53usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Routing Update Callback".to_string(), + url: BookRoute::RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Routing Update Callback".to_string(), + id: "routing-update-callback".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "How does the callback behave?".to_string(), + id: "how-does-the-callback-behave".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Code Example".to_string(), + id: "code-example".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(53usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(53usize), + ); + pages.push((54usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Cookbook".to_string(), + url: BookRoute::CookbookIndex { + section: CookbookIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Cookbook".to_string(), + id: "cookbook".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(54usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookIndex { + section: CookbookIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(54usize), + ); + pages.push((55usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Publishing".to_string(), + url: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Publishing".to_string(), + id: "publishing".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web: Publishing with GitHub Pages".to_string(), + id: "web-publishing-with-github-pages".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Desktop: Creating an installer".to_string(), + id: "desktop-creating-an-installer".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Preparing your application for bundling".to_string(), + id: "preparing-your-application-for-bundling".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Install dioxus CLI".to_string(), + id: "install-dioxus-cli".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Building".to_string(), + id: "building".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(55usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookPublishing { + section: CookbookPublishingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(55usize), + ); + pages.push((56usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Anti-patterns".to_string(), + url: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Antipatterns".to_string(), + id: "antipatterns".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Unnecessarily Nested Fragments".to_string(), + id: "unnecessarily-nested-fragments".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Incorrect Iterator Keys".to_string(), + id: "incorrect-iterator-keys".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Avoid Interior Mutability in Props".to_string(), + id: "avoid-interior-mutability-in-props".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Avoid Updating State During Render".to_string(), + id: "avoid-updating-state-during-render".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(56usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(56usize), + ); + pages.push((57usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Error Handling".to_string(), + url: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Error handling".to_string(), + id: "error-handling".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The simplest – returning None".to_string(), + id: "the-simplest--returning-none".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Early return on result".to_string(), + id: "early-return-on-result".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Match results".to_string(), + id: "match-results".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Passing error states through components".to_string(), + id: "passing-error-states-through-components".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Going global".to_string(), + id: "going-global".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(57usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(57usize), + ); + pages.push((58usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Integrations".to_string(), + url: BookRoute::CookbookIntegrationsIndex { + section: CookbookIntegrationsIndexSection::Empty, + }, + segments: vec![], + sections: vec![], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(58usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookIntegrationsIndex { + section: CookbookIntegrationsIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(58usize), + ); + pages.push((59usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Logging".to_string(), + url: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Logging".to_string(), + id: "logging".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The Log Crate".to_string(), + id: "the-log-crate".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dioxus Logger".to_string(), + id: "dioxus-logger".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom Format".to_string(), + id: "custom-format".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Timestamps".to_string(), + id: "timestamps".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dioxus Logger Platform Intricacies".to_string(), + id: "dioxus-logger-platform-intricacies".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Final Notes".to_string(), + id: "final-notes".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Desktop and Server".to_string(), + id: "desktop-and-server".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web".to_string(), + id: "web".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Wasm Logger Platform Intricacies".to_string(), + id: "wasm-logger-platform-intricacies".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Android".to_string(), + id: "android".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Viewing Android Logs".to_string(), + id: "viewing-android-logs".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "iOS".to_string(), + id: "ios".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Viewing IOS Logs".to_string(), + id: "viewing-ios-logs".to_string(), + level: 4usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(59usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(59usize), + ); + pages.push((60usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Internationalization".to_string(), + url: BookRoute::CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Internationalization".to_string(), + id: "internationalization".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The full code for internationalization".to_string(), + id: "the-full-code-for-internationalization".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(60usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(60usize), + ); + pages.push((61usize, { + ::use_mdbook::mdbook_shared::Page { + title: "State Management".to_string(), + url: BookRoute::CookbookStateIndex { + section: CookbookStateIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "State Cookbook".to_string(), + id: "state-cookbook".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(61usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookStateIndex { + section: CookbookStateIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(61usize), + ); + pages.push((62usize, { + ::use_mdbook::mdbook_shared::Page { + title: "External State".to_string(), + url: BookRoute::CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Working with External State".to_string(), + id: "working-with-external-state".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Working with non-reactive State".to_string(), + id: "working-with-non-reactive-state".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Making Reactive State External".to_string(), + id: "making-reactive-state-external".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(62usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(62usize), + ); + pages.push((63usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Custom Hooks".to_string(), + url: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Custom Hooks".to_string(), + id: "custom-hooks".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Composing Hooks".to_string(), + id: "composing-hooks".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom Hook Logic".to_string(), + id: "custom-hook-logic".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Hook Anti-Patterns".to_string(), + id: "hook-anti-patterns".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(63usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(63usize), + ); + pages.push((64usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Testing".to_string(), + url: BookRoute::CookbookTesting { + section: CookbookTestingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Testing".to_string(), + id: "testing".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Component Testing".to_string(), + id: "component-testing".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Hook Testing".to_string(), + id: "hook-testing".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "End to End Testing".to_string(), + id: "end-to-end-testing".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(64usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookTesting { + section: CookbookTestingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(64usize), + ); + pages.push((65usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Examples".to_string(), + url: BookRoute::CookbookExamples { + section: CookbookExamplesSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Examples".to_string(), + id: "examples".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(65usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookExamples { + section: CookbookExamplesSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(65usize), + ); + pages.push((66usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Tailwind".to_string(), + url: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Tailwind".to_string(), + id: "tailwind".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup".to_string(), + id: "setup".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Bonus Steps".to_string(), + id: "bonus-steps".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Development".to_string(), + id: "development".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web".to_string(), + id: "web".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Desktop".to_string(), + id: "desktop".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(66usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(66usize), + ); + pages.push((67usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Custom Renderer".to_string(), + url: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Custom Renderer".to_string(), + id: "custom-renderer".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The specifics:".to_string(), + id: "the-specifics".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Templates".to_string(), + id: "templates".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Mutations".to_string(), + id: "mutations".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Node storage".to_string(), + id: "node-storage".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "An Example".to_string(), + id: "an-example".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Building Templates".to_string(), + id: "building-templates".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Applying Mutations".to_string(), + id: "applying-mutations".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Event loop".to_string(), + id: "event-loop".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom raw elements".to_string(), + id: "custom-raw-elements".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Native Core".to_string(), + id: "native-core".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The RealDom".to_string(), + id: "the-realdom".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Example".to_string(), + id: "example".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Layout".to_string(), + id: "layout".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Text Editing".to_string(), + id: "text-editing".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conclusion".to_string(), + id: "conclusion".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(67usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(67usize), + ); + pages.push((68usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Optimizing".to_string(), + url: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Optimizing".to_string(), + id: "optimizing".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Building in release mode".to_string(), + id: "building-in-release-mode".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "UPX".to_string(), + id: "upx".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Build configuration".to_string(), + id: "build-configuration".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Stable".to_string(), + id: "stable".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Unstable".to_string(), + id: "unstable".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "wasm-opt".to_string(), + id: "wasm-opt".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Improving Dioxus code".to_string(), + id: "improving-dioxus-code".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Bundling and minifying the output JS and HTML".to_string(), + id: "bundling-and-minifying-the-output-js-and-html".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(68usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(68usize), + ); + pages.push((69usize, { + ::use_mdbook::mdbook_shared::Page { + title: "CLI".to_string(), + url: BookRoute::CliIndex { + section: CliIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Introduction".to_string(), + id: "introduction".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Features".to_string(), + id: "features".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(69usize), + } + })); + page_id_mapping.insert( + BookRoute::CliIndex { + section: CliIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(69usize), + ); + pages.push((70usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Installation".to_string(), + url: BookRoute::CliInstallation { + section: CliInstallationSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Installation".to_string(), + id: "installation".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Install the stable version (recommended)".to_string(), + id: "install-the-stable-version-recommended".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Install the latest development build through git".to_string(), + id: "install-the-latest-development-build-through-git".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(70usize), + } + })); + page_id_mapping.insert( + BookRoute::CliInstallation { + section: CliInstallationSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(70usize), + ); + pages.push((71usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Create a Project".to_string(), + url: BookRoute::CliCreating { + section: CliCreatingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Create a Project".to_string(), + id: "create-a-project".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Initializing a project".to_string(), + id: "initializing-a-project".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(71usize), + } + })); + page_id_mapping.insert( + BookRoute::CliCreating { + section: CliCreatingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(71usize), + ); + pages.push((72usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Configure Project".to_string(), + url: BookRoute::CliConfigure { + section: CliConfigureSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Configure Project".to_string(), + id: "configure-project".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Structure".to_string(), + id: "structure".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Application 🔒".to_string(), + id: "application-".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web.App 🔒".to_string(), + id: "webapp-".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web.Watcher 🔒".to_string(), + id: "webwatcher-".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web.Resource 🔒".to_string(), + id: "webresource-".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web.Resource.Dev 🔒".to_string(), + id: "webresourcedev-".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web.Proxy".to_string(), + id: "webproxy".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Config example".to_string(), + id: "config-example".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Desktop and TUI".to_string(), + id: "desktop-and-tui".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(72usize), + } + })); + page_id_mapping.insert( + BookRoute::CliConfigure { + section: CliConfigureSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(72usize), + ); + pages.push((73usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Translate HTML".to_string(), + url: BookRoute::CliTranslate { + section: CliTranslateSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Translating existing HTML".to_string(), + id: "translating-existing-html".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Usage".to_string(), + id: "usage".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(73usize), + } + })); + page_id_mapping.insert( + BookRoute::CliTranslate { + section: CliTranslateSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(73usize), + ); + pages.push((74usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Contributing".to_string(), + url: BookRoute::ContributingIndex { + section: ContributingIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Contributing".to_string(), + id: "contributing".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Improving Docs".to_string(), + id: "improving-docs".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Working on the Ecosystem".to_string(), + id: "working-on-the-ecosystem".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Bugs & Features".to_string(), + id: "bugs--features".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Before you contribute".to_string(), + id: "before-you-contribute".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "How to test dioxus with local crate".to_string(), + id: "how-to-test-dioxus-with-local-crate".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(74usize), + } + })); + page_id_mapping.insert( + BookRoute::ContributingIndex { + section: ContributingIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(74usize), + ); + pages.push((75usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Project Structure".to_string(), + url: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Project Structure".to_string(), + id: "project-structure".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Renderers".to_string(), + id: "renderers".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "State Management/Hooks".to_string(), + id: "state-managementhooks".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Core utilities".to_string(), + id: "core-utilities".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Native Renderer Utilities".to_string(), + id: "native-renderer-utilities".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web renderer tooling".to_string(), + id: "web-renderer-tooling".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Developer tooling".to_string(), + id: "developer-tooling".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(75usize), + } + })); + page_id_mapping.insert( + BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(75usize), + ); + pages.push((76usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Walkthrough of Internals".to_string(), + url: BookRoute::ContributingWalkthroughReadme { + section: ContributingWalkthroughReadmeSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Walkthrough of the Hello World Example Internals".to_string(), + id: "walkthrough-of-the-hello-world-example-internals".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The Source File".to_string(), + id: "the-source-file".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The rsx! Macro".to_string(), + id: "the-rsx-macro".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Launching the App".to_string(), + id: "launching-the-app".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The Virtual DOM".to_string(), + id: "the-virtual-dom".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The Initial Render".to_string(), + id: "the-initial-render".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Waiting for Events".to_string(), + id: "waiting-for-events".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Diffing Scopes".to_string(), + id: "diffing-scopes".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conclusion".to_string(), + id: "conclusion".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(76usize), + } + })); + page_id_mapping.insert( + BookRoute::ContributingWalkthroughReadme { + section: ContributingWalkthroughReadmeSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(76usize), + ); + pages.push((77usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Guiding Principles".to_string(), + url: BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Overall Goals".to_string(), + id: "overall-goals".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Cross-Platform".to_string(), + id: "cross-platform".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Performance".to_string(), + id: "performance".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Type Safety".to_string(), + id: "type-safety".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Developer Experience".to_string(), + id: "developer-experience".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(77usize), + } + })); + page_id_mapping.insert( + BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(77usize), + ); + pages.push((78usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Roadmap".to_string(), + url: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Roadmap & Feature-set".to_string(), + id: "roadmap--feature-set".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Features".to_string(), + id: "features".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Roadmap".to_string(), + id: "roadmap".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Core".to_string(), + id: "core".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "SSR".to_string(), + id: "ssr".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Desktop".to_string(), + id: "desktop".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Mobile".to_string(), + id: "mobile".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Bundling (CLI)".to_string(), + id: "bundling-cli".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Essential hooks".to_string(), + id: "essential-hooks".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Work in Progress".to_string(), + id: "work-in-progress".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Build Tool".to_string(), + id: "build-tool".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Server Component Support".to_string(), + id: "server-component-support".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Native rendering".to_string(), + id: "native-rendering".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(78usize), + } + })); + page_id_mapping.insert( + BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(78usize), + ); + pages.push((79usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Migration".to_string(), + url: BookRoute::MigrationIndex { + section: MigrationIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "0.3 Migration Guide".to_string(), + id: "03-migration-guide".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(79usize), + } + })); + page_id_mapping.insert( + BookRoute::MigrationIndex { + section: MigrationIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(79usize), + ); + pages.push((80usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Router".to_string(), + url: BookRoute::MigrationRouter { + section: MigrationRouterSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Router".to_string(), + id: "router".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Defining Your Router".to_string(), + id: "defining-your-router".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Linking to routes".to_string(), + id: "linking-to-routes".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "External Links".to_string(), + id: "external-links".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "use_router".to_string(), + id: "use-router".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "use_route".to_string(), + id: "use-route".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "use_navigator".to_string(), + id: "use-navigator".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "New features".to_string(), + id: "new-features".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(80usize), + } + })); + page_id_mapping.insert( + BookRoute::MigrationRouter { + section: MigrationRouterSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(80usize), + ); + pages.push((81usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Hot Reload".to_string(), + url: BookRoute::MigrationHotReload { + section: MigrationHotReloadSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Hot reloading".to_string(), + id: "hot-reloading".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(81usize), + } + })); + page_id_mapping.insert( + BookRoute::MigrationHotReload { + section: MigrationHotReloadSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(81usize), + ); + ::use_mdbook::mdbook_shared::MdBook { + summary: ::use_mdbook::mdbook_shared::Summary { + title: Some("Summary".to_string()), + prefix_chapters: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Introduction".to_string(), + location: Some(BookRoute::Index { + section: IndexSection::Empty, + }), + number: None, + nested_items: vec![], + }), + ], + numbered_chapters: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Getting Started".to_string(), + location: Some(BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Choosing A Web Renderer".to_string(), + location: Some(BookRoute::GettingStartedChoosingAWebRenderer { + section: GettingStartedChoosingAWebRendererSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Web".to_string(), + location: Some(BookRoute::GettingStartedWasm { + section: GettingStartedWasmSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Liveview".to_string(), + location: Some(BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32, 3u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Fullstack".to_string(), + location: Some(BookRoute::GettingStartedFullstack { + section: GettingStartedFullstackSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32, 4u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Desktop".to_string(), + location: Some(BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32, 5u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Mobile".to_string(), + location: Some(BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32, 6u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Terminal UI".to_string(), + location: Some(BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32, 7u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Guide".to_string(), + location: Some(BookRoute::GuideIndex { + section: GuideIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Your First Component".to_string(), + location: Some(BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "State".to_string(), + location: Some(BookRoute::GuideState { + section: GuideStateSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Data Fetching".to_string(), + location: Some(BookRoute::GuideDataFetching { + section: GuideDataFetchingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32, 3u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Full Code".to_string(), + location: Some(BookRoute::GuideFullCode { + section: GuideFullCodeSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32, 4u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Separator, + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Reference".to_string(), + location: Some(BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "RSX".to_string(), + location: Some(BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Components".to_string(), + location: Some(BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Props".to_string(), + location: Some(BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 3u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Event Handlers".to_string(), + location: Some(BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 4u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Hooks".to_string(), + location: Some(BookRoute::ReferenceHooks { + section: ReferenceHooksSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 5u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "User Input".to_string(), + location: Some(BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 6u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Context".to_string(), + location: Some(BookRoute::ReferenceContext { + section: ReferenceContextSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 7u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Dynamic Rendering".to_string(), + location: Some(BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 8u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Routing".to_string(), + location: Some(BookRoute::ReferenceRouter { + section: ReferenceRouterSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 9u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "UseFuture".to_string(), + location: Some(BookRoute::ReferenceUseFuture { + section: ReferenceUseFutureSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 10u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "UseCoroutine".to_string(), + location: Some(BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 11u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Spawn".to_string(), + location: Some(BookRoute::ReferenceSpawn { + section: ReferenceSpawnSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 12u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Desktop".to_string(), + location: Some(BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 13u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Web".to_string(), + location: Some(BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 14u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "SSR".to_string(), + location: Some(BookRoute::ReferenceSsr { + section: ReferenceSsrSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 15u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Liveview".to_string(), + location: Some(BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 16u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Fullstack".to_string(), + location: Some(BookRoute::ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 17u32], + ), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Server Functions".to_string(), + location: Some(BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 17u32, 1u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Extractors".to_string(), + location: Some(BookRoute::ReferenceFullstackExtractors { + section: ReferenceFullstackExtractorsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 17u32, 2u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Middleware".to_string(), + location: Some(BookRoute::ReferenceFullstackMiddleware { + section: ReferenceFullstackMiddlewareSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 17u32, 3u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Authentication".to_string(), + location: Some(BookRoute::ReferenceFullstackAuthentication { + section: ReferenceFullstackAuthenticationSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 17u32, 4u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Routing".to_string(), + location: Some(BookRoute::ReferenceFullstackRouting { + section: ReferenceFullstackRoutingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 17u32, 5u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Separator, + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Router".to_string(), + location: Some(BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![4u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Example Project".to_string(), + location: Some(BookRoute::RouterExampleIndex { + section: RouterExampleIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![4u32, 1u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Creating Our First Route".to_string(), + location: Some(BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 1u32, 1u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Building a Nest".to_string(), + location: Some(BookRoute::RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 1u32, 2u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Navigation Targets".to_string(), + location: Some(BookRoute::RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 1u32, 3u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Redirection Perfection".to_string(), + location: Some(BookRoute::RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 1u32, 4u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Full Code".to_string(), + location: Some(BookRoute::RouterExampleFullCode { + section: RouterExampleFullCodeSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 1u32, 5u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Reference".to_string(), + location: Some(BookRoute::RouterReferenceIndex { + section: RouterReferenceIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![4u32, 2u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Defining Routes".to_string(), + location: Some(BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 1u32], + ), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Nested Routes".to_string(), + location: Some(BookRoute::RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 1u32, 1u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Layouts".to_string(), + location: Some(BookRoute::RouterReferenceLayouts { + section: RouterReferenceLayoutsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 2u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Navigation".to_string(), + location: Some(BookRoute::RouterReferenceNavigationIndex { + section: RouterReferenceNavigationIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 3u32], + ), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Programmatic Navigation".to_string(), + location: Some(BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 3u32, 1u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "History Providers".to_string(), + location: Some(BookRoute::RouterReferenceHistoryProviders { + section: RouterReferenceHistoryProvidersSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 4u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "History Buttons".to_string(), + location: Some(BookRoute::RouterReferenceHistoryButtons { + section: RouterReferenceHistoryButtonsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 5u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Static Generation".to_string(), + location: Some(BookRoute::RouterReferenceStaticGeneration { + section: RouterReferenceStaticGenerationSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 6u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Routing Update Callback".to_string(), + location: Some(BookRoute::RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 7u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Separator, + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Cookbook".to_string(), + location: Some(BookRoute::CookbookIndex { + section: CookbookIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Publishing".to_string(), + location: Some(BookRoute::CookbookPublishing { + section: CookbookPublishingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Anti-patterns".to_string(), + location: Some(BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Error Handling".to_string(), + location: Some(BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 3u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Integrations".to_string(), + location: Some(BookRoute::CookbookIntegrationsIndex { + section: CookbookIntegrationsIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 4u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Logging".to_string(), + location: Some(BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![5u32, 4u32, 1u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Internationalization".to_string(), + location: Some(BookRoute::CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![5u32, 4u32, 2u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "State Management".to_string(), + location: Some(BookRoute::CookbookStateIndex { + section: CookbookStateIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 5u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "External State".to_string(), + location: Some(BookRoute::CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![5u32, 5u32, 1u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Custom Hooks".to_string(), + location: Some(BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![5u32, 5u32, 2u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Testing".to_string(), + location: Some(BookRoute::CookbookTesting { + section: CookbookTestingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 6u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Examples".to_string(), + location: Some(BookRoute::CookbookExamples { + section: CookbookExamplesSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 7u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Tailwind".to_string(), + location: Some(BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 8u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Custom Renderer".to_string(), + location: Some(BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 9u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Optimizing".to_string(), + location: Some(BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![5u32, 10u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Separator, + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "CLI".to_string(), + location: Some(BookRoute::CliIndex { + section: CliIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![6u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Installation".to_string(), + location: Some(BookRoute::CliInstallation { + section: CliInstallationSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![6u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Create a Project".to_string(), + location: Some(BookRoute::CliCreating { + section: CliCreatingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![6u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Configure Project".to_string(), + location: Some(BookRoute::CliConfigure { + section: CliConfigureSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![6u32, 3u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Translate HTML".to_string(), + location: Some(BookRoute::CliTranslate { + section: CliTranslateSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![6u32, 4u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Separator, + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Contributing".to_string(), + location: Some(BookRoute::ContributingIndex { + section: ContributingIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![7u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Project Structure".to_string(), + location: Some(BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![7u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Walkthrough of Internals".to_string(), + location: Some(BookRoute::ContributingWalkthroughReadme { + section: ContributingWalkthroughReadmeSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![7u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Guiding Principles".to_string(), + location: Some(BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![7u32, 3u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Roadmap".to_string(), + location: Some(BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![7u32, 4u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Separator, + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Migration".to_string(), + location: Some(BookRoute::MigrationIndex { + section: MigrationIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![8u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Router".to_string(), + location: Some(BookRoute::MigrationRouter { + section: MigrationRouterSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![8u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Hot Reload".to_string(), + location: Some(BookRoute::MigrationHotReload { + section: MigrationHotReloadSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![8u32, 2u32]), + ), + nested_items: vec![], + }), + ], + }), + ], + suffix_chapters: vec![], + }, + pages: pages.into_iter().collect(), + page_id_mapping, + } + }); +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum IndexSection { + #[default] + Empty, + Introduction, + Features, + Multiplatform, + Stability, +} +impl std::str::FromStr for IndexSection { + type Err = IndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "introduction" => Ok(Self::Introduction), + "features" => Ok(Self::Features), + "multiplatform" => Ok(Self::Multiplatform), + "stability" => Ok(Self::Stability), + _ => Err(IndexSectionParseError), + } + } +} +impl std::fmt::Display for IndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Introduction => f.write_str("introduction"), + Self::Features => f.write_str("features"), + Self::Multiplatform => f.write_str("multiplatform"), + Self::Stability => f.write_str("stability"), + } + } +} +#[derive(Debug)] +pub struct IndexSectionParseError; +impl std::fmt::Display for IndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of IndexSectionintroduction, features, multiplatform, stability", + )?; + Ok(()) + } +} +impl std::error::Error for IndexSectionParseError {} +#[component(no_case_check)] +pub fn Index(section: IndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "introduction", + Link { + to: BookRoute::Index { + section: IndexSection::Introduction, + }, + class: "header", + "Introduction" + } + } + p { + "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." + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\npub fn App(cx: Scope) -> Element {{\n    let mut count = use_state(cx, || 0);\n\n    cx.render(rsx! {{\n        h1 {{ "High-Five counter: {{count}}" }}\n        button {{ onclick: move |_| count += 1, "Up high!" }}\n        button {{ onclick: move |_| count -= 1, "Down low!" }}\n    }})\n}}
\n", + name: "readme.rs".to_string(), + } + DemoFrame { readme::App {} } + p { + "Dioxus is heavily inspired by React. If you know React, getting started with Dioxus will be a breeze." + } + blockquote { + p { + "This guide assumes you already know some " + Link { to: "https://www.rust-lang.org/", "Rust" } + "! If not, we recommend reading " + Link { to: "https://doc.rust-lang.org/book/ch01-00-getting-started.html", + em { "the book" } + } + " to learn Rust first." + } + } + h2 { id: "features", + Link { + to: BookRoute::Index { + section: IndexSection::Features, + }, + class: "header", + "Features" + } + } + ul { + li { "Desktop apps running natively (no Electron!) in less than 10 lines of code." } + li { "Incredibly ergonomic and powerful state management." } + li { + "Comprehensive inline documentation – hover and guides for all HTML elements, listeners, and events." + } + li { "Extremely memory efficient – 0 global allocations for steady-state components." } + li { "Multi-channel asynchronous scheduler for first-class async support." } + li { + "And more! Read the " + Link { to: "https://dioxuslabs.com/blog/introducing-dioxus/", "full release post" } + "." + } + } + h3 { id: "multiplatform", + Link { + to: BookRoute::Index { + section: IndexSection::Multiplatform, + }, + class: "header", + "Multiplatform" + } + } + p { + "Dioxus is a " + em { "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 " + code { "html" } + " feature enabled, but this can be disabled depending on your target renderer." + } + p { "Right now, we have several 1st-party renderers:" } + ul { + li { "WebSys (for WASM): Great support" } + li { "Tao/Tokio (for Desktop apps): Good support" } + li { "Tao/Tokio (for Mobile apps): Poor support" } + li { "SSR (for generating static markup)" } + li { "TUI/Rink (for terminal-based apps): Experimental" } + } + h2 { id: "stability", + Link { + to: BookRoute::Index { + section: IndexSection::Stability, + }, + class: "header", + "Stability" + } + } + p { "Dioxus has not reached a stable release yet." } + p { + "Web: Since the web is a fairly mature platform, we expect there to be very little API churn for web-based features." + } + p { + "Desktop: APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart." + } + p { "SSR: We don't expect the SSR API to change drastically in the future." } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedIndexSection { + #[default] + Empty, + GettingStarted, + Prerequisites, + AnEditor, + Rust, + SetupGuides, +} +impl std::str::FromStr for GettingStartedIndexSection { + type Err = GettingStartedIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "getting-started" => Ok(Self::GettingStarted), + "prerequisites" => Ok(Self::Prerequisites), + "an-editor" => Ok(Self::AnEditor), + "rust" => Ok(Self::Rust), + "setup-guides" => Ok(Self::SetupGuides), + _ => Err(GettingStartedIndexSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::GettingStarted => f.write_str("getting-started"), + Self::Prerequisites => f.write_str("prerequisites"), + Self::AnEditor => f.write_str("an-editor"), + Self::Rust => f.write_str("rust"), + Self::SetupGuides => f.write_str("setup-guides"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedIndexSectionParseError; +impl std::fmt::Display for GettingStartedIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedIndexSectiongetting-started, prerequisites, an-editor, rust, setup-guides", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedIndexSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedIndex(section: GettingStartedIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "getting-started", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::GettingStarted, + }, + class: "header", + "Getting Started" + } + } + p { "This section will help you set up your Dioxus project!" } + h2 { id: "prerequisites", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Prerequisites, + }, + class: "header", + "Prerequisites" + } + } + h3 { id: "an-editor", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::AnEditor, + }, + class: "header", + "An Editor" + } + } + p { + "Dioxus integrates very well with the " + Link { to: "https://rust-analyzer.github.io", "Rust-Analyzer LSP plugin" } + " which will provide appropriate syntax highlighting, code navigation, folding, and more." + } + h3 { id: "rust", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Rust, + }, + class: "header", + "Rust" + } + } + p { + "Head over to " + Link { to: "http://rust-lang.org", "https://rust-lang.org" } + " and install the Rust compiler." + } + p { + "We strongly recommend going through the " + Link { to: "https://doc.rust-lang.org/book/ch01-00-getting-started.html", + "official Rust book" + } + " " + em { "completely" } + ". However, we hope that a Dioxus app can serve as a great first Rust project. With Dioxus, you'll learn about:" + } + ul { + li { "Error handling" } + li { "Structs, Functions, Enums" } + li { "Closures" } + li { "Macros" } + } + p { + "We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need deep knowledge of async, lifetimes, or smart pointers until you start building complex Dioxus apps." + } + h2 { id: "setup-guides", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::SetupGuides, + }, + class: "header", + "Setup Guides" + } + } + p { + "Dioxus supports multiple platforms. Choose the platform you want to target below to get platform-specific setup instructions:" + } + ul { + li { + Link { + to: BookRoute::GettingStartedChoosingAWebRenderer { + section: GettingStartedChoosingAWebRendererSection::Empty, + }, + "Choosing a Web Renderer" + } + } + li { + Link { + to: BookRoute::GettingStartedWasm { + section: GettingStartedWasmSection::Empty, + }, + "Client Side" + } + ": runs in the browser through WebAssembly" + } + li { + Link { + to: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Empty, + }, + "Liveview" + } + ": runs on the server, renders in the browser using WebSockets" + } + li { + Link { + to: BookRoute::GettingStartedFullstack { + section: GettingStartedFullstackSection::Empty, + }, + "Fullstack" + } + ": renders to HTML text on the server and hydrates it on the client" + } + li { + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Empty, + }, + "Desktop" + } + ": runs in a web view on desktop" + } + li { + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::Empty, + }, + "Mobile" + } + ": runs in a web view on mobile" + } + li { + Link { + to: BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::Empty, + }, + "Terminal UI" + } + ": renders text-based graphics in the terminal" + } + } + blockquote { + p { + "More information on any platform you choose is available in the section of the same name in the " + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Empty, + }, + "Reference" + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedChoosingAWebRendererSection { + #[default] + Empty, + ChoosingAWebRenderer, + DioxusLiveview, + DioxusWeb, + DioxusFullstack, +} +impl std::str::FromStr for GettingStartedChoosingAWebRendererSection { + type Err = GettingStartedChoosingAWebRendererSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "choosing-a-web-renderer" => Ok(Self::ChoosingAWebRenderer), + "dioxus-liveview" => Ok(Self::DioxusLiveview), + "dioxus-web" => Ok(Self::DioxusWeb), + "dioxus-fullstack" => Ok(Self::DioxusFullstack), + _ => Err(GettingStartedChoosingAWebRendererSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedChoosingAWebRendererSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ChoosingAWebRenderer => f.write_str("choosing-a-web-renderer"), + Self::DioxusLiveview => f.write_str("dioxus-liveview"), + Self::DioxusWeb => f.write_str("dioxus-web"), + Self::DioxusFullstack => f.write_str("dioxus-fullstack"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedChoosingAWebRendererSectionParseError; +impl std::fmt::Display for GettingStartedChoosingAWebRendererSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedChoosingAWebRendererSectionchoosing-a-web-renderer, dioxus-liveview, dioxus-web, dioxus-fullstack", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedChoosingAWebRendererSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedChoosingAWebRenderer( + section: GettingStartedChoosingAWebRendererSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "choosing-a-web-renderer", + Link { + to: BookRoute::GettingStartedChoosingAWebRenderer { + section: GettingStartedChoosingAWebRendererSection::ChoosingAWebRenderer, + }, + class: "header", + "Choosing a web renderer" + } + } + p { "Dioxus has three different renderers that target the web:" } + ul { + li { + Link { + to: BookRoute::GettingStartedWasm { + section: GettingStartedWasmSection::Empty, + }, + "dioxus-web" + } + " allows you to render your application to HTML with " + Link { to: "https://rustwasm.github.io/docs/book/", "WebAssembly" } + " on the client" + } + li { + Link { + to: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Empty, + }, + "dioxus-liveview" + } + " allows you to run your application on the server and render it to HTML on the client with a websocket" + } + li { + Link { + to: BookRoute::GettingStartedFullstack { + section: GettingStartedFullstackSection::Empty, + }, + "dioxus-fullstack" + } + " allows you to initially render static HTML on the server and then update that HTML from the client with " + Link { to: "https://rustwasm.github.io/docs/book/", "WebAssembly" } + } + } + p { "Each approach has its tradeoffs:" } + h3 { id: "dioxus-liveview", + Link { + to: BookRoute::GettingStartedChoosingAWebRenderer { + section: GettingStartedChoosingAWebRendererSection::DioxusLiveview, + }, + class: "header", + "Dioxus Liveview" + } + } + ul { + li { + p { + "Liveview rendering communicates with the server over a WebSocket connection. It essentially moves all of the work that Client-side rendering does to the server." + } + } + li { + p { + "This makes it " + strong { + "easy to communicate with the server, but more difficult to communicate with the client/browser APIS" + } + "." + } + } + li { + p { + "Each interaction also requires a message to be sent to the server and back which can cause " + strong { "issues with latency" } + "." + } + } + li { + p { + "Because Liveview uses a websocket to render, the page will be blank until the WebSocket connection has been established and the first renderer has been sent from the websocket. Just like with client side rendering, this can make the page " + strong { "less SEO-friendly" } + "." + } + } + li { + p { + "Because the page is rendered on the server and the page is sent to the client piece by piece, you never need to send the entire application to the client. The initial load time can be faster than client-side rendering with large applications because Liveview only needs to send a constant small websocket script regardless of the size of the application." + } + } + } + blockquote { + p { + "Liveview is a good fit for applications that already need to communicate with the server frequently (like real time collaborative apps), but don't need to communicate with as many client/browser APIs." + } + } + p { + Link { to: "https://mermaid.live/edit#pako:eNplULFOw0AM_RXLc7Mw3sBQVUIMRYgKdcli5ZzkRHIuPl8QqvrvXJICRXiy3nt-9-6dsRHP6DAZGe8CdUpjNd3VEcpsVT4SK1TVPRxYJ1YHL_yeOdkqWMGF3w4U32Y6nSQmXvknMQYNXW8g7bfk2JPBg0g3MCTmdH1rJhenx2is1FiYri43wJ8or3O2H1Liv0w3hw724kMb2MMzdcUYNziyjhR8-f15Pq3Reh65RldWzy3lwWqs46VIKZscPmODzjTzBvPJ__aFrqUhFZR9MNH92uhS7OULYSF1lw", + img { + src: "https://mermaid.ink/img/pako:eNplULFOw0AM_RXLc7Mw3sBQVUIMRYgKdcli5ZzkRHIuPl8QqvrvXJICRXiy3nt-9-6dsRHP6DAZGe8CdUpjNd3VEcpsVT4SK1TVPRxYJ1YHL_yeOdkqWMGF3w4U32Y6nSQmXvknMQYNXW8g7bfk2JPBg0g3MCTmdH1rJhenx2is1FiYri43wJ8or3O2H1Liv0w3hw724kMb2MMzdcUYNziyjhR8-f15Pq3Reh65RldWzy3lwWqs46VIKZscPmODzjTzBvPJ__aFrqUhFZR9MNH92uhS7OULYSF1lw?type=png", + alt: "", + title: "", + } + } + } + h3 { id: "dioxus-web", + Link { + to: BookRoute::GettingStartedChoosingAWebRenderer { + section: GettingStartedChoosingAWebRendererSection::DioxusWeb, + }, + class: "header", + "Dioxus Web" + } + } + ul { + li { + p { + "With Client side rendering, you send your application to the client, and then the client generates all of the HTML of the page dynamically." + } + } + li { + p { + "This means that the page will be blank until the JavaScript bundle has loaded and the application has initialized. This can result in " + strong { "slower first render times and poor SEO performance" } + "." + } + } + } + blockquote { + p { + "SEO stands for Search Engine Optimization. It refers to the practice of making your website more likely to appear in search engine results. Search engines like Google and Bing use web crawlers to index the content of websites. Most of these crawlers are not able to run JavaScript, so they will not be able to index the content of your page if it is rendered client-side." + } + } + ul { + li { + "Client-side rendered applications need to use " + strong { "weakly typed requests to communicate with the server" } + "." + } + } + blockquote { + p { + "Client-side rendering is a good starting point for most applications. It is well supported and makes it easy to communicate with the client/browser APIs." + } + } + p { + Link { to: "https://mermaid.live/edit#pako:eNpVkDFPwzAQhf-KdXOzMHpgqJAQAwytEIsXK35JLBJfez4Xoar_HSemQtzke9_z2e-u1HMAWcrqFU_Rj-KX7vLgkqm1F_7KENN1j-YIuUCsOeBckLUZmrjx_ezT54rziVNG42-sMBLHSQ0Pd8vH5NU8M48zTAby71sr3CYdkAIEoen37h-y5n3910tSiO81cqIdLZDFx1DDXNerjnTCAke2HgMGX2Z15NKtWn1RPn6nnqxKwY7KKfzFJzv4OVcVISrLa1vQtqfbDzd0ZKY", + img { + src: "https://mermaid.ink/img/pako:eNpVkDFPwzAQhf-KdXOzMHpgqJAQAwytEIsXK35JLBJfez4Xoar_HSemQtzke9_z2e-u1HMAWcrqFU_Rj-KX7vLgkqm1F_7KENN1j-YIuUCsOeBckLUZmrjx_ezT54rziVNG42-sMBLHSQ0Pd8vH5NU8M48zTAby71sr3CYdkAIEoen37h-y5n3910tSiO81cqIdLZDFx1DDXNerjnTCAke2HgMGX2Z15NKtWn1RPn6nnqxKwY7KKfzFJzv4OVcVISrLa1vQtqfbDzd0ZKY?type=png", + alt: "", + title: "", + } + } + } + h3 { id: "dioxus-fullstack", + Link { + to: BookRoute::GettingStartedChoosingAWebRenderer { + section: GettingStartedChoosingAWebRendererSection::DioxusFullstack, + }, + class: "header", + "Dioxus Fullstack" + } + } + p { "Fullstack rendering happens in two parts:" } + ol { + li { + "The page is rendered on the server. This can include fetching any data you need to render the page." + } + li { + "The page is hydrated on the client. (Hydration is taking the HTML page from the server and adding all of the event listeners Dioxus needs on the client). Any updates to the page happen on the client after this point." + } + } + p { + "Because the page is initially rendered on the server, the page will be fully rendered when it is sent to the client. This results in a faster first render time and makes the page more SEO-friendly." + } + ul { + li { + strong { "Fast initial render" } + } + li { + strong { "Works well with SEO" } + } + li { + strong { "Type safe easy communication with the server" } + } + li { + strong { "Access to the client/browser APIs" } + } + li { + strong { "Fast interactivity" } + } + } + p { + "Finally, we can use " + Link { + to: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::Empty, + }, + "server functions" + } + " to communicate with the server in a type-safe way." + } + p { + "This approach uses both the dioxus-web and dioxus-ssr crates. To integrate those two packages and " + code { "axum" } + ", " + code { "warp" } + ", or " + code { "salvo" } + ", Dioxus provides the " + code { "dioxus-fullstack" } + " crate." + } + p { + "There can be more complexity with fullstack applications because your code runs in two different places. Dioxus tries to mitigate this with server functions and other helpers." + } + p { + Link { to: "https://mermaid.live/edit#pako:eNpdkL1uwzAMhF9F4BwvHTV0KAIUHdohQdFFi2CdbQG2mFCUiyDIu9e2-hOUE3H34UDelVoOIEtZvWIffS9-auYHl8wyT8KfGWKa5tEcITPEmgPOBVkrUMXNPyAFCMJK5BOnjIq8scJI7Ac13N1RH4NX88zcjzAZyJX-8bfIl6QQ32qcv7PuhP-ANe_rpb8KJ9rRBJl8DMt71zXAkQ6Y4Mgua0Dny6iOXLotqC_Kx0tqyaoU7Kicwl8hZDs_5kVFiMryWivbmrt9AacxbGg", + img { + src: "https://mermaid.ink/img/pako:eNpdkL1uwzAMhF9F4BwvHTV0KAIUHdohQdFFi2CdbQG2mFCUiyDIu9e2-hOUE3H34UDelVoOIEtZvWIffS9-auYHl8wyT8KfGWKa5tEcITPEmgPOBVkrUMXNPyAFCMJK5BOnjIq8scJI7Ac13N1RH4NX88zcjzAZyJX-8bfIl6QQ32qcv7PuhP-ANe_rpb8KJ9rRBJl8DMt71zXAkQ6Y4Mgua0Dny6iOXLotqC_Kx0tqyaoU7Kicwl8hZDs_5kVFiMryWivbmrt9AacxbGg?type=png", + alt: "", + title: "", + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedWasmSection { + #[default] + Empty, + Web, + Support, + Tooling, + CreatingAProject, + HotReload, + Setup, + Usage, + Limitations, +} +impl std::str::FromStr for GettingStartedWasmSection { + type Err = GettingStartedWasmSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "web" => Ok(Self::Web), + "support" => Ok(Self::Support), + "tooling" => Ok(Self::Tooling), + "creating-a-project" => Ok(Self::CreatingAProject), + "hot-reload" => Ok(Self::HotReload), + "setup" => Ok(Self::Setup), + "usage" => Ok(Self::Usage), + "limitations" => Ok(Self::Limitations), + _ => Err(GettingStartedWasmSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedWasmSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Web => f.write_str("web"), + Self::Support => f.write_str("support"), + Self::Tooling => f.write_str("tooling"), + Self::CreatingAProject => f.write_str("creating-a-project"), + Self::HotReload => f.write_str("hot-reload"), + Self::Setup => f.write_str("setup"), + Self::Usage => f.write_str("usage"), + Self::Limitations => f.write_str("limitations"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedWasmSectionParseError; +impl std::fmt::Display for GettingStartedWasmSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedWasmSectionweb, support, tooling, creating-a-project, hot-reload, setup, usage, limitations", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedWasmSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedWasm(section: GettingStartedWasmSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "web", + Link { + to: BookRoute::GettingStartedWasm { + section: GettingStartedWasmSection::Web, + }, + class: "header", + "Web" + } + } + p { + "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 " + code { "dioxus" } + " and " + code { "dioxus-web" } + " crates." + } + p { + "A build of Dioxus for the web will be roughly equivalent to the size of a React build (70kb vs 65kb) but it will load significantly faster because " + Link { to: "https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/", + "WebAssembly can be compiled as it is streamed" + } + "." + } + p { "Examples:" } + ul { + li { + Link { to: "https://github.com/DioxusLabs/example-projects/tree/master/todomvc", + "TodoMVC" + } + } + li { + Link { to: "https://github.com/DioxusLabs/example-projects/tree/master/ecommerce-site", + "ECommerce" + } + } + } + p { + Link { to: "https://github.com/DioxusLabs/example-projects/blob/master/todomvc", + img { + src: "https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png", + alt: "TodoMVC example", + title: "", + } + } + } + blockquote { + p { + "Note: Because of the limitations of Wasm, " + Link { to: "https://rustwasm.github.io/docs/book/reference/which-crates-work-with-wasm.html", + "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)." + } + } + h2 { id: "support", + Link { + to: BookRoute::GettingStartedWasm { + section: GettingStartedWasmSection::Support, + }, + class: "header", + "Support" + } + } + p { "The Web is the best-supported target platform for Dioxus." } + ul { + li { + "Because your app will be compiled to WASM you have access to browser APIs through " + Link { to: "https://rustwasm.github.io/docs/wasm-bindgen/introduction.html", + "wasm-bindgen" + } + "." + } + li { + "Dioxus provides hydration to resume apps that are rendered on the server. See the " + Link { + to: BookRoute::GettingStartedFullstack { + section: GettingStartedFullstackSection::Empty, + }, + "fullstack" + } + " getting started guide for more information." + } + } + h2 { id: "tooling", + Link { + to: BookRoute::GettingStartedWasm { + section: GettingStartedWasmSection::Tooling, + }, + class: "header", + "Tooling" + } + } + p { + "To develop your Dioxus app for the web, you'll need a tool to build and serve your assets. We recommend using " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/cli", + "dioxus-cli" + } + " which includes a build system, Wasm optimization, a dev server, and support hot reloading:" + } + CodeBlock { contents: "
\ncargo install dioxus-cli
\n" } + p { + "Make sure the " + code { "wasm32-unknown-unknown" } + " target for rust is installed:" + } + CodeBlock { contents: "
\nrustup target add wasm32-unknown-unknown
\n" } + h2 { id: "creating-a-project", + Link { + to: BookRoute::GettingStartedWasm { + section: GettingStartedWasmSection::CreatingAProject, + }, + class: "header", + "Creating a Project" + } + } + p { "Create a new crate:" } + CodeBlock { contents: "
\ncargo new --bin demo\ncd demo
\n" } + p { + "Add Dioxus and the web renderer as dependencies (this will edit your " + code { "Cargo.toml" } + "):" + } + CodeBlock { contents: "
\ncargo add dioxus\ncargo add dioxus-web
\n" } + p { + "Edit your " + code { "main.rs" } + ":" + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\n// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types\nuse dioxus::prelude::*;\n\nfn main() {{\n    // launch the web app\n    dioxus_web::launch(App);\n}}\n\n// create a component that renders a div with the text "Hello, world!"\nfn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            "Hello, world!"\n        }}\n    }})\n}}
\n", + name: "hello_world_web.rs".to_string(), + } + p { "And to serve our app:" } + CodeBlock { contents: "
\ndx serve
\n" } + p { + "If you open the browser and navigate to " + code { "127.0.0.1" } + " you should see an app that looks like this:" + } + DemoFrame { hello_world::HelloWorldCounter {} } + h2 { id: "hot-reload", + Link { + to: BookRoute::GettingStartedWasm { + section: GettingStartedWasmSection::HotReload, + }, + class: "header", + "Hot Reload" + } + } + ol { + li { + "Hot reloading allows much faster iteration times inside of rsx calls by interpreting them and streaming the edits." + } + li { + "It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program." + } + } + p { + "For the web renderer, you can use the dioxus cli to serve your application with hot reloading enabled." + } + h3 { id: "setup", + Link { + to: BookRoute::GettingStartedWasm { + section: GettingStartedWasmSection::Setup, + }, + class: "header", + "Setup" + } + } + p { + "Install " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/cli", + "dioxus-cli" + } + "." + } + h3 { id: "usage", + Link { + to: BookRoute::GettingStartedWasm { + section: GettingStartedWasmSection::Usage, + }, + class: "header", + "Usage" + } + } + ol { + li { "Run:" } + } + CodeBlock { contents: "
\ndx serve --hot-reload
\n" } + ol { + li { "Change some code within a rsx or render macro" } + li { "Open your localhost in a browser" } + li { "Save and watch the style change without recompiling" } + } + h3 { id: "limitations", + Link { + to: BookRoute::GettingStartedWasm { + section: GettingStartedWasmSection::Limitations, + }, + class: "header", + "Limitations" + } + } + ol { + li { + "The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will require a full recompile to capture the expression." + } + li { + "Components, Iterators, and some attributes can contain arbitrary rust code and will trigger a full recompile when changed." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedLiveviewSection { + #[default] + Empty, + Liveview, + Support, + Setup, + HotReload, + HotReloadSetup, + Usage, + Limitations, +} +impl std::str::FromStr for GettingStartedLiveviewSection { + type Err = GettingStartedLiveviewSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "liveview" => Ok(Self::Liveview), + "support" => Ok(Self::Support), + "setup" => Ok(Self::Setup), + "hot-reload" => Ok(Self::HotReload), + "hot-reload-setup" => Ok(Self::HotReloadSetup), + "usage" => Ok(Self::Usage), + "limitations" => Ok(Self::Limitations), + _ => Err(GettingStartedLiveviewSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedLiveviewSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Liveview => f.write_str("liveview"), + Self::Support => f.write_str("support"), + Self::Setup => f.write_str("setup"), + Self::HotReload => f.write_str("hot-reload"), + Self::HotReloadSetup => f.write_str("hot-reload-setup"), + Self::Usage => f.write_str("usage"), + Self::Limitations => f.write_str("limitations"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedLiveviewSectionParseError; +impl std::fmt::Display for GettingStartedLiveviewSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedLiveviewSectionliveview, support, setup, hot-reload, hot-reload-setup, usage, limitations", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedLiveviewSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedLiveview(section: GettingStartedLiveviewSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "liveview", + Link { + to: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Liveview, + }, + class: "header", + "Liveview" + } + } + p { + "Liveview allows apps to " + em { "run" } + " on the server and " + em { "render" } + " in the browser. It uses WebSockets to communicate between the server and the browser." + } + p { "Examples:" } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/axum.rs", + "Axum Example" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/salvo.rs", + "Salvo Example" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples/warp.rs", + "Warp Example" + } + } + } + h2 { id: "support", + Link { + to: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Support, + }, + class: "header", + "Support" + } + } + p { + "Liveview is currently limited in capability when compared to the Web platform. Liveview apps run on the server in 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." + } + h2 { id: "setup", + Link { + to: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Setup, + }, + class: "header", + "Setup" + } + } + p { + "For this guide, we're going to show how to use Dioxus Liveview with " + Link { to: "https://docs.rs/axum/latest/axum/", "Axum" } + "." + } + p { "Make sure you have Rust and Cargo installed, and then create a new project:" } + CodeBlock { contents: "
\ncargo new --bin demo\ncd demo
\n" } + p { "Add Dioxus and the liveview renderer with the Axum feature as dependencies:" } + CodeBlock { contents: "
\ncargo add dioxus\ncargo add dioxus-liveview --features axum
\n" } + p { + "Next, add all the Axum dependencies. This will be different if you're using a different Web Framework" + } + CodeBlock { contents: "
\ncargo add tokio --features full\ncargo add axum
\n" } + p { "Your dependencies should look roughly like this:" } + CodeBlock { + contents: "
\n[dependencies]\naxum = "0.4.5"\ndioxus = {{ version = "*" }}\ndioxus-liveview = {{ version = "*", features = ["axum"] }}\ntokio = {{ version = "1.15.0", features = ["full"] }}
\n", + } + p { "Now, set up your Axum app to respond on an endpoint." } + CodeBlock { + contents: "
\nuse axum::{{extract::ws::WebSocketUpgrade, response::Html, routing::get, Router}};\nuse dioxus::prelude::*;\n\n#[tokio::main]\nasync fn main() {{\n    let addr: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into();\n\n    let view = dioxus_liveview::LiveViewPool::new();\n\n    let app = Router::new()\n        // The root route contains the glue code to connect to the WebSocket\n        .route(\n            "/",\n            get(move || async move {{\n                Html(format!(\n                    r#"\n                <!DOCTYPE html>\n                <html>\n                <head> <title>Dioxus LiveView with Axum</title>  </head>\n                <body> <div id="main"></div> </body>\n                {{glue}}\n                </html>\n                "#,\n                    // Create the glue code to connect to the WebSocket on the "/ws" route\n                    glue = dioxus_liveview::interpreter_glue(&format!("ws://{{addr}}/ws"))\n                ))\n            }}),\n        )\n        // The WebSocket route is what Dioxus uses to communicate with the browser\n        .route(\n            "/ws",\n            get(move |ws: WebSocketUpgrade| async move {{\n                ws.on_upgrade(move |socket| async move {{\n                    // When the WebSocket is upgraded, launch the LiveView with the app component\n                    _ = view.launch(dioxus_liveview::axum_socket(socket), app).await;\n                }})\n            }}),\n        );\n\n    println!("Listening on http://{{addr}}");\n\n    axum::Server::bind(&addr.to_string().parse().unwrap())\n        .serve(app.into_make_service())\n        .await\n        .unwrap();\n}}
\n", + name: "hello_world_liveview.rs".to_string(), + } + p { "And then add our app component:" } + CodeBlock { + contents: "
\nfn app(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            "Hello, world!"\n        }}\n    }})\n}}
\n", + name: "hello_world_liveview.rs".to_string(), + } + p { "And that's it!" } + h2 { id: "hot-reload", + Link { + to: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::HotReload, + }, + class: "header", + "Hot Reload" + } + } + ol { + li { + "Hot reloading allows much faster iteration times inside of rsx calls by interpreting them and streaming the edits." + } + li { + "It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program." + } + } + h3 { id: "hot-reload-setup", + Link { + to: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::HotReloadSetup, + }, + class: "header", + "Hot Reload Setup" + } + } + p { + "Install " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/cli", + "dioxus-cli" + } + "." + } + h3 { id: "usage", + Link { + to: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Usage, + }, + class: "header", + "Usage" + } + } + ol { + li { "Run:" } + } + CodeBlock { contents: "
\ndx serve --hot-reload --platform desktop
\n" } + ol { + li { + "Change some code within " + code { "rsx" } + " or " + code { "render" } + " macro" + } + li { "Save and watch the style change without recompiling" } + } + h3 { id: "limitations", + Link { + to: BookRoute::GettingStartedLiveview { + section: GettingStartedLiveviewSection::Limitations, + }, + class: "header", + "Limitations" + } + } + ol { + li { + "The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will require a full recompile to capture the expression." + } + li { + "Components, Iterators, and some attributes can contain arbitrary rust code and will trigger a full recompile when changed." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedFullstackSection { + #[default] + Empty, + Fullstack, + GettingStarted, + Setup, + HotReload, + Usage, + Limitations, +} +impl std::str::FromStr for GettingStartedFullstackSection { + type Err = GettingStartedFullstackSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "fullstack" => Ok(Self::Fullstack), + "getting-started" => Ok(Self::GettingStarted), + "setup" => Ok(Self::Setup), + "hot-reload" => Ok(Self::HotReload), + "usage" => Ok(Self::Usage), + "limitations" => Ok(Self::Limitations), + _ => Err(GettingStartedFullstackSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedFullstackSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Fullstack => f.write_str("fullstack"), + Self::GettingStarted => f.write_str("getting-started"), + Self::Setup => f.write_str("setup"), + Self::HotReload => f.write_str("hot-reload"), + Self::Usage => f.write_str("usage"), + Self::Limitations => f.write_str("limitations"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedFullstackSectionParseError; +impl std::fmt::Display for GettingStartedFullstackSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedFullstackSectionfullstack, getting-started, setup, hot-reload, usage, limitations", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedFullstackSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedFullstack( + section: GettingStartedFullstackSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "fullstack", + Link { + to: BookRoute::GettingStartedFullstack { + section: GettingStartedFullstackSection::Fullstack, + }, + class: "header", + "Fullstack" + } + } + blockquote { + p { + "This guide assumes you read the " + Link { + to: BookRoute::GettingStartedWasm { + section: GettingStartedWasmSection::Empty, + }, + "Web" + } + " getting started guide and installed the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/cli", + "Dioxus-cli" + } + } + } + h1 { id: "getting-started", + Link { + to: BookRoute::GettingStartedFullstack { + section: GettingStartedFullstackSection::GettingStarted, + }, + class: "header", + "Getting Started" + } + } + h2 { id: "setup", + Link { + to: BookRoute::GettingStartedFullstack { + section: GettingStartedFullstackSection::Setup, + }, + class: "header", + "Setup" + } + } + p { + "For this guide, we're going to show how to use Dioxus with " + Link { to: "https://docs.rs/axum/latest/axum/", "Axum" } + ", but " + code { "dioxus-fullstack" } + " also integrates with the " + Link { to: "https://docs.rs/warp/latest/warp/", "Warp" } + " and " + Link { to: "https://docs.rs/salvo/latest/salvo/", "Salvo" } + " web frameworks." + } + p { "Make sure you have Rust and Cargo installed, and then create a new project:" } + CodeBlock { contents: "
\ncargo new --bin demo\ncd demo
\n" } + p { + "Add " + code { "dioxus" } + " and " + code { "dioxus-fullstack" } + " as dependencies:" + } + CodeBlock { contents: "
\ncargo add dioxus\ncargo add dioxus-fullstack
\n" } + p { + "Next, set up features for the server ( " + code { "ssr" } + ") and the client ( " + code { "web" } + "):" + } + CodeBlock { contents: "
\n[features]\ndefault = []\nssr = ["dioxus-fullstack/axum"]\nweb = ["dioxus-fullstack/web"]
\n" } + p { "Your dependencies should look roughly like this:" } + CodeBlock { + contents: "
\n[dependencies]\ndioxus = {{ version = "*" }}\ndioxus-fullstack = {{ version = "*" }}\n\n[features]\ndefault = []\nssr = ["dioxus-fullstack/axum"]\nweb = ["dioxus-fullstack/web"]
\n", + } + p { "Now, set up your Axum app to serve the Dioxus app." } + CodeBlock { + contents: "
\n#![allow(non_snake_case, unused)]\nuse dioxus::prelude::*;\nuse dioxus_fullstack::prelude::*;\n\nfn main() {{\n    LaunchBuilder::new(app).launch();\n}}\n\nfn app(cx: Scope) -> Element {{\n    let mut count = use_state(cx, || 0);\n\n    cx.render(rsx! {{\n        h1 {{ "High-Five counter: {{count}}" }}\n        button {{ onclick: move |_| count += 1, "Up high!" }}\n        button {{ onclick: move |_| count -= 1, "Down low!" }}\n    }})\n}}
\n", + name: "server_basic.rs".to_string(), + } + p { "Now, run your app with:" } + CodeBlock { contents: "
\ndx build --features web --release\ncargo run --features ssr --release
\n" } + p { + "Finally, open " + code { "http://localhost:8080" } + " in your browser. You should see a server-side rendered page with a counter." + } + SandBoxFrame { url: "https://codesandbox.io/p/sandbox/dioxus-fullstack-2nwsrz?file=%2Fsrc%2Fmain.rs%3A5%2C1" } + h2 { id: "hot-reload", + Link { + to: BookRoute::GettingStartedFullstack { + section: GettingStartedFullstackSection::HotReload, + }, + class: "header", + "Hot Reload" + } + } + ol { + li { + "Hot reloading allows much faster iteration times inside of rsx calls by interpreting them and streaming the edits." + } + li { + "It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program." + } + } + h3 { id: "usage", + Link { + to: BookRoute::GettingStartedFullstack { + section: GettingStartedFullstackSection::Usage, + }, + class: "header", + "Usage" + } + } + ol { + li { "Run:" } + } + CodeBlock { contents: "
\ndx build --features web\ndx serve --features ssr --hot-reload --platform desktop
\n" } + ol { + li { "Change some code within a rsx or render macro" } + li { "Save and watch the style change without recompiling" } + } + h3 { id: "limitations", + Link { + to: BookRoute::GettingStartedFullstack { + section: GettingStartedFullstackSection::Limitations, + }, + class: "header", + "Limitations" + } + } + ol { + li { + "The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will require a full recompile to capture the expression." + } + li { + "Components, Iterators, and some attributes can contain arbitrary rust code and will trigger a full recompile when changed." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedDesktopSection { + #[default] + Empty, + DesktopOverview, + Examples, + GettingStarted, + PlatformSpecificDependencies, + Windows, + Linux, + Macos, + CreatingAProject, + HotReload, + Setup, + Usage, + Limitations, +} +impl std::str::FromStr for GettingStartedDesktopSection { + type Err = GettingStartedDesktopSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "desktop-overview" => Ok(Self::DesktopOverview), + "examples" => Ok(Self::Examples), + "getting-started" => Ok(Self::GettingStarted), + "platform-specific-dependencies" => Ok(Self::PlatformSpecificDependencies), + "windows" => Ok(Self::Windows), + "linux" => Ok(Self::Linux), + "macos" => Ok(Self::Macos), + "creating-a-project" => Ok(Self::CreatingAProject), + "hot-reload" => Ok(Self::HotReload), + "setup" => Ok(Self::Setup), + "usage" => Ok(Self::Usage), + "limitations" => Ok(Self::Limitations), + _ => Err(GettingStartedDesktopSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedDesktopSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::DesktopOverview => f.write_str("desktop-overview"), + Self::Examples => f.write_str("examples"), + Self::GettingStarted => f.write_str("getting-started"), + Self::PlatformSpecificDependencies => f.write_str("platform-specific-dependencies"), + Self::Windows => f.write_str("windows"), + Self::Linux => f.write_str("linux"), + Self::Macos => f.write_str("macos"), + Self::CreatingAProject => f.write_str("creating-a-project"), + Self::HotReload => f.write_str("hot-reload"), + Self::Setup => f.write_str("setup"), + Self::Usage => f.write_str("usage"), + Self::Limitations => f.write_str("limitations"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedDesktopSectionParseError; +impl std::fmt::Display for GettingStartedDesktopSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedDesktopSectiondesktop-overview, examples, getting-started, platform-specific-dependencies, windows, linux, macos, creating-a-project, hot-reload, setup, usage, limitations", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedDesktopSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedDesktop(section: GettingStartedDesktopSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "desktop-overview", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::DesktopOverview, + }, + class: "header", + "Desktop overview" + } + } + p { + "Build a standalone native desktop app that looks and feels the same across operating systems." + } + p { + "Apps built with Dioxus desktop use the system WebView to render the page. This makes the final size of application much smaller than other WebView renderers (typically under 5MB)." + } + p { + "Although desktop apps are rendered in a WebView, your Rust code runs natively. This means that browser APIs are " + em { "not" } + " available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs " + em { "are" } + " accessible, so streaming, WebSockets, filesystem, etc are all easily accessible though system APIs." + } + p { + "Dioxus desktop is built off " + Link { to: "https://tauri.app/", "Tauri" } + ". Right now there are limited Dioxus abstractions over the menubar, event handling, etc. In some places you may need to leverage Tauri directly – through " + Link { to: "http://github.com/tauri-apps/wry/", "Wry" } + " and " + Link { to: "http://github.com/tauri-apps/tao", "Tao" } + "." + } + blockquote { + p { + "In the future, we plan to move to a custom web renderer-based DOM renderer with WGPU integrations (" + Link { to: "https://github.com/DioxusLabs/blitz", "Blitz" } + ")." + } + } + h2 { id: "examples", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Examples, + }, + class: "header", + "Examples" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/example-projects/blob/master/file-explorer", + "File Explorer" + } + } + li { + Link { to: "https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner", + "WiFi Scanner" + } + } + } + p { + Link { to: "https://github.com/DioxusLabs/example-projects/tree/master/file-explorer", + img { + src: "https://github.com/DioxusLabs/example-projects/raw/master/file-explorer/assets/image.png", + alt: "File Explorer screenshot", + title: "", + } + } + } + p { + "Here's a " + Link { to: "https://github.com/search?q=repo%3ADioxusLabs%2Fdioxus+path%3A%2F%5Eexamples%5C%2F%2F+%22use+dioxus_desktop%22&type=code", + "query" + } + " for the main repo to find examples which use " + code { "dioxus_desktop" } + " (might not be 100% acurrate)." + } + h1 { id: "getting-started", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::GettingStarted, + }, + class: "header", + "Getting started" + } + } + h2 { id: "platform-specific-dependencies", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::PlatformSpecificDependencies, + }, + class: "header", + "Platform-specific dependencies" + } + } + p { + "Dioxus desktop renders through a WebView. Depending on your platform, you might need to install some dependencies." + } + h3 { id: "windows", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Windows, + }, + class: "header", + "Windows" + } + } + p { + "Windows apps depend on WebView2 – a library that should be installed in all modern Windows distributions. If you have Edge installed, then Dioxus will work fine. If you " + em { "don't" } + " have WebView2, " + Link { to: "https://developer.microsoft.com/en-us/microsoft-edge/webview2/", + "then you can install it through Microsoft" + } + ". MS provides 3 options:" + } + ol { + li { + "A tiny \"evergreen\" " + em { "bootstrapper" } + " that fetches an installer from Microsoft's CDN." + } + li { + "A tiny " + em { "installer" } + " that fetches WebView2 from Microsoft's CDN." + } + li { "A statically linked version of WebView2 in your final binary for offline users." } + } + p { "For development purposes, use Option 1." } + h3 { id: "linux", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Linux, + }, + class: "header", + "Linux" + } + } + p { + "WebView Linux apps require WebkitGtk. When distributing, this can be part of your dependency tree in your " + code { ".rpm" } + " or " + code { ".deb" } + ". However, likely, your users will already have WebkitGtk." + } + CodeBlock { contents: "
\nsudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev
\n" } + p { + "When using Debian/bullseye " + code { "libappindicator3-dev" } + " is no longer available but replaced by " + code { "libayatana-appindicator3-dev" } + "." + } + CodeBlock { contents: "
\n# on Debian/bullseye use:\nsudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev
\n" } + p { + "If you run into issues, make sure you have all the basics installed, as outlined in the " + Link { to: "https://beta.tauri.app/guides/prerequisites/", "Tauri docs" } + "." + } + h3 { id: "macos", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Macos, + }, + class: "header", + "MacOS" + } + } + p { + "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)." + } + h2 { id: "creating-a-project", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::CreatingAProject, + }, + class: "header", + "Creating a Project" + } + } + p { "Create a new crate:" } + CodeBlock { contents: "
\ncargo new --bin demo\ncd demo
\n" } + p { + "Add Dioxus and the desktop renderer as dependencies (this will edit your " + code { "Cargo.toml" } + "):" + } + CodeBlock { contents: "
\ncargo add dioxus\ncargo add dioxus-desktop
\n" } + p { + "Edit your " + code { "main.rs" } + ":" + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\n// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types\nuse dioxus::prelude::*;\n\nfn main() {{\n    // launch the dioxus app in a webview\n    dioxus_desktop::launch(App);\n}}\n\n// define a component that renders a div with the text "Hello, world!"\nfn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            "Hello, world!"\n        }}\n    }})\n}}
\n", + name: "hello_world_desktop.rs".to_string(), + } + h2 { id: "hot-reload", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::HotReload, + }, + class: "header", + "Hot Reload" + } + } + ol { + li { + "Hot reloading allows much faster iteration times inside of RSX calls by interpreting them and streaming the edits." + } + li { + "It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program." + } + } + h3 { id: "setup", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Setup, + }, + class: "header", + "Setup" + } + } + p { + "Install " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/cli", + "dioxus-cli" + } + "." + } + h3 { id: "usage", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Usage, + }, + class: "header", + "Usage" + } + } + ol { + li { "Run:" } + } + CodeBlock { contents: "
\ndx serve --hot-reload --platform desktop
\n" } + ol { + li { + "Change some code within a " + code { "rsx" } + " or " + code { "render" } + " macro." + } + li { "Save and watch the style change without recompiling." } + } + h3 { id: "limitations", + Link { + to: BookRoute::GettingStartedDesktop { + section: GettingStartedDesktopSection::Limitations, + }, + class: "header", + "Limitations" + } + } + ol { + li { + "The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the RSX call, it will require a full recompile to capture the expression." + } + li { + "Components, Iterators, and some attributes can contain arbitrary rust code and will trigger a full recompile when changed." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedMobileSection { + #[default] + Empty, + MobileApp, + Support, + GettingSetUp, + SettingUpDependencies, + AndroidDependencies, + IosDependencies, + SettingUpYourProject, + Running, + Ios, + Android, +} +impl std::str::FromStr for GettingStartedMobileSection { + type Err = GettingStartedMobileSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "mobile-app" => Ok(Self::MobileApp), + "support" => Ok(Self::Support), + "getting-set-up" => Ok(Self::GettingSetUp), + "setting-up-dependencies" => Ok(Self::SettingUpDependencies), + "android-dependencies" => Ok(Self::AndroidDependencies), + "ios-dependencies" => Ok(Self::IosDependencies), + "setting-up-your-project" => Ok(Self::SettingUpYourProject), + "running" => Ok(Self::Running), + "ios" => Ok(Self::Ios), + "android" => Ok(Self::Android), + _ => Err(GettingStartedMobileSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedMobileSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::MobileApp => f.write_str("mobile-app"), + Self::Support => f.write_str("support"), + Self::GettingSetUp => f.write_str("getting-set-up"), + Self::SettingUpDependencies => f.write_str("setting-up-dependencies"), + Self::AndroidDependencies => f.write_str("android-dependencies"), + Self::IosDependencies => f.write_str("ios-dependencies"), + Self::SettingUpYourProject => f.write_str("setting-up-your-project"), + Self::Running => f.write_str("running"), + Self::Ios => f.write_str("ios"), + Self::Android => f.write_str("android"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedMobileSectionParseError; +impl std::fmt::Display for GettingStartedMobileSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedMobileSectionmobile-app, support, getting-set-up, setting-up-dependencies, android-dependencies, ios-dependencies, setting-up-your-project, running, ios, android", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedMobileSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedMobile(section: GettingStartedMobileSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "mobile-app", + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::MobileApp, + }, + class: "header", + "Mobile App" + } + } + p { "Build a mobile app with Dioxus!" } + p { + "Example: " + Link { to: "https://github.com/DioxusLabs/example-projects/blob/master/ios_demo", + "Todo App" + } + } + h2 { id: "support", + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::Support, + }, + class: "header", + "Support" + } + } + p { + "Mobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with either the platform's WebView or experimentally with " + Link { to: "https://github.com/DioxusLabs/blitz", "WGPU" } + ". WebView doesn't support animations, transparency, and native widgets." + } + p { + "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." + } + h2 { id: "getting-set-up", + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::GettingSetUp, + }, + class: "header", + "Getting Set up" + } + } + p { + "Getting set up with mobile can be quite challenging. The tooling here isn't great (yet) and might take some hacking around to get things working." + } + h3 { id: "setting-up-dependencies", + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::SettingUpDependencies, + }, + class: "header", + "Setting up dependencies" + } + } + h4 { id: "android-dependencies", + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::AndroidDependencies, + }, + class: "header", + "Android Dependencies" + } + } + p { "First, install the rust Android targets:" } + CodeBlock { contents: "
\nrustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
\n" } + p { + "To develop on Android, you will need to " + Link { to: "https://developer.android.com/studio", "install Android Studio" } + "." + } + p { "Once you have installed Android Studio, you will need to install the Android SDK and NDK:" } + ol { + li { "Create a blank Android project" } + li { + "Select " + code { "Tools > SDK manager" } + } + li { + "Navigate to the " + code { "SDK tools" } + " window:" + } + } + p { + img { + src: asset!( + "/assets/static/android_ndk_install.png", ImageAssetOptions::new().with_webp() + ), + alt: "NDK install window", + title: "", + } + } + p { "Then select:" } + ul { + li { "The SDK" } + li { "The SDK Command line tools" } + li { "The NDK (side by side)" } + li { "CMAKE" } + } + ol { + li { + "Select " + code { "apply" } + " and follow the prompts" + } + } + blockquote { + p { + "More details that could be useful for debugging any errors you encounter are available " + Link { to: "https://developer.android.com/studio/intro/update#sdk-manager", + "in the official android docs" + } + } + } + p { "Next set the Java, Android and NDK home variables:" } + p { "Mac:" } + CodeBlock { contents: "
\nexport JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"\nexport ANDROID_HOME="$HOME/Library/Android/sdk"\nexport NDK_HOME="$ANDROID_HOME/ndk/25.2.9519653"
\n" } + p { "Windows:" } + CodeBlock { + contents: "
\n[System.Environment]::SetEnvironmentVariable("JAVA_HOME", "C:\\Program Files\\Android\\Android Studio\\jbr", "User")\n[System.Environment]::SetEnvironmentVariable("ANDROID_HOME", "$env:LocalAppData\\Android\\Sdk", "User")\n[System.Environment]::SetEnvironmentVariable("NDK_HOME", "$env:LocalAppData\\Android\\Sdk\\ndk\\25.2.9519653", "User")
\n", + } + blockquote { + p { "The NDK version in the paths should match the version you installed in the last step" } + } + h4 { id: "ios-dependencies", + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::IosDependencies, + }, + class: "header", + "IOS Dependencies" + } + } + p { "First, install the rust IOS targets:" } + CodeBlock { contents: "
\nrustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim
\n" } + p { + "To develop on IOS, you will need to " + Link { to: "https://apps.apple.com/us/app/xcode/id497799835", "install XCode" } + "." + } + blockquote { + p { + "Note: On Apple silicon you must run Xcode on rosetta. Goto Application > Right Click Xcode > Get Info > Open in Rosetta." + " " + "If you are using M1, you will have to run " + code { "cargo build --target x86_64-apple-ios" } + " instead of " + code { "cargo apple build" } + " if you want to run in simulator." + } + } + h3 { id: "setting-up-your-project", + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::SettingUpYourProject, + }, + class: "header", + "Setting up your project" + } + } + p { "First, we need to create a rust project:" } + CodeBlock { contents: "
\ncargo new dioxus-mobile-test\ncd dioxus-mobile-test
\n" } + p { + "Next, we can use " + code { "cargo-mobile2" } + " to create a project for mobile:" + } + CodeBlock { contents: "
\ncargo install --git https://github.com/tauri-apps/cargo-mobile2\ncargo mobile init
\n" } + p { + "When you run " + code { "cargo mobile init" } + ", you will be asked a series of questions about your project. One of those questions is what template you should use. Dioxus currently doesn't have a template in Tauri mobile, instead you can use the " + code { "wry" } + " template." + } + blockquote { + p { + "You may also be asked to input your team ID for IOS. You can find your team id " + Link { to: "https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/", + "here" + } + " or create a team id by creating a developer account " + Link { to: "https://developer.apple.com/help/account/get-started/about-your-developer-account", + "here" + } + } + } + p { "Next, we need to modify our dependencies to include dioxus:" } + CodeBlock { contents: "
\ncargo add dioxus\ncargo add dioxus-desktop --no-default-features --features tokio_runtime
\n" } + p { "Finally, we need to add a component to renderer. Modify your main function:" } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\npub fn main() -> Result<()> {{\n    // Right now we're going through dioxus-desktop but we'd like to go through dioxus-mobile\n    // That will seed the index.html with some fixes that prevent the page from scrolling/zooming etc\n    dioxus_desktop::launch_cfg(\n        app,\n        // Note that we have to disable the viewport goofiness of the browser.\n        // Dioxus_mobile should do this for us\n        dioxus_desktop::Config::default().with_custom_index(r#"<!DOCTYPE html>\n        <html>\n          <head>\n            <title>Dioxus app</title>\n            <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />\n            <!-- CUSTOM HEAD -->\n          </head>\n          <body>\n            <div id="main"></div>\n            <!-- MODULE LOADER -->\n          </body>\n        </html>\n       "#.into()),\n    );\n\n    Ok(())\n}}\n\nfn app(cx: Scope) -> Element {{\n    let items = cx.use_hook(|| vec![1, 2, 3]);\n\n    log::debug!("Hello from the app");\n\n    render! {{\n        div {{\n            h1 {{ "Hello, Mobile"}}\n            div {{ margin_left: "auto", margin_right: "auto", width: "200px", padding: "10px", border: "1px solid black",\n                button {{\n                    onclick: move|_| {{\n                        println!("Clicked!");\n                        items.push(items.len());\n                        cx.needs_update_any(ScopeId(0));\n                        println!("Requested update");\n                    }},\n                    "Add item"\n                }}\n                for item in items.iter() {{\n                    div {{ "- {{item}}" }}\n                }}\n            }}\n        }}\n    }}\n}}
\n", + } + h2 { id: "running", + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::Running, + }, + class: "header", + "Running" + } + } + p { + "From there, you'll want to get a build of the crate using whichever platform you're targeting (simulator or actual hardware). For now, we'll just stick with the simulator" + } + h3 { id: "ios", + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::Ios, + }, + class: "header", + "IOS" + } + } + p { "To build your project for IOS, you can run:" } + CodeBlock { contents: "
\ncargo build --target aarch64-apple-ios-sim
\n" } + p { "Next, open XCode (this might take awhile if you've never opened XCode before):" } + CodeBlock { contents: "
\ncargo apple open
\n" } + p { "This will open XCode with this particular project." } + p { + "From there, just click the \"play\" button with the right target and the app should be running!" + } + p { + img { + src: asset!("/assets/static/IOS-dioxus-demo.png", ImageAssetOptions::new().with_webp()), + alt: "ios_demo", + title: "", + } + } + p { + "Note that clicking play doesn't cause a new build, so you'll need to keep rebuilding the app between changes. The tooling here is very young, so please be patient. If you want to contribute to make things easier, please do! We'll be happy to help." + } + h3 { id: "android", + Link { + to: BookRoute::GettingStartedMobile { + section: GettingStartedMobileSection::Android, + }, + class: "header", + "Android" + } + } + p { + "To build your project on Android you can run:" + code { "cargo android build" } + } + p { "Next, open Android studio:" } + CodeBlock { contents: "
\ncargo android open
\n" } + p { "This will open an android studio project for this application." } + p { + "Next we need to create a simulator in Android studio to run our app in. To create a simulator click on the phone icon in the top right of Android studio:" + } + p { + img { + src: asset!( + "/assets/static/android-studio-simulator.png", ImageAssetOptions::new() + .with_webp() + ), + alt: "android studio manage devices", + title: "", + } + } + p { + "Then click the " + code { "create a virtual device" } + " button and follow the prompts:" + } + p { + img { + src: asset!( + "/assets/static/android-studio-devices.png", ImageAssetOptions::new().with_webp() + ), + alt: "android studio devices", + title: "", + } + } + p { "Finally, launch your device by clicking the play button on the device you created:" } + p { + img { + src: asset!( + "/assets/static/android-studio-device.png", ImageAssetOptions::new().with_webp() + ), + alt: "android studio device", + title: "", + } + } + p { "Now you can start your application from your terminal by running:" } + CodeBlock { contents: "
\ncargo android run
\n" } + p { + img { + src: asset!( + "/assets/static/Android-Dioxus-demo.png", ImageAssetOptions::new().with_webp() + ), + alt: "android_demo", + title: "", + } + } + blockquote { + p { "More information is available in the Android docs:" } + ul { + li { "https://developer.android.com/ndk/guides" } + li { "https://developer.android.com/studio/projects/install-ndk" } + li { "https://source.android.com/docs/setup/build/rust/building-rust-modules/overview" } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedTuiSection { + #[default] + Empty, + TerminalUi, + Support, + GettingSetUp, + HotReload, + Setup, + Usage, + Limitations, +} +impl std::str::FromStr for GettingStartedTuiSection { + type Err = GettingStartedTuiSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "terminal-ui" => Ok(Self::TerminalUi), + "support" => Ok(Self::Support), + "getting-set-up" => Ok(Self::GettingSetUp), + "hot-reload" => Ok(Self::HotReload), + "setup" => Ok(Self::Setup), + "usage" => Ok(Self::Usage), + "limitations" => Ok(Self::Limitations), + _ => Err(GettingStartedTuiSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedTuiSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::TerminalUi => f.write_str("terminal-ui"), + Self::Support => f.write_str("support"), + Self::GettingSetUp => f.write_str("getting-set-up"), + Self::HotReload => f.write_str("hot-reload"), + Self::Setup => f.write_str("setup"), + Self::Usage => f.write_str("usage"), + Self::Limitations => f.write_str("limitations"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedTuiSectionParseError; +impl std::fmt::Display for GettingStartedTuiSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedTuiSectionterminal-ui, support, getting-set-up, hot-reload, setup, usage, limitations", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedTuiSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedTui(section: GettingStartedTuiSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "terminal-ui", + Link { + to: BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::TerminalUi, + }, + class: "header", + "Terminal UI" + } + } + p { "You can build a text-based interface that will run in the terminal using Dioxus." } + p { + img { + src: "https://github.com/DioxusLabs/rink/raw/master/examples/example.png", + alt: "Hello World screenshot", + title: "", + } + } + blockquote { + p { + "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." + } + } + h2 { id: "support", + Link { + to: BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::Support, + }, + class: "header", + "Support" + } + } + p { + "TUI support is currently quite experimental. But, if you're willing to venture into the realm of the unknown, this guide will get you started." + } + ul { + li { "It uses flexbox for the layout" } + li { "It only supports a subset of the attributes and elements" } + li { + "Regular widgets will not work in the tui render, but the tui renderer has its own widget components that start with a capital letter. See the " + Link { to: "https://github.com/DioxusLabs/dioxus/blob/master/packages/dioxus-tui/examples/widgets.rs", + "widgets example" + } + } + li { "1px is one character line height. Your regular CSS px does not translate" } + li { "If your app panics, your terminal is wrecked. This will be fixed eventually" } + } + h2 { id: "getting-set-up", + Link { + to: BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::GettingSetUp, + }, + class: "header", + "Getting Set up" + } + } + p { "Start by making a new package and adding Dioxus and the TUI renderer as dependencies." } + CodeBlock { contents: "
\ncargo new --bin demo\ncd demo\ncargo add dioxus\ncargo add dioxus-tui
\n" } + p { + "Then, edit your " + code { "main.rs" } + " with the basic template." + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\n// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types\nuse dioxus::prelude::*;\n\nfn main() {{\n    // launch the app in the terminal\n    dioxus_tui::launch(App);\n}}\n\n// create a component that renders a div with the text "Hello, world!"\nfn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            "Hello, world!"\n        }}\n    }})\n}}
\n", + name: "hello_world_tui.rs".to_string(), + } + p { "To run our app:" } + CodeBlock { contents: "
\ncargo run
\n" } + p { + "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." + } + CodeBlock { + contents: "
\n// todo remove deprecated\n#![allow(non_snake_case, deprecated)]\n\nuse dioxus::events::{{KeyCode, KeyboardEvent}};\nuse dioxus::prelude::*;\nuse dioxus_tui::TuiContext;\n\nfn main() {{\n    dioxus_tui::launch_cfg(\n        App,\n        dioxus_tui::Config::new()\n            .without_ctrl_c_quit()\n            // Some older terminals only support 16 colors or ANSI colors\n            // If your terminal is one of these, change this to BaseColors or ANSI\n            .with_rendering_mode(dioxus_tui::RenderingMode::Rgb),\n    );\n}}\n\nfn App(cx: Scope) -> Element {{\n    let tui_ctx: TuiContext = cx.consume_context().unwrap();\n\n    cx.render(rsx! {{\n        div {{\n            width: "100%",\n            height: "10px",\n            background_color: "red",\n            justify_content: "center",\n            align_items: "center",\n            onkeydown: move |k: KeyboardEvent| if let KeyCode::Q = k.key_code {{\n                tui_ctx.quit();\n            }},\n\n            "Hello world!"\n        }}\n    }})\n}}
\n", + name: "hello_world_tui_no_ctrl_c.rs".to_string(), + } + h2 { id: "hot-reload", + Link { + to: BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::HotReload, + }, + class: "header", + "Hot Reload" + } + } + ol { + li { + "Hot reloading allows much faster iteration times inside of rsx calls by interpreting them and streaming the edits." + } + li { + "It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program." + } + } + h3 { id: "setup", + Link { + to: BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::Setup, + }, + class: "header", + "Setup" + } + } + p { + "Install " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/cli", + "dioxus-cli" + } + "." + } + h3 { id: "usage", + Link { + to: BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::Usage, + }, + class: "header", + "Usage" + } + } + ol { + li { "Run:" } + } + CodeBlock { contents: "
\ndx serve --hot-reload --platform desktop
\n" } + ol { + li { "Change some code within a rsx or render macro" } + li { "Save and watch the style change without recompiling" } + } + h3 { id: "limitations", + Link { + to: BookRoute::GettingStartedTui { + section: GettingStartedTuiSection::Limitations, + }, + class: "header", + "Limitations" + } + } + ol { + li { + "The interpreter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will require a full recompile to capture the expression." + } + li { + "Components, Iterators, and some attributes can contain arbitrary rust code and will trigger a full recompile when changed." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GuideIndexSection { + #[default] + Empty, +} +impl std::str::FromStr for GuideIndexSection { + type Err = GuideIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + _ => Err(GuideIndexSectionParseError), + } + } +} +impl std::fmt::Display for GuideIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + } + } +} +#[derive(Debug)] +pub struct GuideIndexSectionParseError; +impl std::fmt::Display for GuideIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of GuideIndexSection")?; + Ok(()) + } +} +impl std::error::Error for GuideIndexSectionParseError {} +#[component(no_case_check)] +pub fn GuideIndex(section: GuideIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + p { + "In this guide, you'll learn to use Dioxus to build user interfaces that run anywhere. We will recreate the hackernews homepage in Dioxus:" + } + DemoFrame { hackernews_complete::App {} } + p { + "This guide serves a very brief overview of Dioxus. Throughout the guide, there will be links to the " + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Empty, + }, + "reference" + } + " with more details about specific concepts." + } + p { + "First, lets setup our dependencies. In addition to the dependencies you added in the " + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Empty, + }, + "getting started" + } + " guide for your platform, we need to set up a few more dependencies to work with the hacker news API:" + } + CodeBlock { contents: "
\ncargo add chrono --features serde\ncargo add futures\ncargo add reqwest --features json\ncargo add serde --features derive\ncargo add serde_json\ncargo add async_recursion
\n" } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GuideYourFirstComponentSection { + #[default] + Empty, + YourFirstComponent, + DynamicText, + CreatingElements, + SettingAttributes, + CreatingAComponent, + CreatingProps, + CleaningUpOurInterface, +} +impl std::str::FromStr for GuideYourFirstComponentSection { + type Err = GuideYourFirstComponentSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "your-first-component" => Ok(Self::YourFirstComponent), + "dynamic-text" => Ok(Self::DynamicText), + "creating-elements" => Ok(Self::CreatingElements), + "setting-attributes" => Ok(Self::SettingAttributes), + "creating-a-component" => Ok(Self::CreatingAComponent), + "creating-props" => Ok(Self::CreatingProps), + "cleaning-up-our-interface" => Ok(Self::CleaningUpOurInterface), + _ => Err(GuideYourFirstComponentSectionParseError), + } + } +} +impl std::fmt::Display for GuideYourFirstComponentSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::YourFirstComponent => f.write_str("your-first-component"), + Self::DynamicText => f.write_str("dynamic-text"), + Self::CreatingElements => f.write_str("creating-elements"), + Self::SettingAttributes => f.write_str("setting-attributes"), + Self::CreatingAComponent => f.write_str("creating-a-component"), + Self::CreatingProps => f.write_str("creating-props"), + Self::CleaningUpOurInterface => f.write_str("cleaning-up-our-interface"), + } + } +} +#[derive(Debug)] +pub struct GuideYourFirstComponentSectionParseError; +impl std::fmt::Display for GuideYourFirstComponentSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GuideYourFirstComponentSectionyour-first-component, dynamic-text, creating-elements, setting-attributes, creating-a-component, creating-props, cleaning-up-our-interface", + )?; + Ok(()) + } +} +impl std::error::Error for GuideYourFirstComponentSectionParseError {} +#[component(no_case_check)] +pub fn GuideYourFirstComponent( + section: GuideYourFirstComponentSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "your-first-component", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::YourFirstComponent, + }, + class: "header", + "Your First Component" + } + } + p { + "This chapter will teach you how to create a " + Link { + to: BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Empty, + }, + "Component" + } + " that displays a link to a post on hackernews." + } + p { + "First, let's define how to display a post. Dioxus is a " + em { "declarative" } + " framework. This means that instead of telling Dioxus what to do (e.g. to \"create an element\" or \"set the color to red\") we simply " + em { "declare" } + " how we want the UI to look." + } + p { + "To declare what you want your UI to look like, you will need to use the " + code { "rsx" } + " macro. Let's modify the rsx macro in the " + code { "App" } + " function from the " + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Empty, + }, + "getting started" + } + " to show information about our story:" + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    render! {{\n        "story"\n    }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + p { "If you run your application you should see something like this:" } + DemoFrame { hackernews_post::story_v1::App {} } + blockquote { + p { "RSX mirrors HTML. Because of this you will need to know some html to use Dioxus." } + p { "Here are some resources to help get you started learning HTML:" } + ul { + li { + Link { to: "https://developer.mozilla.org/en-US/docs/Learn/HTML", + "MDN HTML Guide" + } + } + li { + Link { to: "https://www.w3schools.com/html/default.asp", "W3 Schools HTML Tutorial" } + } + } + p { + "In addition to HTML, Dioxus uses CSS to style applications. You can either use traditional CSS (what this guide uses) or use a tool like " + Link { to: "https://tailwindcss.com/docs/installation", "tailwind CSS" } + ":" + } + ul { + li { + Link { to: "https://developer.mozilla.org/en-US/docs/Learn/HTML", + "MDN Traditional CSS Guide" + } + } + li { + Link { to: "https://www.w3schools.com/css/default.asp", + "W3 Schools Traditional CSS Tutorial" + } + } + li { + Link { to: "https://tailwindcss.com/docs/installation", "Tailwind tutorial" } + " (used with the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/examples/tailwind", + "Tailwind setup example" + } + ")" + } + } + p { + "If you have existing html code, you can use the " + Link { + to: BookRoute::CliTranslate { + section: CliTranslateSection::Empty, + }, + "translate" + } + " command to convert it to RSX. Or if you prefer to write html, you can use the " + Link { to: "https://github.com/DioxusLabs/dioxus-html-macro", "html! macro" } + " to write html directly in your code." + } + } + h2 { id: "dynamic-text", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::DynamicText, + }, + class: "header", + "Dynamic Text" + } + } + p { + "Let's expand our " + code { "App" } + " component to include the story title, author, score, time posted, and number of comments. We can insert dynamic text in the render macro by inserting variables inside " + code { "{{}}" } + "s (this works similarly to the formatting in the " + Link { to: "https://doc.rust-lang.org/std/macro.println.html", "println!" } + " macro):" + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    let title = "title";\n    let by = "author";\n    let score = 0;\n    let time = chrono::Utc::now();\n    let comments = "comments";\n\n    render! {{\n        "{{title}} by {{by}} ({{score}}) {{time}} {{comments}}"\n    }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + DemoFrame { hackernews_post::story_v2::App {} } + h2 { id: "creating-elements", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::CreatingElements, + }, + class: "header", + "Creating Elements" + } + } + p { + "Next, let's wrap our post description in a " + Link { to: "https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div", + code { "div" } + } + ". You can create HTML elements in Dioxus by putting a " + code { "{{" } + " after the element name and a " + code { "}}" } + " after the last child of the element:" + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    let title = "title";\n    let by = "author";\n    let score = 0;\n    let time = chrono::Utc::now();\n    let comments = "comments";\n\n    render! {{\n        div {{\n            "{{title}} by {{by}} ({{score}}) {{time}} {{comments}}"\n        }}\n    }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + DemoFrame { hackernews_post::story_v3::App {} } + blockquote { + p { + "You can read more about elements in the " + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Empty, + }, + "rsx reference" + } + "." + } + } + h2 { id: "setting-attributes", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::SettingAttributes, + }, + class: "header", + "Setting Attributes" + } + } + p { "Next, let's add some padding around our post listing with an attribute." } + p { + "Attributes (and " + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }, + "listeners" + } + ") modify the behavior or appearance of the element they are attached to. They are specified inside the " + code { "{{}}" } + " brackets before any children, using the " + code { "name: value" } + " syntax. You can format the text in the attribute as you would with a text node:" + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    let title = "title";\n    let by = "author";\n    let score = 0;\n    let time = chrono::Utc::now();\n    let comments = "comments";\n\n    render! {{\n        div {{\n            padding: "0.5rem",\n            position: "relative",\n            "{{title}} by {{by}} ({{score}}) {{time}} {{comments}}"\n        }}\n    }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + DemoFrame { hackernews_post::story_v4::App {} } + blockquote { + p { + "Note: All attributes defined in " + Link { to: "https://docs.rs/dioxus-html/latest/dioxus_html/", + code { "dioxus-html" } + } + " follow the snake_case naming convention. They transform their " + code { "snake_case" } + " names to HTML's " + code { "camelCase" } + " attributes." + } + } + blockquote { + p { + "Note: Styles can be used directly outside of the " + code { "style:" } + " attribute. In the above example, " + code { "color: \"red\"" } + " is turned into " + code { "style=\"color: red\"" } + "." + } + } + blockquote { + p { + "You can read more about elements in the " + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Empty, + }, + "attribute reference" + } + } + } + h2 { id: "creating-a-component", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::CreatingAComponent, + }, + class: "header", + "Creating a Component" + } + } + p { + "Just like you wouldn't want to write a complex program in a single, long, " + code { "main" } + " function, you shouldn't build a complex UI in a single " + code { "App" } + " function. Instead, you should break down the functionality of an app in logical parts called components." + } + p { + "A component is a Rust function, named in UpperCamelCase, that takes a " + code { "Scope" } + " parameter and returns an " + code { "Element" } + " describing the UI it wants to render. In fact, our " + code { "App" } + " function is a component!" + } + p { "Let's pull our story description into a new component:" } + CodeBlock { + contents: "
\nfn StoryListing(cx: Scope) -> Element {{\n    let title = "title";\n    let by = "author";\n    let score = 0;\n    let time = chrono::Utc::now();\n    let comments = "comments";\n\n    render! {{\n        div {{\n            padding: "0.5rem",\n            position: "relative",\n            "{{title}} by {{by}} ({{score}}) {{time}} {{comments}}"\n        }}\n    }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + p { + "We can render our component like we would an element by putting " + code { "{{}}" } + "s after the component name. Let's modify our " + code { "App" } + " component to render our new StoryListing component:" + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    render! {{\n        StoryListing {{\n\n        }}\n    }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + DemoFrame { hackernews_post::story_v5::App {} } + blockquote { + p { + "You can read more about elements in the " + Link { + to: BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Empty, + }, + "component reference" + } + } + } + h2 { id: "creating-props", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::CreatingProps, + }, + class: "header", + "Creating Props" + } + } + p { + "Just like you can pass arguments to a function or attributes to an element, you can pass props to a component that customize its behavior!" + } + p { + "We can define arguments that components can take when they are rendered (called " + code { "Props" } + ") by adding the " + code { "#[component]" } + " macro before our function definition and adding extra function arguments." + } + p { + "Currently, our " + code { "StoryListing" } + " component always renders the same story. We can modify it to accept a story to render as a prop." + } + p { + "We will also define what a post is and include information for how to transform our post to and from a different format using " + Link { to: "https://serde.rs", "serde" } + ". This will be used with the hackernews API in a later chapter:" + } + CodeBlock { + contents: "
\nuse chrono::{{DateTime, Utc}};\nuse serde::{{Deserialize, Serialize}};\n\n// Define the Hackernews types\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryPageData {{\n    #[serde(flatten)]\n    pub item: StoryItem,\n    #[serde(default)]\n    pub comments: Vec<Comment>,\n}}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct Comment {{\n    pub id: i64,\n    /// there will be no by field if the comment was deleted\n    #[serde(default)]\n    pub by: String,\n    #[serde(default)]\n    pub text: String,\n    #[serde(with = "chrono::serde::ts_seconds")]\n    pub time: DateTime<Utc>,\n    #[serde(default)]\n    pub kids: Vec<i64>,\n    #[serde(default)]\n    pub sub_comments: Vec<Comment>,\n    pub r#type: String,\n}}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryItem {{\n    pub id: i64,\n    pub title: String,\n    pub url: Option<String>,\n    pub text: Option<String>,\n    #[serde(default)]\n    pub by: String,\n    #[serde(default)]\n    pub score: i64,\n    #[serde(default)]\n    pub descendants: i64,\n    #[serde(with = "chrono::serde::ts_seconds")]\n    pub time: DateTime<Utc>,\n    #[serde(default)]\n    pub kids: Vec<i64>,\n    pub r#type: String,\n}}\n\n#[component]\nfn StoryListing(cx: Scope, story: StoryItem) -> Element {{\n    let StoryItem {{\n        title,\n        by,\n        score,\n        time,\n        kids,\n        ..\n    }} = story;\n\n    let comments = kids.len();\n\n    render! {{\n        div {{\n            padding: "0.5rem",\n            position: "relative",\n            "{{title}} by {{by}} ({{score}}) {{time}} {{comments}}"\n        }}\n    }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + p { + "Now, let's modify the " + code { "App" } + " component to pass the story to our " + code { "StoryListing" } + " component like we would set an attribute on an element:" + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    render! {{\n        StoryListing {{\n            story: StoryItem {{\n                id: 0,\n                title: "hello hackernews".to_string(),\n                url: None,\n                text: None,\n                by: "Author".to_string(),\n                score: 0,\n                descendants: 0,\n                time: chrono::Utc::now(),\n                kids: vec![],\n                r#type: "".to_string(),\n            }}\n        }}\n    }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + DemoFrame { hackernews_post::story_v6::App {} } + blockquote { + p { + "You can read more about Props in the " + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::Empty, + }, + "Props reference" + } + } + } + h2 { id: "cleaning-up-our-interface", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::CleaningUpOurInterface, + }, + class: "header", + "Cleaning Up Our Interface" + } + } + p { + "Finally, by combining elements and attributes, we can make our post listing much more appealing:" + } + p { "Full code up to this point:" } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\n// Define the Hackernews types\nuse chrono::{{DateTime, Utc}};\nuse serde::{{Deserialize, Serialize}};\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryPageData {{\n    #[serde(flatten)]\n    pub item: StoryItem,\n    #[serde(default)]\n    pub comments: Vec<Comment>,\n}}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct Comment {{\n    pub id: i64,\n    /// there will be no by field if the comment was deleted\n    #[serde(default)]\n    pub by: String,\n    #[serde(default)]\n    pub text: String,\n    #[serde(with = "chrono::serde::ts_seconds")]\n    pub time: DateTime<Utc>,\n    #[serde(default)]\n    pub kids: Vec<i64>,\n    #[serde(default)]\n    pub sub_comments: Vec<Comment>,\n    pub r#type: String,\n}}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryItem {{\n    pub id: i64,\n    pub title: String,\n    pub url: Option<String>,\n    pub text: Option<String>,\n    #[serde(default)]\n    pub by: String,\n    #[serde(default)]\n    pub score: i64,\n    #[serde(default)]\n    pub descendants: i64,\n    #[serde(with = "chrono::serde::ts_seconds")]\n    pub time: DateTime<Utc>,\n    #[serde(default)]\n    pub kids: Vec<i64>,\n    pub r#type: String,\n}}\n\npub fn App(cx: Scope) -> Element {{\n    render! {{\n        StoryListing {{\n            story: StoryItem {{\n                id: 0,\n                title: "hello hackernews".to_string(),\n                url: None,\n                text: None,\n                by: "Author".to_string(),\n                score: 0,\n                descendants: 0,\n                time: Utc::now(),\n                kids: vec![],\n                r#type: "".to_string(),\n            }}\n        }}\n    }}\n}}\n\n#[component]\nfn StoryListing(cx: Scope, story: StoryItem) -> Element {{\n    let StoryItem {{\n        title,\n        url,\n        by,\n        score,\n        time,\n        kids,\n        id,\n        ..\n    }} = story;\n\n    let url = url.as_deref().unwrap_or_default();\n    let hostname = url\n        .trim_start_matches("https://")\n        .trim_start_matches("http://")\n        .trim_start_matches("www.");\n    let score = format!("{{score}} {{}}", if *score == 1 {{ " point" }} else {{ " points" }});\n    let comments = format!(\n        "{{}} {{}}",\n        kids.len(),\n        if kids.len() == 1 {{\n            " comment"\n        }} else {{\n            " comments"\n        }}\n    );\n    let time = time.format("%D %l:%M %p");\n\n    cx.render(rsx! {{\n        div {{\n            padding: "0.5rem",\n            position: "relative",\n            div {{\n                font_size: "1.5rem",\n                a {{\n                    href: url,\n                    "{{title}}"\n                }}\n                a {{\n                    color: "gray",\n                    href: "https://news.ycombinator.com/from?site={{hostname}}",\n                    text_decoration: "none",\n                    " ({{hostname}})"\n                }}\n            }}\n            div {{\n                display: "flex",\n                flex_direction: "row",\n                color: "gray",\n                div {{\n                    "{{score}}"\n                }}\n                div {{\n                    padding_left: "0.5rem",\n                    "by {{by}}"\n                }}\n                div {{\n                    padding_left: "0.5rem",\n                    "{{time}}"\n                }}\n                div {{\n                    padding_left: "0.5rem",\n                    "{{comments}}"\n                }}\n            }}\n        }}\n    }})\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + DemoFrame { hackernews_post::story_final::App {} } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GuideStateSection { + #[default] + Empty, + Interactivity, + CreatingAPreview, + EventHandlers, + State, + TheRulesOfHooks, + NoHooksInConditionals, + NoHooksInClosures, + NoHooksInLoops, +} +impl std::str::FromStr for GuideStateSection { + type Err = GuideStateSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "interactivity" => Ok(Self::Interactivity), + "creating-a-preview" => Ok(Self::CreatingAPreview), + "event-handlers" => Ok(Self::EventHandlers), + "state" => Ok(Self::State), + "the-rules-of-hooks" => Ok(Self::TheRulesOfHooks), + "no-hooks-in-conditionals" => Ok(Self::NoHooksInConditionals), + "no-hooks-in-closures" => Ok(Self::NoHooksInClosures), + "no-hooks-in-loops" => Ok(Self::NoHooksInLoops), + _ => Err(GuideStateSectionParseError), + } + } +} +impl std::fmt::Display for GuideStateSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Interactivity => f.write_str("interactivity"), + Self::CreatingAPreview => f.write_str("creating-a-preview"), + Self::EventHandlers => f.write_str("event-handlers"), + Self::State => f.write_str("state"), + Self::TheRulesOfHooks => f.write_str("the-rules-of-hooks"), + Self::NoHooksInConditionals => f.write_str("no-hooks-in-conditionals"), + Self::NoHooksInClosures => f.write_str("no-hooks-in-closures"), + Self::NoHooksInLoops => f.write_str("no-hooks-in-loops"), + } + } +} +#[derive(Debug)] +pub struct GuideStateSectionParseError; +impl std::fmt::Display for GuideStateSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GuideStateSectioninteractivity, creating-a-preview, event-handlers, state, the-rules-of-hooks, no-hooks-in-conditionals, no-hooks-in-closures, no-hooks-in-loops", + )?; + Ok(()) + } +} +impl std::error::Error for GuideStateSectionParseError {} +#[component(no_case_check)] +pub fn GuideState(section: GuideStateSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "interactivity", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::Interactivity, + }, + class: "header", + "Interactivity" + } + } + p { "In this chapter, we will add a preview for articles you hover over or links you focus on." } + h2 { id: "creating-a-preview", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::CreatingAPreview, + }, + class: "header", + "Creating a Preview" + } + } + p { + "First, let's split our app into a Stories component on the left side of the screen, and a preview component on the right side of the screen:" + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            display: "flex",\n            flex_direction: "row",\n            width: "100%",\n            div {{\n                width: "50%",\n                Stories {{}}\n            }}\n            div {{\n                width: "50%",\n                Preview {{}}\n            }}\n        }}\n    }})\n}}\n\n// New\nfn Stories(cx: Scope) -> Element {{\n    render! {{\n        StoryListing {{\n            story: StoryItem {{\n                id: 0,\n                title: "hello hackernews".to_string(),\n                url: None,\n                text: None,\n                by: "Author".to_string(),\n                score: 0,\n                descendants: 0,\n                time: chrono::Utc::now(),\n                kids: vec![],\n                r#type: "".to_string(),\n            }}\n        }}\n    }}\n}}\n\n// New\n#[derive(Clone, Debug)]\nenum PreviewState {{\n    Unset,\n    Loading,\n    Loaded(StoryPageData),\n}}\n\n// New\nfn Preview(cx: Scope) -> Element {{\n    let preview_state = PreviewState::Unset;\n    match preview_state {{\n        PreviewState::Unset => render! {{\n            "Hover over a story to preview it here"\n        }},\n        PreviewState::Loading => render! {{\n            "Loading..."\n        }},\n        PreviewState::Loaded(story) => {{\n            let title = &story.item.title;\n            let url = story.item.url.as_deref().unwrap_or_default();\n            let text = story.item.text.as_deref().unwrap_or_default();\n            render! {{\n                div {{\n                    padding: "0.5rem",\n                    div {{\n                        font_size: "1.5rem",\n                        a {{\n                            href: "{{url}}",\n                            "{{title}}"\n                        }}\n                    }}\n                    div {{\n                        dangerous_inner_html: "{{text}}",\n                    }}\n                    for comment in &story.comments {{\n                        Comment {{ comment: comment.clone() }}\n                    }}\n                }}\n            }}\n        }}\n    }}\n}}\n\n// NEW\n#[component]\nfn Comment(cx: Scope, comment: Comment) -> Element<'a> {{\n    render! {{\n        div {{\n            padding: "0.5rem",\n            div {{\n                color: "gray",\n                "by {{comment.by}}"\n            }}\n            div {{\n                dangerous_inner_html: "{{comment.text}}"\n            }}\n            for kid in &comment.sub_comments {{\n                Comment {{ comment: kid.clone() }}\n            }}\n        }}\n    }}\n}}
\n", + name: "hackernews_state.rs".to_string(), + } + DemoFrame { hackernews_state::app_v1::App {} } + h2 { id: "event-handlers", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::EventHandlers, + }, + class: "header", + "Event Handlers" + } + } + p { + "Next, we need to detect when the user hovers over a section or focuses a link. We can use an " + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }, + "event listener" + } + " to listen for the hover and focus events." + } + p { + "Event handlers are similar to regular attributes, but their name usually starts with " + code { "on" } + "- and they accept closures as values. The closure will be called whenever the event it listens for is triggered. When an event is triggered, information about the event is passed to the closure though the " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.Event.html", + "Event" + } + " structure." + } + p { + "Let's create a " + Link { to: "https://docs.rs/dioxus/latest/dioxus/events/fn.onmouseenter.html", + code { "onmouseenter" } + } + " event listener in the " + code { "StoryListing" } + " component:" + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n    div {{\n        padding: "0.5rem",\n        position: "relative",\n        onmouseenter: move |_| {{\n            // NEW\n        }},\n        div {{\n            font_size: "1.5rem",\n            a {{\n                href: url,\n                onfocus: move |_event| {{\n                    // NEW\n                }},\n                "{{title}}"\n            }}\n            a {{\n                color: "gray",\n                href: "https://news.ycombinator.com/from?site={{hostname}}",\n                text_decoration: "none",\n                " ({{hostname}})"\n            }}\n        }}\n        div {{\n            display: "flex",\n            flex_direction: "row",\n            color: "gray",\n            div {{\n                "{{score}}"\n            }}\n            div {{\n                padding_left: "0.5rem",\n                "by {{by}}"\n            }}\n            div {{\n                padding_left: "0.5rem",\n                "{{time}}"\n            }}\n            div {{\n                padding_left: "0.5rem",\n                "{{comments}}"\n            }}\n        }}\n    }}\n}})
\n", + name: "hackernews_state.rs".to_string(), + } + blockquote { + p { + "You can read more about Event Handlers in the " + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }, + "Event Handler reference" + } + } + } + h2 { id: "state", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::State, + }, + class: "header", + "State" + } + } + p { + "So far our components have had no state like normal rust functions. To make our application change when we hover over a link we need state to store the currently hovered link in the root of the application." + } + p { + "You can create state in dioxus using hooks. Hooks are Rust functions that take a reference to " + code { "ScopeState" } + " (in a component, you can pass " + code { "cx" } + "), and provide you with functionality and state." + } + p { + "In this case, we will use the " + code { "use_shared_state_provider" } + " and " + code { "use_shared_state" } + " hooks:" + } + ul { + li { + "You can provide a closure to " + code { "use_shared_state_provider" } + " that determines the initial value of the shared state and provides the value to all child components" + } + li { + "You can then use the " + code { "use_shared_state" } + " hook to read and modify that state in the " + code { "Preview" } + " and " + code { "StoryListing" } + " components" + } + li { + "When the value updates, " + code { "use_shared_state" } + " will make the component re-render, and provides you with the new value" + } + } + blockquote { + p { + "Note: You should prefer local state hooks like use_state or use_ref when you only use state in one component. Because we use state in multiple components, we can use a " + Link { + to: BookRoute::ReferenceContext { + section: ReferenceContextSection::Empty, + }, + "global state pattern" + } + } + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    use_shared_state_provider(cx, || PreviewState::Unset);
\n", + name: "hackernews_state.rs".to_string(), + } + CodeBlock { + contents: "
\n#[component]\nfn StoryListing(cx: Scope, story: StoryItem) -> Element {{\n    // New\n    let preview_state = use_shared_state::<PreviewState>(cx).unwrap();\n    let StoryItem {{\n        title,\n        url,\n        by,\n        score,\n        time,\n        kids,\n        ..\n    }} = story;\n\n    let url = url.as_deref().unwrap_or_default();\n    let hostname = url\n        .trim_start_matches("https://")\n        .trim_start_matches("http://")\n        .trim_start_matches("www.");\n    let score = format!("{{score}} {{}}", if *score == 1 {{ " point" }} else {{ " points" }});\n    let comments = format!(\n        "{{}} {{}}",\n        kids.len(),\n        if kids.len() == 1 {{\n            " comment"\n        }} else {{\n            " comments"\n        }}\n    );\n    let time = time.format("%D %l:%M %p");\n\n    cx.render(rsx! {{\n        div {{\n            padding: "0.5rem",\n            position: "relative",\n            onmouseenter: move |_event| {{\n                // NEW\n                // set the preview state to this story\n                *preview_state.write() = PreviewState::Loaded(StoryPageData {{\n                    item: story.clone(),\n                    comments: vec![],\n                }});\n            }},\n            div {{\n                font_size: "1.5rem",\n                a {{\n                    href: url,\n                    onfocus: move |_event| {{\n                        // NEW\n                        // set the preview state to this story\n                        *preview_state.write() = PreviewState::Loaded(StoryPageData {{\n                            item: story.clone(),\n                            comments: vec![],\n                        }});\n                    }},
\n", + name: "hackernews_state.rs".to_string(), + } + CodeBlock { + contents: "
\nfn Preview(cx: Scope) -> Element {{\n    // New\n    let preview_state = use_shared_state::<PreviewState>(cx)?;\n\n    // New\n    match &*preview_state.read() {{
\n", + name: "hackernews_state.rs".to_string(), + } + DemoFrame { hackernews_state::App {} } + blockquote { + p { + "You can read more about Hooks in the " + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::Empty, + }, + "Hooks reference" + } + } + } + h3 { id: "the-rules-of-hooks", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::TheRulesOfHooks, + }, + class: "header", + "The Rules of Hooks" + } + } + p { + "Hooks are a powerful way to manage state in Dioxus, but there are some rules you need to follow to insure they work as expected. Dioxus uses the order you call hooks to differentiate between hooks. Because the order you call hooks matters, you must follow these rules:" + } + ol { + li { "Hooks may be only used in components or other hooks (we'll get to that later)" } + li { + "On every call to the component function" + ol { + li { "The same hooks must be called" } + li { "In the same order" } + } + } + li { + "Hooks name's should start with " + code { "use_" } + " so you don't accidentally confuse them with regular functions" + } + } + p { "These rules mean that there are certain things you can't do with hooks:" } + h4 { id: "no-hooks-in-conditionals", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::NoHooksInConditionals, + }, + class: "header", + "No Hooks in Conditionals" + } + } + CodeBlock { + contents: "
\n// ❌ don't call hooks in conditionals!\n// We must ensure that the same hooks will be called every time\n// But `if` statements only run if the conditional is true!\n// So we might violate rule 2.\nif you_are_happy && you_know_it {{\n    let something = use_state(cx, || "hands");\n    println!("clap your {{something}}")\n}}\n\n// ✅ instead, *always* call use_state\n// You can put other stuff in the conditional though\nlet something = use_state(cx, || "hands");\nif you_are_happy && you_know_it {{\n    println!("clap your {{something}}")\n}}
\n", + name: "hooks_bad.rs".to_string(), + } + h4 { id: "no-hooks-in-closures", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::NoHooksInClosures, + }, + class: "header", + "No Hooks in Closures" + } + } + CodeBlock { + contents: "
\n// ❌ don't call hooks inside closures!\n// We can't guarantee that the closure, if used, will be called in the same order every time\nlet _a = || {{\n    let b = use_state(cx, || 0);\n    b.get()\n}};\n\n// ✅ instead, move hook `b` outside\nlet b = use_state(cx, || 0);\nlet _a = || b.get();
\n", + name: "hooks_bad.rs".to_string(), + } + h4 { id: "no-hooks-in-loops", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::NoHooksInLoops, + }, + class: "header", + "No Hooks in Loops" + } + } + CodeBlock { + contents: "
\n// `names` is a Vec<&str>\n\n// ❌ Do not use hooks in loops!\n// In this case, if the length of the Vec changes, we break rule 2\nfor _name in &names {{\n    let is_selected = use_state(cx, || false);\n    println!("selected: {{is_selected}}");\n}}\n\n// ✅ Instead, use a hashmap with use_ref\nlet selection_map = use_ref(cx, HashMap::<&str, bool>::new);\n\nfor name in &names {{\n    let is_selected = selection_map.read()[name];\n    println!("selected: {{is_selected}}");\n}}
\n", + name: "hooks_bad.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GuideDataFetchingSection { + #[default] + Empty, + FetchingData, + DefiningTheApi, + WorkingWithAsync, + LazilyFetchingData, +} +impl std::str::FromStr for GuideDataFetchingSection { + type Err = GuideDataFetchingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "fetching-data" => Ok(Self::FetchingData), + "defining-the-api" => Ok(Self::DefiningTheApi), + "working-with-async" => Ok(Self::WorkingWithAsync), + "lazily-fetching-data" => Ok(Self::LazilyFetchingData), + _ => Err(GuideDataFetchingSectionParseError), + } + } +} +impl std::fmt::Display for GuideDataFetchingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::FetchingData => f.write_str("fetching-data"), + Self::DefiningTheApi => f.write_str("defining-the-api"), + Self::WorkingWithAsync => f.write_str("working-with-async"), + Self::LazilyFetchingData => f.write_str("lazily-fetching-data"), + } + } +} +#[derive(Debug)] +pub struct GuideDataFetchingSectionParseError; +impl std::fmt::Display for GuideDataFetchingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GuideDataFetchingSectionfetching-data, defining-the-api, working-with-async, lazily-fetching-data", + )?; + Ok(()) + } +} +impl std::error::Error for GuideDataFetchingSectionParseError {} +#[component(no_case_check)] +pub fn GuideDataFetching(section: GuideDataFetchingSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "fetching-data", + Link { + to: BookRoute::GuideDataFetching { + section: GuideDataFetchingSection::FetchingData, + }, + class: "header", + "Fetching Data" + } + } + p { + "In this chapter, we will fetch data from the hacker news API and use it to render the list of top posts in our application." + } + h2 { id: "defining-the-api", + Link { + to: BookRoute::GuideDataFetching { + section: GuideDataFetchingSection::DefiningTheApi, + }, + class: "header", + "Defining the API" + } + } + p { + "First we need to create some utilities to fetch data from the hackernews API using " + Link { to: "https://docs.rs/reqwest/latest/reqwest/index.html", "reqwest" } + ":" + } + CodeBlock { + contents: "
\n// Define the Hackernews API\nuse futures::future::join_all;\n\npub static BASE_API_URL: &str = "https://hacker-news.firebaseio.com/v0/";\npub static ITEM_API: &str = "item/";\npub static USER_API: &str = "user/";\nconst COMMENT_DEPTH: i64 = 2;\n\npub async fn get_story_preview(id: i64) -> Result<StoryItem, reqwest::Error> {{\n    let url = format!("{{}}{{}}{{}}.json", BASE_API_URL, ITEM_API, id);\n    reqwest::get(&url).await?.json().await\n}}\n\npub async fn get_stories(count: usize) -> Result<Vec<StoryItem>, reqwest::Error> {{\n    let url = format!("{{}}topstories.json", BASE_API_URL);\n    let stories_ids = &reqwest::get(&url).await?.json::<Vec<i64>>().await?[..count];\n\n    let story_futures = stories_ids[..usize::min(stories_ids.len(), count)]\n        .iter()\n        .map(|&story_id| get_story_preview(story_id));\n    let stories = join_all(story_futures)\n        .await\n        .into_iter()\n        .filter_map(|story| story.ok())\n        .collect();\n    Ok(stories)\n}}\n\npub async fn get_story(id: i64) -> Result<StoryPageData, reqwest::Error> {{\n    let url = format!("{{}}{{}}{{}}.json", BASE_API_URL, ITEM_API, id);\n    let mut story = reqwest::get(&url).await?.json::<StoryPageData>().await?;\n    let comment_futures = story.item.kids.iter().map(|&id| get_comment(id));\n    let comments = join_all(comment_futures)\n        .await\n        .into_iter()\n        .filter_map(|c| c.ok())\n        .collect();\n\n    story.comments = comments;\n    Ok(story)\n}}\n\n#[async_recursion::async_recursion(?Send)]\npub async fn get_comment_with_depth(id: i64, depth: i64) -> Result<Comment, reqwest::Error> {{\n    let url = format!("{{}}{{}}{{}}.json", BASE_API_URL, ITEM_API, id);\n    let mut comment = reqwest::get(&url).await?.json::<Comment>().await?;\n    if depth > 0 {{\n        let sub_comments_futures = comment\n            .kids\n            .iter()\n            .map(|story_id| get_comment_with_depth(*story_id, depth - 1));\n        comment.sub_comments = join_all(sub_comments_futures)\n            .await\n            .into_iter()\n            .filter_map(|c| c.ok())\n            .collect();\n    }}\n    Ok(comment)\n}}\n\npub async fn get_comment(comment_id: i64) -> Result<Comment, reqwest::Error> {{\n    let comment = get_comment_with_depth(comment_id, COMMENT_DEPTH).await?;\n    Ok(comment)\n}}
\n", + name: "hackernews_async.rs".to_string(), + } + h2 { id: "working-with-async", + Link { + to: BookRoute::GuideDataFetching { + section: GuideDataFetchingSection::WorkingWithAsync, + }, + class: "header", + "Working with Async" + } + } + p { + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_future.html", + code { "use_future" } + } + " is a " + Link { + to: BookRoute::GuideState { + section: GuideStateSection::Empty, + }, + "hook" + } + " that lets you run an async closure, and provides you with its result." + } + p { + "For example, we can make an API request (using " + Link { to: "https://docs.rs/reqwest/latest/reqwest/index.html", "reqwest" } + ") inside " + code { "use_future" } + ":" + } + CodeBlock { + contents: "
\nfn Stories(cx: Scope) -> Element {{\n    // Fetch the top 10 stories on Hackernews\n    let stories = use_future(cx, (), |_| get_stories(10));\n\n    // check if the future is resolved\n    match stories.value() {{\n        Some(Ok(list)) => {{\n            // if it is, render the stories\n            render! {{\n                div {{\n                    // iterate over the stories with a for loop\n                    for story in list {{\n                        // render every story with the StoryListing component\n                        StoryListing {{ story: story.clone() }}\n                    }}\n                }}\n            }}\n        }}\n        Some(Err(err)) => {{\n            // if there was an error, render the error\n            render! {{"An error occurred while fetching stories {{err}}"}}\n        }}\n        None => {{\n            // if the future is not resolved yet, render a loading message\n            render! {{"Loading items"}}\n        }}\n    }}\n}}
\n", + name: "hackernews_async.rs".to_string(), + } + p { + "The code inside " + code { "use_future" } + " will be submitted to the Dioxus scheduler once the component has rendered." + } + p { + "We can use " + code { ".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 " + code { "None" } + ". However, once the future is finished, the component will be re-rendered and the value will now be " + code { "Some(...)" } + ", containing the return value of the closure." + } + p { + "We can then render the result by looping over each of the posts and rendering them with the " + code { "StoryListing" } + " component." + } + DemoFrame { hackernews_async::fetch::App {} } + blockquote { + p { + "You can read more about working with Async in Dioxus in the " + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Empty, + }, + "Async reference" + } + } + } + h2 { id: "lazily-fetching-data", + Link { + to: BookRoute::GuideDataFetching { + section: GuideDataFetchingSection::LazilyFetchingData, + }, + class: "header", + "Lazily Fetching Data" + } + } + p { "Finally, we will lazily fetch the comments on each post as the user hovers over the post." } + p { + "We need to revisit the code that handles hovering over an item. Instead of passing an empty list of comments, we can fetch all the related comments when the user hovers over the item." + } + p { + "We will cache the list of comments with a " + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_ref.html", + "use_ref" + } + " hook. This hook allows you to store some state in a single component. When the user triggers fetching the comments we will check if the response has already been cached before fetching the data from the hackernews API." + } + CodeBlock { + contents: "
\n// New\nasync fn resolve_story(\n    full_story: UseRef<Option<StoryPageData>>,\n    preview_state: UseSharedState<PreviewState>,\n    story_id: i64,\n) {{\n    if let Some(cached) = &*full_story.read() {{\n        *preview_state.write() = PreviewState::Loaded(cached.clone());\n        return;\n    }}\n\n    *preview_state.write() = PreviewState::Loading;\n    if let Ok(story) = get_story(story_id).await {{\n        *preview_state.write() = PreviewState::Loaded(story.clone());\n        *full_story.write() = Some(story);\n    }}\n}}\n\n#[component]\nfn StoryListing(cx: Scope, story: StoryItem) -> Element {{\n    let preview_state = use_shared_state::<PreviewState>(cx).unwrap();\n    let StoryItem {{\n        title,\n        url,\n        by,\n        score,\n        time,\n        kids,\n        id,\n        ..\n    }} = story;\n    // New\n    let full_story = use_ref(cx, || None);\n\n    let url = url.as_deref().unwrap_or_default();\n    let hostname = url\n        .trim_start_matches("https://")\n        .trim_start_matches("http://")\n        .trim_start_matches("www.");\n    let score = format!("{{score}} {{}}", if *score == 1 {{ " point" }} else {{ " points" }});\n    let comments = format!(\n        "{{}} {{}}",\n        kids.len(),\n        if kids.len() == 1 {{\n            " comment"\n        }} else {{\n            " comments"\n        }}\n    );\n    let time = time.format("%D %l:%M %p");\n\n    cx.render(rsx! {{\n        div {{\n            padding: "0.5rem",\n            position: "relative",\n            onmouseenter: move |_event| {{\n                // New\n                // If you return a future from an event handler, it will be run automatically\n                resolve_story(full_story.clone(), preview_state.clone(), *id)\n            }},\n            div {{\n                font_size: "1.5rem",\n                a {{\n                    href: url,\n                    onfocus: move |_event| {{\n                        // New\n                        resolve_story(full_story.clone(), preview_state.clone(), *id)\n                    }},\n                    // ...
\n", + name: "hackernews_async.rs".to_string(), + } + DemoFrame { hackernews_async::App {} } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GuideFullCodeSection { + #[default] + Empty, + Conclusion, + Challenges, + TheFullCodeForTheHackerNewsProject, +} +impl std::str::FromStr for GuideFullCodeSection { + type Err = GuideFullCodeSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "conclusion" => Ok(Self::Conclusion), + "challenges" => Ok(Self::Challenges), + "the-full-code-for-the-hacker-news-project" => { + Ok(Self::TheFullCodeForTheHackerNewsProject) + } + _ => Err(GuideFullCodeSectionParseError), + } + } +} +impl std::fmt::Display for GuideFullCodeSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Conclusion => f.write_str("conclusion"), + Self::Challenges => f.write_str("challenges"), + Self::TheFullCodeForTheHackerNewsProject => { + f.write_str("the-full-code-for-the-hacker-news-project") + } + } + } +} +#[derive(Debug)] +pub struct GuideFullCodeSectionParseError; +impl std::fmt::Display for GuideFullCodeSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GuideFullCodeSectionconclusion, challenges, the-full-code-for-the-hacker-news-project", + )?; + Ok(()) + } +} +impl std::error::Error for GuideFullCodeSectionParseError {} +#[component(no_case_check)] +pub fn GuideFullCode(section: GuideFullCodeSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "conclusion", + Link { + to: BookRoute::GuideFullCode { + section: GuideFullCodeSection::Conclusion, + }, + class: "header", + "Conclusion" + } + } + p { + "Well done! You've completed the Dioxus guide and built a hackernews application in Dioxus." + } + p { + "To continue your journey, you can attempt a challenge listed below, or look at the " + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Empty, + }, + "Dioxus reference" + } + "." + } + h2 { id: "challenges", + Link { + to: BookRoute::GuideFullCode { + section: GuideFullCodeSection::Challenges, + }, + class: "header", + "Challenges" + } + } + ul { + li { "Organize your components into separate files for better maintainability." } + li { "Give your app some style if you haven't already." } + li { + "Integrate your application with the " + Link { + to: BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }, + "Dioxus router" + } + "." + } + } + h2 { id: "the-full-code-for-the-hacker-news-project", + Link { + to: BookRoute::GuideFullCode { + section: GuideFullCodeSection::TheFullCodeForTheHackerNewsProject, + }, + class: "header", + "The full code for the hacker news project" + } + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\n\npub fn App(cx: Scope) -> Element {{\n    use_shared_state_provider(cx, || PreviewState::Unset);\n\n    cx.render(rsx! {{\n        div {{\n            display: "flex",\n            flex_direction: "row",\n            width: "100%",\n            div {{\n                width: "50%",\n                Stories {{}}\n            }}\n            div {{\n                width: "50%",\n                Preview {{}}\n            }}\n        }}\n    }})\n}}\n\nfn Stories(cx: Scope) -> Element {{\n    let story = use_future(cx, (), |_| get_stories(10));\n\n    match story.value() {{\n        Some(Ok(list)) => render! {{\n            div {{\n                for story in list {{\n                    StoryListing {{ story: story.clone() }}\n                }}\n            }}\n        }},\n        Some(Err(err)) => render! {{"An error occurred while fetching stories {{err}}"}},\n        None => render! {{"Loading items"}},\n    }}\n}}\n\nasync fn resolve_story(\n    full_story: UseRef<Option<StoryPageData>>,\n    preview_state: UseSharedState<PreviewState>,\n    story_id: i64,\n) {{\n    if let Some(cached) = &*full_story.read() {{\n        *preview_state.write() = PreviewState::Loaded(cached.clone());\n        return;\n    }}\n\n    *preview_state.write() = PreviewState::Loading;\n    if let Ok(story) = get_story(story_id).await {{\n        *preview_state.write() = PreviewState::Loaded(story.clone());\n        *full_story.write() = Some(story);\n    }}\n}}\n\n#[component]\nfn StoryListing(cx: Scope, story: StoryItem) -> Element {{\n    let preview_state = use_shared_state::<PreviewState>(cx).unwrap();\n    let StoryItem {{\n        title,\n        url,\n        by,\n        score,\n        time,\n        kids,\n        id,\n        ..\n    }} = story;\n    let full_story = use_ref(cx, || None);\n\n    let url = url.as_deref().unwrap_or_default();\n    let hostname = url\n        .trim_start_matches("https://")\n        .trim_start_matches("http://")\n        .trim_start_matches("www.");\n    let score = format!("{{score}} {{}}", if *score == 1 {{ " point" }} else {{ " points" }});\n    let comments = format!(\n        "{{}} {{}}",\n        kids.len(),\n        if kids.len() == 1 {{\n            " comment"\n        }} else {{\n            " comments"\n        }}\n    );\n    let time = time.format("%D %l:%M %p");\n\n    cx.render(rsx! {{\n        div {{\n            padding: "0.5rem",\n            position: "relative",\n            onmouseenter: move |_event| {{\n                resolve_story(full_story.clone(), preview_state.clone(), *id)\n            }},\n            div {{\n                font_size: "1.5rem",\n                a {{\n                    href: url,\n                    onfocus: move |_event| {{\n                        resolve_story(full_story.clone(), preview_state.clone(), *id)\n                    }},\n                    "{{title}}"\n                }}\n                a {{\n                    color: "gray",\n                    href: "https://news.ycombinator.com/from?site={{hostname}}",\n                    text_decoration: "none",\n                    " ({{hostname}})"\n                }}\n            }}\n            div {{\n                display: "flex",\n                flex_direction: "row",\n                color: "gray",\n                div {{\n                    "{{score}}"\n                }}\n                div {{\n                    padding_left: "0.5rem",\n                    "by {{by}}"\n                }}\n                div {{\n                    padding_left: "0.5rem",\n                    "{{time}}"\n                }}\n                div {{\n                    padding_left: "0.5rem",\n                    "{{comments}}"\n                }}\n            }}\n        }}\n    }})\n}}\n\n#[derive(Clone, Debug)]\nenum PreviewState {{\n    Unset,\n    Loading,\n    Loaded(StoryPageData),\n}}\n\nfn Preview(cx: Scope) -> Element {{\n    let preview_state = use_shared_state::<PreviewState>(cx)?;\n\n    match &*preview_state.read() {{\n        PreviewState::Unset => render! {{\n            "Hover over a story to preview it here"\n        }},\n        PreviewState::Loading => render! {{\n            "Loading..."\n        }},\n        PreviewState::Loaded(story) => {{\n            let title = &story.item.title;\n            let url = story.item.url.as_deref().unwrap_or_default();\n            let text = story.item.text.as_deref().unwrap_or_default();\n            render! {{\n                div {{\n                    padding: "0.5rem",\n                    div {{\n                        font_size: "1.5rem",\n                        a {{\n                            href: "{{url}}",\n                            "{{title}}"\n                        }}\n                    }}\n                    div {{\n                        dangerous_inner_html: "{{text}}",\n                    }}\n                    for comment in &story.comments {{\n                        Comment {{ comment: comment.clone() }}\n                    }}\n                }}\n            }}\n        }}\n    }}\n}}\n\n#[component]\nfn Comment(cx: Scope, comment: Comment) -> Element<'a> {{\n    render! {{\n        div {{\n            padding: "0.5rem",\n            div {{\n                color: "gray",\n                "by {{comment.by}}"\n            }}\n            div {{\n                dangerous_inner_html: "{{comment.text}}"\n            }}\n            for kid in &comment.sub_comments {{\n                Comment {{ comment: kid.clone() }}\n            }}\n        }}\n    }}\n}}\n\n// Define the Hackernews API and types\nuse chrono::{{DateTime, Utc}};\nuse futures::future::join_all;\nuse serde::{{Deserialize, Serialize}};\n\npub static BASE_API_URL: &str = "https://hacker-news.firebaseio.com/v0/";\npub static ITEM_API: &str = "item/";\npub static USER_API: &str = "user/";\nconst COMMENT_DEPTH: i64 = 2;\n\npub async fn get_story_preview(id: i64) -> Result<StoryItem, reqwest::Error> {{\n    let url = format!("{{}}{{}}{{}}.json", BASE_API_URL, ITEM_API, id);\n    reqwest::get(&url).await?.json().await\n}}\n\npub async fn get_stories(count: usize) -> Result<Vec<StoryItem>, reqwest::Error> {{\n    let url = format!("{{}}topstories.json", BASE_API_URL);\n    let stories_ids = &reqwest::get(&url).await?.json::<Vec<i64>>().await?[..count];\n\n    let story_futures = stories_ids[..usize::min(stories_ids.len(), count)]\n        .iter()\n        .map(|&story_id| get_story_preview(story_id));\n    Ok(join_all(story_futures)\n        .await\n        .into_iter()\n        .filter_map(|story| story.ok())\n        .collect())\n}}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryPageData {{\n    #[serde(flatten)]\n    pub item: StoryItem,\n    #[serde(default)]\n    pub comments: Vec<Comment>,\n}}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct Comment {{\n    pub id: i64,\n    /// there will be no by field if the comment was deleted\n    #[serde(default)]\n    pub by: String,\n    #[serde(default)]\n    pub text: String,\n    #[serde(with = "chrono::serde::ts_seconds")]\n    pub time: DateTime<Utc>,\n    #[serde(default)]\n    pub kids: Vec<i64>,\n    #[serde(default)]\n    pub sub_comments: Vec<Comment>,\n    pub r#type: String,\n}}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryItem {{\n    pub id: i64,\n    pub title: String,\n    pub url: Option<String>,\n    pub text: Option<String>,\n    #[serde(default)]\n    pub by: String,\n    #[serde(default)]\n    pub score: i64,\n    #[serde(default)]\n    pub descendants: i64,\n    #[serde(with = "chrono::serde::ts_seconds")]\n    pub time: DateTime<Utc>,\n    #[serde(default)]\n    pub kids: Vec<i64>,\n    pub r#type: String,\n}}\n\npub async fn get_story(id: i64) -> Result<StoryPageData, reqwest::Error> {{\n    let url = format!("{{}}{{}}{{}}.json", BASE_API_URL, ITEM_API, id);\n    let mut story = reqwest::get(&url).await?.json::<StoryPageData>().await?;\n    let comment_futures = story.item.kids.iter().map(|&id| get_comment(id));\n    let comments = join_all(comment_futures)\n        .await\n        .into_iter()\n        .filter_map(|c| c.ok())\n        .collect();\n\n    story.comments = comments;\n    Ok(story)\n}}\n\n#[async_recursion::async_recursion(?Send)]\npub async fn get_comment_with_depth(id: i64, depth: i64) -> Result<Comment, reqwest::Error> {{\n    let url = format!("{{}}{{}}{{}}.json", BASE_API_URL, ITEM_API, id);\n    let mut comment = reqwest::get(&url).await?.json::<Comment>().await?;\n    if depth > 0 {{\n        let sub_comments_futures = comment\n            .kids\n            .iter()\n            .map(|story_id| get_comment_with_depth(*story_id, depth - 1));\n        comment.sub_comments = join_all(sub_comments_futures)\n            .await\n            .into_iter()\n            .filter_map(|c| c.ok())\n            .collect();\n    }}\n    Ok(comment)\n}}\n\npub async fn get_comment(comment_id: i64) -> Result<Comment, reqwest::Error> {{\n    get_comment_with_depth(comment_id, COMMENT_DEPTH).await\n}}
\n", + name: "hackernews_complete.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceIndexSection { + #[default] + Empty, + DioxusReference, + Rendering, + State, + Platforms, +} +impl std::str::FromStr for ReferenceIndexSection { + type Err = ReferenceIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "dioxus-reference" => Ok(Self::DioxusReference), + "rendering" => Ok(Self::Rendering), + "state" => Ok(Self::State), + "platforms" => Ok(Self::Platforms), + _ => Err(ReferenceIndexSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::DioxusReference => f.write_str("dioxus-reference"), + Self::Rendering => f.write_str("rendering"), + Self::State => f.write_str("state"), + Self::Platforms => f.write_str("platforms"), + } + } +} +#[derive(Debug)] +pub struct ReferenceIndexSectionParseError; +impl std::fmt::Display for ReferenceIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceIndexSectiondioxus-reference, rendering, state, platforms", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceIndexSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceIndex(section: ReferenceIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "dioxus-reference", + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::DioxusReference, + }, + class: "header", + "Dioxus Reference" + } + } + p { + "This Reference contains more detailed explanations for all concepts covered in the " + Link { + to: BookRoute::GuideIndex { + section: GuideIndexSection::Empty, + }, + "guide" + } + " and more." + } + h2 { id: "rendering", + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Rendering, + }, + class: "header", + "Rendering" + } + } + ul { + li { + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Empty, + }, + code { "RSX" } + } + " Rsx is a HTML-like macro that allows you to declare UI" + } + li { + Link { + to: BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Empty, + }, + code { "Components" } + } + " Components are the building blocks of UI in Dioxus" + } + li { + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::Empty, + }, + code { "Props" } + } + " Props allow you pass information to Components" + } + li { + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }, + code { "Event Listeners" } + } + " Event listeners let you respond to user input" + } + li { + Link { + to: BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::Empty, + }, + code { "User Input" } + } + " How to handle User input in Dioxus" + } + li { + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::Empty, + }, + code { "Dynamic Rendering" } + } + " How to dynamically render data in Dioxus" + } + } + h2 { id: "state", + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::State, + }, + class: "header", + "State" + } + } + ul { + li { + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::Empty, + }, + code { "Hooks" } + } + ": Hooks allow you to create components state" + } + li { + Link { + to: BookRoute::ReferenceContext { + section: ReferenceContextSection::Empty, + }, + code { "Context" } + } + ": Context allows you to create state in a parent and consume it in children" + } + li { + Link { + to: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::Empty, + }, + code { "Routing" } + } + ": The router helps you manage the URL state" + } + li { + Link { + to: BookRoute::ReferenceUseFuture { + section: ReferenceUseFutureSection::Empty, + }, + code { "UseFuture" } + } + ": Use future allows you to create an async task and monitor it's state" + } + li { + Link { + to: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::Empty, + }, + code { "UseCoroutine" } + } + ": Use coroutine helps you manage external state" + } + li { + Link { + to: BookRoute::ReferenceSpawn { + section: ReferenceSpawnSection::Empty, + }, + code { "Spawn" } + } + ": Spawn creates an async task" + } + } + h2 { id: "platforms", + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Platforms, + }, + class: "header", + "Platforms" + } + } + ul { + li { + Link { + to: BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::Empty, + }, + code { "Desktop" } + } + ": Overview of desktop specific APIS" + } + li { + Link { + to: BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::Empty, + }, + code { "Web" } + } + ": Overview of web specific APIS" + } + li { + Link { + to: BookRoute::ReferenceSsr { + section: ReferenceSsrSection::Empty, + }, + code { "SSR" } + } + ": Overview of the SSR renderer" + } + li { + Link { + to: BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::Empty, + }, + code { "Liveview" } + } + ": Overview of liveview specific APIS" + } + li { + Link { + to: BookRoute::ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection::Empty, + }, + code { "Fullstack" } + } + ": Overview of Fullstack specific APIS" + ul { + li { + Link { + to: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::Empty, + }, + code { "Server Functions" } + } + ": Server functions make it easy to communicate between your server and client" + } + li { + Link { + to: BookRoute::ReferenceFullstackExtractors { + section: ReferenceFullstackExtractorsSection::Empty, + }, + code { "Extractors" } + } + ": Extractors allow you to get extra information out of the headers of a request" + } + li { + Link { + to: BookRoute::ReferenceFullstackMiddleware { + section: ReferenceFullstackMiddlewareSection::Empty, + }, + code { "Middleware" } + } + ": Middleware allows you to wrap a server function request or response" + } + li { + Link { + to: BookRoute::ReferenceFullstackAuthentication { + section: ReferenceFullstackAuthenticationSection::Empty, + }, + code { "Authentication" } + } + ": An overview of how to handle authentication with server functions" + } + li { + Link { + to: BookRoute::ReferenceFullstackRouting { + section: ReferenceFullstackRoutingSection::Empty, + }, + code { "Routing" } + } + ": An overview of how to work with the router in the fullstack renderer" + } + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceRsxSection { + #[default] + Empty, + DescribingTheUi, + RsxFeatures, + Attributes, + CustomAttributes, + SpecialAttributes, + TheHtmlEscapeHatch, + BooleanAttributes, + Interpolation, + Children, + Fragments, + Expressions, + Loops, + IfStatements, +} +impl std::str::FromStr for ReferenceRsxSection { + type Err = ReferenceRsxSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "describing-the-ui" => Ok(Self::DescribingTheUi), + "rsx-features" => Ok(Self::RsxFeatures), + "attributes" => Ok(Self::Attributes), + "custom-attributes" => Ok(Self::CustomAttributes), + "special-attributes" => Ok(Self::SpecialAttributes), + "the-html-escape-hatch" => Ok(Self::TheHtmlEscapeHatch), + "boolean-attributes" => Ok(Self::BooleanAttributes), + "interpolation" => Ok(Self::Interpolation), + "children" => Ok(Self::Children), + "fragments" => Ok(Self::Fragments), + "expressions" => Ok(Self::Expressions), + "loops" => Ok(Self::Loops), + "if-statements" => Ok(Self::IfStatements), + _ => Err(ReferenceRsxSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceRsxSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::DescribingTheUi => f.write_str("describing-the-ui"), + Self::RsxFeatures => f.write_str("rsx-features"), + Self::Attributes => f.write_str("attributes"), + Self::CustomAttributes => f.write_str("custom-attributes"), + Self::SpecialAttributes => f.write_str("special-attributes"), + Self::TheHtmlEscapeHatch => f.write_str("the-html-escape-hatch"), + Self::BooleanAttributes => f.write_str("boolean-attributes"), + Self::Interpolation => f.write_str("interpolation"), + Self::Children => f.write_str("children"), + Self::Fragments => f.write_str("fragments"), + Self::Expressions => f.write_str("expressions"), + Self::Loops => f.write_str("loops"), + Self::IfStatements => f.write_str("if-statements"), + } + } +} +#[derive(Debug)] +pub struct ReferenceRsxSectionParseError; +impl std::fmt::Display for ReferenceRsxSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceRsxSectiondescribing-the-ui, rsx-features, attributes, custom-attributes, special-attributes, the-html-escape-hatch, boolean-attributes, interpolation, children, fragments, expressions, loops, if-statements", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceRsxSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceRsx(section: ReferenceRsxSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "describing-the-ui", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::DescribingTheUi, + }, + class: "header", + "Describing the UI" + } + } + p { + "Dioxus is a " + em { "declarative" } + " framework. This means that instead of telling Dioxus what to do (e.g. to \"create an element\" or \"set the color to red\") we simply " + em { "declare" } + " what we want the UI to look like using RSX." + } + p { "You have already seen a simple example of RSX syntax in the \"hello world\" application:" } + CodeBlock { + contents: "
\n// define a component that renders a div with the text "Hello, world!"\nfn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            "Hello, world!"\n        }}\n    }})\n}}
\n", + name: "hello_world_desktop.rs".to_string(), + } + p { + "Here, we use the " + code { "rsx!" } + " macro to " + em { "declare" } + " that we want a " + code { "div" } + " element, containing the text " + code { "\"Hello, world!\"" } + ". Dioxus takes the RSX and constructs a UI from it." + } + h2 { id: "rsx-features", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::RsxFeatures, + }, + class: "header", + "RSX Features" + } + } + p { + "RSX is very similar to HTML in that it describes elements with attributes and children. Here's an empty " + code { "div" } + " element in RSX, as well as the resulting HTML:" + } + CodeBlock { + contents: "
\ncx.render(rsx!(div {{\n    // attributes / listeners\n    // children\n}}))
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { __interactive_04::Empty {} } + h3 { id: "attributes", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Attributes, + }, + class: "header", + "Attributes" + } + } + p { + "Attributes (and " + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }, + "event handlers" + } + ") modify the behavior or appearance of the element they are attached to. They are specified inside the " + code { "{{}}" } + " brackets, using the " + code { "name: value" } + " syntax. You can provide the value as a literal in the RSX:" + } + CodeBlock { + contents: "
\ncx.render(rsx!(img {{\n    src: "https://avatars.githubusercontent.com/u/79236386?s=200&v=4",\n    class: "primary_button",\n    width: "10px"\n}}))
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::Attributes {} } + blockquote { + p { + "Note: All attributes defined in " + code { "dioxus-html" } + " follow the snake_case naming convention. They transform their " + code { "snake_case" } + " names to HTML's " + code { "camelCase" } + " attributes." + } + } + blockquote { + p { + "Note: Styles can be used directly outside of the " + code { "style:" } + " attribute. In the above example, " + code { "color: \"red\"" } + " is turned into " + code { "style=\"color: red\"" } + "." + } + } + h4 { id: "custom-attributes", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::CustomAttributes, + }, + class: "header", + "Custom Attributes" + } + } + p { + "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:" + } + CodeBlock { + contents: "
\ncx.render(rsx!(div {{\n    "style": "width: 20px; height: 20px; background-color: red;",\n}}))
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::CustomAttributes {} } + h3 { id: "special-attributes", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::SpecialAttributes, + }, + class: "header", + "Special Attributes" + } + } + p { "While most attributes are simply passed on to the HTML, some have special behaviors." } + h4 { id: "the-html-escape-hatch", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::TheHtmlEscapeHatch, + }, + class: "header", + "The HTML Escape Hatch" + } + } + p { + "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 " + code { "dangerous_inner_html" } + "." + } + p { + "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 " + Link { to: "https://dioxuslabs.com", "Dioxus homepage" } + ":" + } + CodeBlock { + contents: "
\n// this should come from a trusted source\nlet contents = "live <b>dangerously</b>";\n\ncx.render(rsx! {{\n    div {{\n        dangerous_inner_html: "{{contents}}",\n    }}\n}})
\n", + name: "dangerous_inner_html.rs".to_string(), + } + DemoFrame { dangerous_inner_html::App {} } + blockquote { + p { + "Note! This attribute is called \"dangerous_inner_html\" because it is " + strong { "dangerous" } + " to pass it data you don't trust. If you're not careful, you can easily expose " + Link { to: "https://en.wikipedia.org/wiki/Cross-site_scripting", + "cross-site scripting (XSS)" + } + " attacks to your users." + } + p { + "If you're handling untrusted input, make sure to sanitize your HTML before passing it into " + code { "dangerous_inner_html" } + " – or just pass it to a Text Element to escape any HTML tags." + } + } + h4 { id: "boolean-attributes", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::BooleanAttributes, + }, + class: "header", + "Boolean Attributes" + } + } + p { + "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 " + code { "\"false\"" } + " will cause them to be removed from the target element." + } + p { + "So this RSX wouldn't actually render the " + code { "hidden" } + " attribute:" + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n    div {{\n        hidden: false,\n        "hello"\n    }}\n}})
\n", + name: "boolean_attribute.rs".to_string(), + } + DemoFrame { boolean_attribute::App {} } + p { + "Not all attributes work like this however. " + em { "Only the following attributes" } + " have this behavior:" + } + ul { + li { + code { "allowfullscreen" } + } + li { + code { "allowpaymentrequest" } + } + li { + code { "async" } + } + li { + code { "autofocus" } + } + li { + code { "autoplay" } + } + li { + code { "checked" } + } + li { + code { "controls" } + } + li { + code { "default" } + } + li { + code { "defer" } + } + li { + code { "disabled" } + } + li { + code { "formnovalidate" } + } + li { + code { "hidden" } + } + li { + code { "ismap" } + } + li { + code { "itemscope" } + } + li { + code { "loop" } + } + li { + code { "multiple" } + } + li { + code { "muted" } + } + li { + code { "nomodule" } + } + li { + code { "novalidate" } + } + li { + code { "open" } + } + li { + code { "playsinline" } + } + li { + code { "readonly" } + } + li { + code { "required" } + } + li { + code { "reversed" } + } + li { + code { "selected" } + } + li { + code { "truespeed" } + } + } + p { + "For any other attributes, a value of " + code { "\"false\"" } + " will be sent directly to the DOM." + } + h3 { id: "interpolation", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Interpolation, + }, + class: "header", + "Interpolation" + } + } + p { + "Similarly to how you can " + Link { to: "https://doc.rust-lang.org/rust-by-example/hello/print/fmt.html", + "format" + } + " Rust strings, you can also interpolate in RSX text. Use " + code { "{{variable}}" } + " to Display the value of a variable in a string, or " + code { "{{variable:?}}" } + " to use the Debug representation:" + } + CodeBlock { + contents: "
\nlet coordinates = (42, 0);\nlet country = "es";\ncx.render(rsx!(div {{\n    class: "country-{{country}}",\n    left: "{{coordinates.0:?}}",\n    top: "{{coordinates.1:?}}",\n    // arbitrary expressions are allowed,\n    // as long as they don't contain `{{}}`\n    div {{\n        "{{country.to_uppercase()}}"\n    }},\n    div {{\n        "{{7*6}}"\n    }},\n    // {{}} can be escaped with {{{{}}}}\n    div {{\n        "{{{{}}}}"\n    }},\n}}))
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::Formatting {} } + h3 { id: "children", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Children, + }, + class: "header", + "Children" + } + } + p { + "To add children to an element, put them inside the " + code { "{{}}" } + " brackets after all attributes and listeners in the element. They can be other elements, text, or " + Link { + to: BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Empty, + }, + "components" + } + ". For example, you could have an " + code { "ol" } + " (ordered list) element, containing 3 " + code { "li" } + " (list item) elements, each of which contains some text:" + } + CodeBlock { + contents: "
\ncx.render(rsx!(ol {{\n    li {{"First Item"}}\n    li {{"Second Item"}}\n    li {{"Third Item"}}\n}}))
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::Children {} } + h3 { id: "fragments", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Fragments, + }, + class: "header", + "Fragments" + } + } + p { + "You can render multiple elements at the top level of " + code { "rsx!" } + " and they will be automatically grouped." + } + CodeBlock { + contents: "
\ncx.render(rsx!(\n    p {{"First Item"}},\n    p {{"Second Item"}},\n))
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::ManyRoots {} } + h3 { id: "expressions", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Expressions, + }, + class: "header", + "Expressions" + } + } + p { + "You can include arbitrary Rust expressions as children within RSX that implements " + Link { to: "https://docs.rs/dioxus-core/0.3/dioxus_core/trait.IntoDynNode.html", + "IntoDynNode" + } + ". This is useful for displaying data from an " + Link { to: "https://doc.rust-lang.org/stable/book/ch13-02-iterators.html#processing-a-series-of-items-with-iterators", + "iterator" + } + ":" + } + CodeBlock { + contents: "
\nlet text = "Dioxus";\ncx.render(rsx!(span {{\n    text.to_uppercase(),\n    // create a list of text from 0 to 9\n    (0..10).map(|i| rsx!{{ i.to_string() }})\n}}))
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::Expression {} } + h3 { id: "loops", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Loops, + }, + class: "header", + "Loops" + } + } + p { "In addition to iterators you can also use for loops directly within RSX:" } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n    // use a for loop where the body itself is RSX\n    div {{\n        // create a list of text from 0 to 9\n        for i in 0..3 {{\n            // NOTE: the body of the loop is RSX not a rust statement\n            div {{\n                "{{i}}"\n            }}\n        }}\n    }}\n    // iterator equivalent\n    div {{\n        (0..3).map(|i| rsx!{{ div {{ "{{i}}" }} }})\n    }}\n}})
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::Loops {} } + h3 { id: "if-statements", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::IfStatements, + }, + class: "header", + "If statements" + } + } + p { "You can also use if statements without an else branch within RSX:" } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n    // use if statements without an else\n    if true {{\n        rsx!(div {{ "true" }})\n    }}\n}})
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::IfStatements {} } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceComponentsSection { + #[default] + Empty, + Components, +} +impl std::str::FromStr for ReferenceComponentsSection { + type Err = ReferenceComponentsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "components" => Ok(Self::Components), + _ => Err(ReferenceComponentsSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceComponentsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Components => f.write_str("components"), + } + } +} +#[derive(Debug)] +pub struct ReferenceComponentsSectionParseError; +impl std::fmt::Display for ReferenceComponentsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of ReferenceComponentsSectioncomponents")?; + Ok(()) + } +} +impl std::error::Error for ReferenceComponentsSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceComponents(section: ReferenceComponentsSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "components", + Link { + to: BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Components, + }, + class: "header", + "Components" + } + } + p { + "Just like you wouldn't want to write a complex program in a single, long, " + code { "main" } + " function, you shouldn't build a complex UI in a single " + code { "App" } + " function. Instead, you should break down the functionality of an app in logical parts called components." + } + p { + "A component is a Rust function, named in UpperCamelCase, that takes a " + code { "Scope" } + " parameter and returns an " + code { "Element" } + " describing the UI it wants to render. In fact, our " + code { "App" } + " function is a component!" + } + CodeBlock { + contents: "
\n// define a component that renders a div with the text "Hello, world!"\nfn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            "Hello, world!"\n        }}\n    }})\n}}
\n", + name: "hello_world_desktop.rs".to_string(), + } + blockquote { + p { + "You'll probably want to add " + code { "#![allow(non_snake_case)]" } + " to the top of your crate to avoid warnings about UpperCamelCase component names" + } + } + p { + "A Component is responsible for some rendering task – typically, rendering an isolated part of the user interface. For example, you could have an " + code { "About" } + " component that renders a short description of Dioxus Labs:" + } + CodeBlock { + contents: "
\npub fn About(cx: Scope) -> Element {{\n    cx.render(rsx!(p {{\n        b {{"Dioxus Labs"}}\n        " An Open Source project dedicated to making Rust UI wonderful."\n    }}))\n}}
\n", + name: "components.rs".to_string(), + } + DemoFrame { components::About {} } + p { + "Then, you can render your component in another component, similarly to how elements are rendered:" + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        About {{}},\n        About {{}},\n    }})\n}}
\n", + name: "components.rs".to_string(), + } + DemoFrame { components::App {} } + blockquote { + p { + "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!" + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceComponentPropsSection { + #[default] + Empty, + ComponentProps, + Deriveprops, + OwnedProps, + BorrowedProps, + PropOptions, + OptionalProps, + ExplicitlyRequiredOptionS, + DefaultProps, + AutomaticConversionWithInto, + TheComponentMacro, + ComponentChildren, + TheChildrenField, +} +impl std::str::FromStr for ReferenceComponentPropsSection { + type Err = ReferenceComponentPropsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "component-props" => Ok(Self::ComponentProps), + "deriveprops" => Ok(Self::Deriveprops), + "owned-props" => Ok(Self::OwnedProps), + "borrowed-props" => Ok(Self::BorrowedProps), + "prop-options" => Ok(Self::PropOptions), + "optional-props" => Ok(Self::OptionalProps), + "explicitly-required-option-s" => Ok(Self::ExplicitlyRequiredOptionS), + "default-props" => Ok(Self::DefaultProps), + "automatic-conversion-with-into" => Ok(Self::AutomaticConversionWithInto), + "the-component-macro" => Ok(Self::TheComponentMacro), + "component-children" => Ok(Self::ComponentChildren), + "the-children-field" => Ok(Self::TheChildrenField), + _ => Err(ReferenceComponentPropsSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceComponentPropsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ComponentProps => f.write_str("component-props"), + Self::Deriveprops => f.write_str("deriveprops"), + Self::OwnedProps => f.write_str("owned-props"), + Self::BorrowedProps => f.write_str("borrowed-props"), + Self::PropOptions => f.write_str("prop-options"), + Self::OptionalProps => f.write_str("optional-props"), + Self::ExplicitlyRequiredOptionS => f.write_str("explicitly-required-option-s"), + Self::DefaultProps => f.write_str("default-props"), + Self::AutomaticConversionWithInto => f.write_str("automatic-conversion-with-into"), + Self::TheComponentMacro => f.write_str("the-component-macro"), + Self::ComponentChildren => f.write_str("component-children"), + Self::TheChildrenField => f.write_str("the-children-field"), + } + } +} +#[derive(Debug)] +pub struct ReferenceComponentPropsSectionParseError; +impl std::fmt::Display for ReferenceComponentPropsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceComponentPropsSectioncomponent-props, deriveprops, owned-props, borrowed-props, prop-options, optional-props, explicitly-required-option-s, default-props, automatic-conversion-with-into, the-component-macro, component-children, the-children-field", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceComponentPropsSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceComponentProps( + section: ReferenceComponentPropsSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "component-props", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::ComponentProps, + }, + class: "header", + "Component Props" + } + } + p { + "Just like you can pass arguments to a function or attributes to an element, 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." + } + h2 { id: "deriveprops", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::Deriveprops, + }, + class: "header", + "derive(Props)" + } + } + p { + "Component props are a single struct annotated with " + code { "#[derive(Props)]" } + ". For a component to accept props, the type of its argument must be " + code { "Scope" } + ". Then, you can access the value of the props using " + code { "cx.props" } + "." + } + p { "There are 2 flavors of Props structs:" } + ul { + li { + "Owned props:" + ul { + li { "Don't have an associated lifetime" } + li { + "Implement " + code { "PartialEq" } + ", allow for memoization (if the props don't change, Dioxus won't re-render the component)" + } + } + } + li { + "Borrowed props:" + ul { + li { + Link { to: "https://doc.rust-lang.org/beta/rust-by-example/scope/borrow.html", + "Borrow" + } + " from a parent component" + } + li { "Cannot be memoized due to lifetime constraints" } + } + } + } + h3 { id: "owned-props", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::OwnedProps, + }, + class: "header", + "Owned Props" + } + } + p { "Owned Props are very simple – they don't borrow anything. Example:" } + CodeBlock { + contents: "
\n// Remember: Owned props must implement `PartialEq`!\n#[derive(PartialEq, Props)]\nstruct LikesProps {{\n    score: i32,\n}}\n\nfn Likes(cx: Scope<LikesProps>) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            "This post has ",\n            b {{ "{{cx.props.score}}" }},\n            " likes"\n        }}\n    }})\n}}
\n", + name: "component_owned_props.rs".to_string(), + } + p { + "You can then pass prop values to the component the same way you would pass attributes to an element:" + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        Likes {{\n            score: 42,\n        }},\n    }})\n}}
\n", + name: "component_owned_props.rs".to_string(), + } + DemoFrame { component_owned_props::App {} } + h3 { id: "borrowed-props", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::BorrowedProps, + }, + class: "header", + "Borrowed Props" + } + } + p { + "Owned props work 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 " + code { "App" } + " Component to a " + code { "TitleCard" } + " subcomponent? A naive solution might be to " + Link { to: "https://doc.rust-lang.org/std/clone/trait.Clone.html", + code { ".clone()" } + } + " the String, creating a copy of it for the subcomponent – but this would be inefficient, especially for larger Strings." + } + p { + "Rust allows for something more efficient – borrowing the String as a " + code { "&str" } + " – this is what Borrowed Props are for!" + } + CodeBlock { + contents: "
\n#[derive(Props)]\nstruct TitleCardProps<'a> {{\n    title: &'a str,\n}}\n\nfn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element {{\n    cx.render(rsx! {{\n        h1 {{ "{{cx.props.title}}" }}\n    }})\n}}
\n", + name: "component_borrowed_props.rs".to_string(), + } + p { "We can then use the component like this:" } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    let hello = "Hello Dioxus!";\n\n    cx.render(rsx!(TitleCard {{ title: hello }}))\n}}
\n", + name: "component_borrowed_props.rs".to_string(), + } + DemoFrame { __interactive_04::component_borrowed_props {} } + p { + "Borrowed props can be very useful, but they do not allow for memorization so they will " + em { "always" } + " rerun when the parent scope is rerendered. Because of this Borrowed Props should be reserved for components that are cheap to rerun or places where cloning data is an issue. Using Borrowed Props everywhere will result in large parts of your app rerunning every interaction." + } + h2 { id: "prop-options", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::PropOptions, + }, + class: "header", + "Prop Options" + } + } + p { + "The " + code { "#[derive(Props)]" } + " macro has some features that let you customize the behavior of props." + } + h3 { id: "optional-props", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::OptionalProps, + }, + class: "header", + "Optional Props" + } + } + p { + "You can create optional fields by using the " + code { "Option<…>" } + " type for a field:" + } + CodeBlock { + contents: "
\n#[derive(Props)]\nstruct OptionalProps<'a> {{\n    title: &'a str,\n    subtitle: Option<&'a str>,\n}}\n\nfn Title<'a>(cx: Scope<'a, OptionalProps>) -> Element<'a> {{\n    cx.render(rsx!(h1{{\n        "{{cx.props.title}}: ",\n        cx.props.subtitle.unwrap_or("No subtitle provided"),\n    }}))\n}}
\n", + name: "component_props_options.rs".to_string(), + } + p { "Then, you can choose to either provide them or not:" } + CodeBlock { + contents: "
\nTitle {{\ntitle: "Some Title",\n}},\nTitle {{\ntitle: "Some Title",\nsubtitle: "Some Subtitle",\n}},\n// Providing an Option explicitly won't compile though:\n// Title {{\n//     title: "Some Title",\n//     subtitle: None,\n// }},
\n", + name: "component_props_options.rs".to_string(), + } + h3 { id: "explicitly-required-option-s", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::ExplicitlyRequiredOptionS, + }, + class: "header", + "Explicitly Required Option s" + } + } + p { + "If you want to explicitly require an " + code { "Option" } + ", and not an optional prop, you can annotate it with " + code { "#[props(!optional)]" } + ":" + } + CodeBlock { + contents: "
\n#[derive(Props)]\nstruct ExplicitOptionProps<'a> {{\n    title: &'a str,\n    #[props(!optional)]\n    subtitle: Option<&'a str>,\n}}\n\nfn ExplicitOption<'a>(cx: Scope<'a, ExplicitOptionProps>) -> Element<'a> {{\n    cx.render(rsx!(h1 {{\n        "{{cx.props.title}}: ",\n        cx.props.subtitle.unwrap_or("No subtitle provided"),\n    }}))\n}}
\n", + name: "component_props_options.rs".to_string(), + } + p { + "Then, you have to explicitly pass either " + code { "Some(\"str\")" } + " or " + code { "None" } + ":" + } + CodeBlock { + contents: "
\nExplicitOption {{\ntitle: "Some Title",\nsubtitle: None,\n}},\nExplicitOption {{\ntitle: "Some Title",\nsubtitle: Some("Some Title"),\n}},\n// This won't compile:\n// ExplicitOption {{\n//     title: "Some Title",\n// }},
\n", + name: "component_props_options.rs".to_string(), + } + h3 { id: "default-props", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::DefaultProps, + }, + class: "header", + "Default Props" + } + } + p { + "You can use " + code { "#[props(default = 42)]" } + " to make a field optional and specify its default value:" + } + CodeBlock { + contents: "
\n#[derive(PartialEq, Props)]\nstruct DefaultProps {{\n    // default to 42 when not provided\n    #[props(default = 42)]\n    number: i64,\n}}\n\nfn DefaultComponent(cx: Scope<DefaultProps>) -> Element {{\n    cx.render(rsx!(h1 {{ "{{cx.props.number}}" }}))\n}}
\n", + name: "component_props_options.rs".to_string(), + } + p { "Then, similarly to optional props, you don't have to provide it:" } + CodeBlock { + contents: "
\nDefaultComponent {{\nnumber: 5,\n}},\nDefaultComponent {{}},
\n", + name: "component_props_options.rs".to_string(), + } + h3 { id: "automatic-conversion-with-into", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::AutomaticConversionWithInto, + }, + class: "header", + "Automatic Conversion with .into" + } + } + p { + "It is common for Rust functions to accept " + code { "impl Into" } + " rather than just " + code { "SomeType" } + " to support a wider range of parameters. If you want similar functionality with props, you can use " + code { "#[props(into)]" } + ". For example, you could add it on a " + code { "String" } + " prop – and " + code { "&str" } + " will also be automatically accepted, as it can be converted into " + code { "String" } + ":" + } + CodeBlock { + contents: "
\n#[derive(PartialEq, Props)]\nstruct IntoProps {{\n    #[props(into)]\n    string: String,\n}}\n\nfn IntoComponent(cx: Scope<IntoProps>) -> Element {{\n    cx.render(rsx!(h1 {{ "{{cx.props.string}}" }}))\n}}
\n", + name: "component_props_options.rs".to_string(), + } + p { "Then, you can use it so:" } + CodeBlock { + contents: "
\nIntoComponent {{\nstring: "some &str",\n}},
\n", + name: "component_props_options.rs".to_string(), + } + h2 { id: "the-component-macro", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::TheComponentMacro, + }, + class: "header", + "The component macro" + } + } + p { + "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 " + code { "cx.props.whatever" } + ", we could just use " + code { "whatever" } + " directly!" + } + p { + code { "component" } + " allows you to do just that. Instead of typing the \"full\" version:" + } + CodeBlock { + contents: "
\n#[derive(Props, PartialEq)]\nstruct TitleCardProps {{\n    title: String,\n}}\n\nfn TitleCard(cx: Scope<TitleCardProps>) -> Element {{\n    cx.render(rsx!{{\n        h1 {{ "{{cx.props.title}}" }}\n    }})\n}}
\n", + } + p { + "...you can define a function that accepts props as arguments. Then, just annotate it with " + code { "#[component]" } + ", and the macro will turn it into a regular Component for you:" + } + CodeBlock { contents: "
\n#[component]\nfn TitleCard(cx: Scope, title: String) -> Element {{\n    cx.render(rsx!{{\n        h1 {{ "{{title}}" }}\n    }})\n}}
\n" } + blockquote { + p { + "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." + } + } + h2 { id: "component-children", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::ComponentChildren, + }, + class: "header", + "Component Children" + } + } + p { + "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 " + code { "Element" } + ":" + } + CodeBlock { + contents: "
\n#[derive(Props)]\nstruct ClickableProps<'a> {{\n    href: &'a str,\n    body: Element<'a>,\n}}\n\nfn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {{\n    cx.render(rsx!(\n        a {{\n            href: "{{cx.props.href}}",\n            class: "fancy-button",\n            &cx.props.body\n        }}\n    ))\n}}
\n", + name: "component_element_props.rs".to_string(), + } + p { + "Then, when rendering the component, you can pass in the output of " + code { "cx.render(rsx!(...))" } + ":" + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n    Clickable {{\n        href: "https://www.youtube.com/watch?v=C-M2hs3sXGo",\n        body: cx.render(rsx!("How to " i {{"not"}} " be seen")),\n    }}\n}})
\n", + name: "component_element_props.rs".to_string(), + } + blockquote { + p { + "Note: Since " + code { "Element<'a>" } + " is a borrowed prop, there will be no memoization." + } + } + blockquote { + p { + "Warning: While it may compile, do not include the same " + code { "Element" } + " more than once in the RSX. The resulting behavior is unspecified." + } + } + h3 { id: "the-children-field", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::TheChildrenField, + }, + class: "header", + "The children field" + } + } + p { + "Rather than passing the RSX through a regular prop, you may wish to accept children similarly to how elements can have children. The \"magic\" " + code { "children" } + " prop lets you achieve this:" + } + CodeBlock { + contents: "
\n#[derive(Props)]\nstruct ClickableProps<'a> {{\n    href: &'a str,\n    children: Element<'a>,\n}}\n\nfn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {{\n    cx.render(rsx!(\n        a {{\n            href: "{{cx.props.href}}",\n            class: "fancy-button",\n            &cx.props.children\n        }}\n    ))\n}}
\n", + name: "component_children.rs".to_string(), + } + p { + "This makes using the component much simpler: simply put the RSX inside the " + code { "{{}}" } + " brackets – and there is no need for a " + code { "render" } + " call or another macro!" + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n    Clickable {{\n        href: "https://www.youtube.com/watch?v=C-M2hs3sXGo",\n        "How to " i {{"not"}} " be seen"\n    }}\n}})
\n", + name: "component_children.rs".to_string(), + } + DemoFrame { component_children::App {} } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceEventHandlersSection { + #[default] + Empty, + EventHandlers, + TheEventObject, + EventPropagation, + PreventDefault, + HandlerProps, + CustomData, +} +impl std::str::FromStr for ReferenceEventHandlersSection { + type Err = ReferenceEventHandlersSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "event-handlers" => Ok(Self::EventHandlers), + "the-event-object" => Ok(Self::TheEventObject), + "event-propagation" => Ok(Self::EventPropagation), + "prevent-default" => Ok(Self::PreventDefault), + "handler-props" => Ok(Self::HandlerProps), + "custom-data" => Ok(Self::CustomData), + _ => Err(ReferenceEventHandlersSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceEventHandlersSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::EventHandlers => f.write_str("event-handlers"), + Self::TheEventObject => f.write_str("the-event-object"), + Self::EventPropagation => f.write_str("event-propagation"), + Self::PreventDefault => f.write_str("prevent-default"), + Self::HandlerProps => f.write_str("handler-props"), + Self::CustomData => f.write_str("custom-data"), + } + } +} +#[derive(Debug)] +pub struct ReferenceEventHandlersSectionParseError; +impl std::fmt::Display for ReferenceEventHandlersSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceEventHandlersSectionevent-handlers, the-event-object, event-propagation, prevent-default, handler-props, custom-data", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceEventHandlersSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceEventHandlers(section: ReferenceEventHandlersSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "event-handlers", + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::EventHandlers, + }, + class: "header", + "Event Handlers" + } + } + p { + "Event handlers are used to respond to user actions. For example, an event handler could be triggered when the user clicks, scrolls, moves the mouse, or types a character." + } + p { + "Event handlers are attached to elements. For example, we usually don't care about all the clicks that happen within an app, only those on a particular button." + } + p { + "Event handlers are similar to regular attributes, but their name usually starts with " + code { "on" } + "- and they accept closures as values. The closure will be called whenever the event it listens for is triggered and will be passed that event." + } + p { + "For example, to handle clicks on an element, we can specify an " + code { "onclick" } + " handler:" + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n    button {{\n        onclick: move |event| log::info!("Clicked! Event: {{event:?}}"),\n        "click me!"\n    }}\n}})
\n", + name: "event_click.rs".to_string(), + } + DemoFrame { event_click::App {} } + h2 { id: "the-event-object", + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::TheEventObject, + }, + class: "header", + "The Event object" + } + } + p { + "Event handlers receive an " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/struct.Event.html", + code { "Event" } + } + " object containing information about the event. Different types of events contain different types of data. For example, mouse-related events contain " + Link { to: "https://docs.rs/dioxus/latest/dioxus/events/struct.MouseData.html", + code { "MouseData" } + } + ", which tells you things like where the mouse was clicked and what mouse buttons were used." + } + p { "In the example above, this event data was logged to the terminal:" } + CodeBlock { + contents: "
\nClicked! Event: UiEvent {{ bubble_state: Cell {{ value: true }}, data: MouseData {{ coordinates: Coordinates {{ screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }}, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) }} }}\nClicked! Event: UiEvent {{ bubble_state: Cell {{ value: true }}, data: MouseData {{ coordinates: Coordinates {{ screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }}, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) }} }}
\n", + } + p { + "To learn what the different event types for HTML provide, read the " + Link { to: "https://docs.rs/dioxus-html/latest/dioxus_html/events/index.html", + "events module docs" + } + "." + } + h3 { id: "event-propagation", + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::EventPropagation, + }, + class: "header", + "Event propagation" + } + } + p { + "Some events will trigger first on the element the event originated at upward. For example, a click event on a " + code { "button" } + " inside a " + code { "div" } + " would first trigger the button's event listener and then the div's event listener." + } + blockquote { + p { + "For more information about event propagation see " + Link { to: "https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling", + "the mdn docs on event bubbling" + } + } + } + p { + "If you want to prevent this behavior, you can call " + code { "stop_propagation()" } + " on the event:" + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n    div {{\n        onclick: move |_event| {{}},\n        "outer",\n        button {{\n            onclick: move |event| {{\n                // now, outer won't be triggered\n                event.stop_propagation();\n            }},\n            "inner"\n        }}\n    }}\n}})
\n", + name: "event_nested.rs".to_string(), + } + h2 { id: "prevent-default", + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::PreventDefault, + }, + class: "header", + "Prevent Default" + } + } + p { + "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." + } + p { + "In some instances, might want to avoid this default behavior. For this, you can add the " + code { "prevent_default" } + " attribute with the name of the handler whose default behavior you want to stop. This attribute can be used for multiple handlers using their name separated by spaces:" + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n    a {{\n        href: "https://example.com",\n        prevent_default: "onclick",\n        onclick: |_| log::info!("link clicked"),\n        "example.com",\n    }}\n}})
\n", + name: "event_prevent_default.rs".to_string(), + } + DemoFrame { event_prevent_default::App {} } + p { "Any event handlers will still be called." } + blockquote { + p { + "Normally, in React or JavaScript, you'd call \"preventDefault\" on the event in the callback. Dioxus does " + em { "not" } + " currently support this behavior. Note: this means you cannot conditionally prevent default behavior based on the data in the event." + } + } + h2 { id: "handler-props", + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::HandlerProps, + }, + class: "header", + "Handler Props" + } + } + p { + "Sometimes, you might want to make a component that accepts an event handler. A simple example would be a " + code { "FancyButton" } + " component, which accepts an " + code { "on_click" } + " handler:" + } + CodeBlock { + contents: "
\n#[derive(Props)]\npub struct FancyButtonProps<'a> {{\n    on_click: EventHandler<'a, MouseEvent>,\n}}\n\npub fn FancyButton<'a>(cx: Scope<'a, FancyButtonProps<'a>>) -> Element<'a> {{\n    cx.render(rsx!(button {{\n        class: "fancy-button",\n        onclick: move |evt| cx.props.on_click.call(evt),\n        "click me pls."\n    }}))\n}}
\n", + name: "event_handler_prop.rs".to_string(), + } + p { "Then, you can use it like any other handler:" } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n    FancyButton {{\n        on_click: move |event| println!("Clicked! {{event:?}}")\n    }}\n}})
\n", + name: "event_handler_prop.rs".to_string(), + } + blockquote { + p { + "Note: just like any other attribute, you can name the handlers anything you want! Though they must start with " + code { "on" } + ", for the prop to be automatically turned into an " + code { "EventHandler" } + " at the call site." + } + } + h2 { id: "custom-data", + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::CustomData, + }, + class: "header", + "Custom Data" + } + } + p { + "Event Handlers are generic over any type, so you can pass in any data you want to them, e.g:" + } + CodeBlock { + contents: "
\nstruct ComplexData(i32);\n\n#[derive(Props)]\npub struct CustomFancyButtonProps<'a> {{\n    on_click: EventHandler<'a, ComplexData>,\n}}\n\npub fn CustomFancyButton<'a>(cx: Scope<'a, CustomFancyButtonProps<'a>>) -> Element<'a> {{\n    cx.render(rsx!(button {{\n        class: "fancy-button",\n        onclick: move |_| cx.props.on_click.call(ComplexData(0)),\n        "click me pls."\n    }}))\n}}
\n", + name: "event_handler_prop.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceHooksSection { + #[default] + Empty, + HooksAndComponentState, + UseStateHook, + OutOfDateUsestate, + RulesOfHooks, + NoHooksInConditionals, + NoHooksInClosures, + NoHooksInLoops, + UseRefHook, + AdditionalResources, +} +impl std::str::FromStr for ReferenceHooksSection { + type Err = ReferenceHooksSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "hooks-and-component-state" => Ok(Self::HooksAndComponentState), + "use-state-hook" => Ok(Self::UseStateHook), + "out-of-date-usestate" => Ok(Self::OutOfDateUsestate), + "rules-of-hooks" => Ok(Self::RulesOfHooks), + "no-hooks-in-conditionals" => Ok(Self::NoHooksInConditionals), + "no-hooks-in-closures" => Ok(Self::NoHooksInClosures), + "no-hooks-in-loops" => Ok(Self::NoHooksInLoops), + "use-ref-hook" => Ok(Self::UseRefHook), + "additional-resources" => Ok(Self::AdditionalResources), + _ => Err(ReferenceHooksSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceHooksSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::HooksAndComponentState => f.write_str("hooks-and-component-state"), + Self::UseStateHook => f.write_str("use-state-hook"), + Self::OutOfDateUsestate => f.write_str("out-of-date-usestate"), + Self::RulesOfHooks => f.write_str("rules-of-hooks"), + Self::NoHooksInConditionals => f.write_str("no-hooks-in-conditionals"), + Self::NoHooksInClosures => f.write_str("no-hooks-in-closures"), + Self::NoHooksInLoops => f.write_str("no-hooks-in-loops"), + Self::UseRefHook => f.write_str("use-ref-hook"), + Self::AdditionalResources => f.write_str("additional-resources"), + } + } +} +#[derive(Debug)] +pub struct ReferenceHooksSectionParseError; +impl std::fmt::Display for ReferenceHooksSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceHooksSectionhooks-and-component-state, use-state-hook, out-of-date-usestate, rules-of-hooks, no-hooks-in-conditionals, no-hooks-in-closures, no-hooks-in-loops, use-ref-hook, additional-resources", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceHooksSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceHooks(section: ReferenceHooksSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "hooks-and-component-state", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::HooksAndComponentState, + }, + class: "header", + "Hooks and component state" + } + } + p { + "So far, our components have had no state like a normal Rust function. 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 opened a drop-down and render different things accordingly." + } + p { + "Hooks allow us to create state in our components. Hooks are Rust functions that take a reference to " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html", + code { "ScopeState" } + } + " (in a component, you can pass " + code { "cx" } + "), and provide you with functionality and state." + } + p { + "Dioxus provides many built-in hooks, but if those hooks don't fit your specific use case, you also can " + Link { + to: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::Empty, + }, + "create your own hook" + } + } + h2 { id: "use-state-hook", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::UseStateHook, + }, + class: "header", + "use_state hook" + } + } + p { + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_state.html", + code { "use_state" } + } + " is one of the simplest hooks." + } + ul { + li { + "You provide a closure that determines the initial value: " + code { "let mut count = use_state(cx, || 0);" } + } + li { + code { "use_state" } + " gives you the current value, and a way to update it by setting it to something else" + } + li { + "When the value updates, " + code { "use_state" } + " makes the component re-render (along with any other component that references it), and then provides you with the new value." + } + } + p { + "For example, you might have seen the counter example, in which state (a number) is tracked using the " + code { "use_state" } + " hook:" + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    // count will be initialized to 0 the first time the component is rendered\n    let mut count = use_state(cx, || 0);\n\n    cx.render(rsx!(\n        h1 {{ "High-Five counter: {{count}}" }}\n        button {{\n            onclick: move |_| {{\n                // changing the count will cause the component to re-render\n                count += 1\n            }},\n            "Up high!"\n        }}\n        button {{\n            onclick: move |_| {{\n                // changing the count will cause the component to re-render\n                count -= 1\n            }},\n            "Down low!"\n        }}\n    ))\n}}
\n", + name: "hooks_counter.rs".to_string(), + } + DemoFrame { hooks_counter::App {} } + p { + "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 – describe what you want in terms of the state, and Dioxus will take care of the rest!" + } + blockquote { + p { + code { "use_state" } + " returns your value wrapped in a smart pointer of type " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseState.html", + code { "UseState" } + } + ". This is why you can both read the value and update it, even within an event handler." + } + } + p { "You can use multiple hooks in the same component if you want:" } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    let mut count_a = use_state(cx, || 0);\n    let mut count_b = use_state(cx, || 0);\n\n    cx.render(rsx!(\n        h1 {{ "Counter_a: {{count_a}}" }}\n        button {{ onclick: move |_| count_a += 1, "a++" }}\n        button {{ onclick: move |_| count_a -= 1, "a--" }}\n        h1 {{ "Counter_b: {{count_b}}" }}\n        button {{ onclick: move |_| count_b += 1, "b++" }}\n        button {{ onclick: move |_| count_b -= 1, "b--" }}\n    ))\n}}
\n", + name: "hooks_counter_two_state.rs".to_string(), + } + DemoFrame { hooks_counter_two_state::App {} } + h3 { id: "out-of-date-usestate", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::OutOfDateUsestate, + }, + class: "header", + "Out-of-date UseState" + } + } + p { + "The value " + code { "UseState" } + " dereferences to is only set when the use_state hook is called every render. This means that if you move the state into a future, or you write to the state and then immediately read the state, it may return an out-of-date value." + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    // count will be initialized to 0 the first time the component is rendered\n    let mut count = use_state(cx, || 0);\n    let first_count_read = use_state(cx, || 0);\n\n    // Increase the count\n    if *count == 0 {{\n        count += 1;\n        first_count_read.set(**count);\n    }}\n\n    cx.render(rsx!(\n        // This uses the deref value\n        h1 {{ "High-Five counter: {{first_count_read}}" }}\n    ))\n}}
\n", + name: "hooks_out_of_date.rs".to_string(), + } + DemoFrame { __interactive_04::hooks_out_of_date {} } + p { + "Instead of using deref to get the inner value from UseState, you can use the " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseState.html#method.current", + code { "current" } + } + " function. This function will always return the current value of the state." + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    let mut count = use_state(cx, || 0);\n    let first_count_read = use_state(cx, || 0);\n\n    // Increase the count\n    if *count == 0 {{\n        count += 1;\n        first_count_read.set(*count.current());\n    }}\n\n    cx.render(rsx!(\n        // Use .current to get the real current value\n        h1 {{ "High-Five counter: {{first_count_read}}" }}\n    ))\n}}
\n", + name: "hooks_out_of_date.rs".to_string(), + } + DemoFrame { __interactive_04::hooks_out_of_date_fixed {} } + h2 { id: "rules-of-hooks", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::RulesOfHooks, + }, + class: "header", + "Rules of hooks" + } + } + p { + "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 " + code { "ScopeState" } + ", which is why you must pass " + code { "&cx" } + " to them." + } + p { + "But how can Dioxus differentiate between multiple hooks in the same component? As you saw in the second example, both " + code { "use_state" } + " functions were called with the same parameters, so how come they can return different things when the counters are different?" + } + CodeBlock { + contents: "
\nlet mut count_a = use_state(cx, || 0);\nlet mut count_b = use_state(cx, || 0);
\n", + name: "hooks_counter_two_state.rs".to_string(), + } + p { + "This is only possible because the two hooks are always called in the same order, so Dioxus knows which is which. Because the order you call hooks matters, you must follow certain rules when using hooks:" + } + ol { + li { "Hooks may be only used in components or other hooks (we'll get to that later)." } + li { "On every call to a component function." } + li { + "The same hooks must be called (except in the case of early returns, as explained later in the " + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::Empty, + }, + "Error Handling chapter" + } + ")." + } + li { "In the same order." } + li { + "Hook names should start with " + code { "use_" } + " so you don't accidentally confuse them with regular" + " " + "functions (" + code { "use_state()" } + ", " + code { "use_ref()" } + ", " + code { "use_future()" } + ", etc...)." + } + } + p { "These rules mean that there are certain things you can't do with hooks:" } + h3 { id: "no-hooks-in-conditionals", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::NoHooksInConditionals, + }, + class: "header", + "No hooks in conditionals" + } + } + CodeBlock { + contents: "
\n// ❌ don't call hooks in conditionals!\n// We must ensure that the same hooks will be called every time\n// But `if` statements only run if the conditional is true!\n// So we might violate rule 2.\nif you_are_happy && you_know_it {{\n    let something = use_state(cx, || "hands");\n    println!("clap your {{something}}")\n}}\n\n// ✅ instead, *always* call use_state\n// You can put other stuff in the conditional though\nlet something = use_state(cx, || "hands");\nif you_are_happy && you_know_it {{\n    println!("clap your {{something}}")\n}}
\n", + name: "hooks_bad.rs".to_string(), + } + h3 { id: "no-hooks-in-closures", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::NoHooksInClosures, + }, + class: "header", + "No hooks in closures" + } + } + CodeBlock { + contents: "
\n// ❌ don't call hooks inside closures!\n// We can't guarantee that the closure, if used, will be called in the same order every time\nlet _a = || {{\n    let b = use_state(cx, || 0);\n    b.get()\n}};\n\n// ✅ instead, move hook `b` outside\nlet b = use_state(cx, || 0);\nlet _a = || b.get();
\n", + name: "hooks_bad.rs".to_string(), + } + h3 { id: "no-hooks-in-loops", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::NoHooksInLoops, + }, + class: "header", + "No hooks in loops" + } + } + CodeBlock { + contents: "
\n// `names` is a Vec<&str>\n\n// ❌ Do not use hooks in loops!\n// In this case, if the length of the Vec changes, we break rule 2\nfor _name in &names {{\n    let is_selected = use_state(cx, || false);\n    println!("selected: {{is_selected}}");\n}}\n\n// ✅ Instead, use a hashmap with use_ref\nlet selection_map = use_ref(cx, HashMap::<&str, bool>::new);\n\nfor name in &names {{\n    let is_selected = selection_map.read()[name];\n    println!("selected: {{is_selected}}");\n}}
\n", + name: "hooks_bad.rs".to_string(), + } + h2 { id: "use-ref-hook", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::UseRefHook, + }, + class: "header", + "use_ref hook" + } + } + p { + code { "use_state" } + " is great for tracking simple values. However, in the " + Link { to: "https://docs.rs/dioxus/latest/dioxus/hooks/struct.UseState.html", + code { "UseState" } + " API" + } + ", you may notice that the only way to modify its value is to replace it with something else (e.g., by calling " + code { "set" } + ", or through one of the " + code { "+=" } + ", " + code { "-=" } + " 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 component's state?" + } + p { + "For example, suppose we want to maintain a " + code { "Vec" } + " of values. If we stored it with " + code { "use_state" } + ", the" + " " + "only way to add a new value to the list would be to copy the existing " + code { "Vec" } + ", add our value to it," + " " + "and then replace the existing " + code { "Vec" } + " in the state with it. This is expensive! We want to modify the" + " " + "existing " + code { "Vec" } + " instead." + } + p { + "Thankfully, there is another hook for that, " + code { "use_ref" } + "! It is similar to " + code { "use_state" } + ", but it lets you get a mutable reference to the contained data." + } + p { + "Here's a simple example that keeps a list of events in a " + code { "use_ref" } + ". We can acquire write access to the state with " + code { ".with_mut()" } + ", and then just " + code { ".push" } + " a new value to the state:" + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    let list = use_ref(cx, Vec::new);\n\n    cx.render(rsx!(\n        p {{ "Current list: {{list.read():?}}" }}\n        button {{\n            onclick: move |event| {{\n                list.with_mut(|list| list.push(event));\n            }},\n            "Click me!"\n        }}\n    ))\n}}
\n", + name: "hooks_use_ref.rs".to_string(), + } + DemoFrame { __interactive_04::hooks_use_ref {} } + blockquote { + p { + "The return values of " + code { "use_state" } + " and " + code { "use_ref" } + " (" + "\u{a0}" + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseState.html", + code { "UseState" } + } + " and" + "\u{a0}" + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseRef.html", + code { "UseRef" } + } + ", respectively) are in" + "\u{a0}" + "some ways similar to " + Link { to: "https://doc.rust-lang.org/std/cell/", + code { "Cell" } + } + " and" + "\u{a0}" + Link { to: "https://doc.rust-lang.org/std/cell/struct.RefCell.html", + code { "RefCell" } + } + " – they provide interior" + "\u{a0}" + "mutability. However, these Dioxus wrappers also ensure that the component gets re-rendered" + "\u{a0}" + "whenever you change the state." + } + } + h2 { id: "additional-resources", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::AdditionalResources, + }, + class: "header", + "Additional resources" + } + } + ul { + li { + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/", "dioxus_hooks API docs" } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/hooks", + "dioxus_hooks source code" + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceUserInputSection { + #[default] + Empty, + UserInput, + ControlledInputs, + UncontrolledInputs, + HandlingFiles, +} +impl std::str::FromStr for ReferenceUserInputSection { + type Err = ReferenceUserInputSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "user-input" => Ok(Self::UserInput), + "controlled-inputs" => Ok(Self::ControlledInputs), + "uncontrolled-inputs" => Ok(Self::UncontrolledInputs), + "handling-files" => Ok(Self::HandlingFiles), + _ => Err(ReferenceUserInputSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceUserInputSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::UserInput => f.write_str("user-input"), + Self::ControlledInputs => f.write_str("controlled-inputs"), + Self::UncontrolledInputs => f.write_str("uncontrolled-inputs"), + Self::HandlingFiles => f.write_str("handling-files"), + } + } +} +#[derive(Debug)] +pub struct ReferenceUserInputSectionParseError; +impl std::fmt::Display for ReferenceUserInputSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceUserInputSectionuser-input, controlled-inputs, uncontrolled-inputs, handling-files", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceUserInputSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceUserInput(section: ReferenceUserInputSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "user-input", + Link { + to: BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::UserInput, + }, + class: "header", + "User Input" + } + } + p { + "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." + } + h2 { id: "controlled-inputs", + Link { + to: BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::ControlledInputs, + }, + class: "header", + "Controlled Inputs" + } + } + p { + "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:" + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    let name = use_state(cx, || "bob".to_string());\n\n    cx.render(rsx! {{\n        input {{\n            // we tell the component what to render\n            value: "{{name}}",\n            // and what to do when the value changes\n            oninput: move |evt| name.set(evt.value.clone()),\n        }}\n    }})\n}}
\n", + name: "input_controlled.rs".to_string(), + } + DemoFrame { input_controlled::App {} } + p { "Notice the flexibility – you can:" } + ul { + li { "Also display the same contents in another element, and they will be in sync" } + li { "Transform the input every time it is modified (e.g. to make sure it is upper case)" } + li { "Validate the input every time it changes" } + li { + "Have custom logic happening when the input changes (e.g. network request for autocompletion)" + } + li { + "Programmatically change the value (e.g. a \"randomize\" button that fills the input with nonsense)" + } + } + h2 { id: "uncontrolled-inputs", + Link { + to: BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::UncontrolledInputs, + }, + class: "header", + "Uncontrolled Inputs" + } + } + p { + "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 will be editable anyway (this is built into the browser). This approach can be more performant, but less flexible. For example, it's harder to keep the input in sync with another element." + } + p { + "Since you don't necessarily have the current value of the uncontrolled input in state, you can access it either by listening to " + code { "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. " + code { "oninput" } + " or " + code { "onsubmit" } + "):" + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        form {{\n            onsubmit: move |event| {{\n                log::info!("Submitted! {{event:?}}")\n            }},\n            input {{ name: "name", }},\n            input {{ name: "age", }},\n            input {{ name: "date", }},\n            input {{ r#type: "submit", }},\n        }}\n    }})\n}}
\n", + name: "input_uncontrolled.rs".to_string(), + } + DemoFrame { input_uncontrolled::App {} } + CodeBlock { contents: "
\nSubmitted! UiEvent {{ data: FormData {{ value: "", values: {{"age": "very old", "date": "1966", "name": "Fred"}} }} }}
\n" } + h2 { id: "handling-files", + Link { + to: BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::HandlingFiles, + }, + class: "header", + "Handling files" + } + } + p { + "You can insert a file picker by using an input element of type " + code { "file" } + ". This element supports the " + code { "multiple" } + " attribute, to let you pick more files at the same time. You can select a folder by adding the " + code { "directory" } + " attribute: Dioxus will map this attribute to browser specific attributes, because there is no standardized way to allow a directory to be selected." + } + p { + code { "type" } + " is a Rust keyword, so when specifying the type of the input field, you have to write it as " + code { "r#type:\"file\"" } + "." + } + p { + "Extracting the selected files is a bit different from what you may typically use in Javascript." + } + p { + "The " + code { "FormData" } + " event contains a " + code { "files" } + " field with data about the uploaded files. This field contains a " + code { "FileEngine" } + " struct which lets you fetch the filenames selected by the user. This example saves the filenames of the selected files to a " + code { "Vec" } + ":" + } + CodeBlock { + contents: "
\npub fn App(cx: Scope) -> Element {{\n    let filenames: &UseRef<Vec<String>> = use_ref(cx, Vec::new);\n    cx.render(rsx! {{\n        input {{\n            // tell the input to pick a file\n            r#type:"file",\n            // list the accepted extensions\n            accept: ".txt,.rs",\n            // pick multiple files\n            multiple: true,\n            onchange: move |evt| {{\n                if let Some(file_engine) = &evt.files {{\n                    let files = file_engine.files();\n                    for file_name in files {{\n                        filenames.write().push(file_name);\n                    }}\n                }}\n            }}\n        }}\n    }})\n}}
\n", + name: "input_fileengine.rs".to_string(), + } + p { + "If you're planning to read the file content, you need to do it asynchronously, to keep the rest of the UI interactive. This example event handler loads the content of the selected files in an async closure:" + } + CodeBlock { + contents: "
\nonchange: move |evt| {{\n    // A helper macro to use hooks in async environments\n    to_owned![files_uploaded];\n    async move {{\n        if let Some(file_engine) = &evt.files {{\n            let files = file_engine.files();\n            for file_name in &files {{\n                // Make sure to use async/await when doing heavy I/O operations,\n                // to not freeze the interface in the meantime\n                if let Some(file) = file_engine.read_file_to_string(file_name).await{{\n                    files_uploaded.write().push(file);\n                }}\n            }}\n        }}\n    }}\n}}
\n", + name: "input_fileengine_async.rs".to_string(), + } + p { + "Lastly, this example shows you how to select a folder, by setting the " + code { "directory" } + " attribute to " + code { "true" } + "." + } + CodeBlock { + contents: "
\ninput {{\n    r#type:"file",\n    // Select a folder by setting the directory attribute\n    directory: true,\n    onchange: |evt| {{\n        if let Some(file_engine) = &evt.files {{\n            let files = file_engine.files();\n            for file_name in files {{\n                println!("{{}}", file_name);\n                // Do something with the folder path\n            }}\n        }}\n    }}\n}}
\n", + name: "input_fileengine_folder.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceContextSection { + #[default] + Empty, + SharingState, + LiftingState, + UsingSharedState, +} +impl std::str::FromStr for ReferenceContextSection { + type Err = ReferenceContextSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "sharing-state" => Ok(Self::SharingState), + "lifting-state" => Ok(Self::LiftingState), + "using-shared-state" => Ok(Self::UsingSharedState), + _ => Err(ReferenceContextSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceContextSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::SharingState => f.write_str("sharing-state"), + Self::LiftingState => f.write_str("lifting-state"), + Self::UsingSharedState => f.write_str("using-shared-state"), + } + } +} +#[derive(Debug)] +pub struct ReferenceContextSectionParseError; +impl std::fmt::Display for ReferenceContextSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceContextSectionsharing-state, lifting-state, using-shared-state", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceContextSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceContext(section: ReferenceContextSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "sharing-state", + Link { + to: BookRoute::ReferenceContext { + section: ReferenceContextSection::SharingState, + }, + class: "header", + "Sharing State" + } + } + p { + "Often, multiple components need to access the same state. Depending on your needs, there are several ways to implement this." + } + h2 { id: "lifting-state", + Link { + to: BookRoute::ReferenceContext { + section: ReferenceContextSection::LiftingState, + }, + class: "header", + "Lifting State" + } + } + p { + "One approach to share state between components is to \"lift\" it up to the nearest common ancestor. This means putting the " + code { "use_state" } + " hook in a parent component, and passing the needed values down as props." + } + p { + "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)." + } + blockquote { + p { + "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, maintainable, and performant (this is even more important for larger, complex apps)." + } + } + p { + "We start with a " + code { "Meme" } + " component, responsible for rendering a meme with a given caption:" + } + CodeBlock { + contents: "
\n#[component]\nfn Meme<'a>(cx: Scope<'a>, caption: &'a str) -> Element<'a> {{\n    let container_style = r#"\n        position: relative;\n        width: fit-content;\n    "#;\n\n    let caption_container_style = r#"\n        position: absolute;\n        bottom: 0;\n        left: 0;\n        right: 0;\n        padding: 16px 8px;\n    "#;\n\n    let caption_style = r"\n        font-size: 32px;\n        margin: 0;\n        color: white;\n        text-align: center;\n    ";\n\n    cx.render(rsx!(\n        div {{\n            style: "{{container_style}}",\n            img {{\n                src: "https://i.imgflip.com/2zh47r.jpg",\n                height: "500px",\n            }},\n            div {{\n                style: "{{caption_container_style}}",\n                p {{\n                    style: "{{caption_style}}",\n                    "{{caption}}"\n                }}\n            }}\n        }}\n    ))\n}}
\n", + name: "meme_editor.rs".to_string(), + } + blockquote { + p { + "Note that the " + code { "Meme" } + " component is unaware where the caption is coming from – it could be stored in " + code { "use_state" } + ", " + code { "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!" + } + } + p { + "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 " + code { "Meme" } + " component? Instead, it should accept the current caption as a prop, as well as an event handler to delegate input events to:" + } + CodeBlock { + contents: "
\n#[component]\nfn CaptionEditor<'a>(\n    cx: Scope<'a>,\n    caption: &'a str,\n    on_input: EventHandler<'a, FormEvent>,\n) -> Element<'a> {{\n    let input_style = r"\n        border: none;\n        background: cornflowerblue;\n        padding: 8px 16px;\n        margin: 0;\n        border-radius: 4px;\n        color: white;\n    ";\n\n    cx.render(rsx!(input {{\n        style: "{{input_style}}",\n        value: "{{caption}}",\n        oninput: move |event| on_input.call(event),\n    }}))\n}}
\n", + name: "meme_editor.rs".to_string(), + } + p { + "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." + } + CodeBlock { + contents: "
\nfn MemeEditor(cx: Scope) -> Element {{\n    let container_style = r"\n        display: flex;\n        flex-direction: column;\n        gap: 16px;\n        margin: 0 auto;\n        width: fit-content;\n    ";\n\n    let caption = use_state(cx, || "me waiting for my rust code to compile".to_string());\n\n    cx.render(rsx! {{\n        div {{\n            style: "{{container_style}}",\n            h1 {{ "Meme Editor" }},\n            Meme {{\n                caption: caption,\n            }},\n            CaptionEditor {{\n                caption: caption,\n                on_input: move |event: FormEvent| {{caption.set(event.value.clone());}},\n            }},\n        }}\n    }})\n}}
\n", + name: "meme_editor.rs".to_string(), + } + p { + img { + src: asset!( + "/assets/static/meme_editor_screenshot.png", ImageAssetOptions::new().with_webp() + ), + alt: "Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: \"me waiting for a language feature\"", + title: "", + } + } + h2 { id: "using-shared-state", + Link { + to: BookRoute::ReferenceContext { + section: ReferenceContextSection::UsingSharedState, + }, + class: "header", + "Using Shared State" + } + } + p { + "Sometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient." + } + p { + "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." + } + blockquote { + p { + "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!" + } + } + p { + "Now, we could write another " + code { "use_state" } + " in the top component, and pass " + code { "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!" + } + p { + "Dioxus offers a better solution than this \"prop drilling\" – providing context. The " + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_shared_state_provider.html", + code { "use_shared_state_provider" } + } + " hook is similar to " + code { "use_ref" } + ", but it makes it available through " + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_shared_state.html", + code { "use_shared_state" } + } + " for all children components." + } + p { "First, we have to create a struct for our dark mode configuration:" } + CodeBlock { + contents: "
\nstruct DarkMode(bool);
\n", + name: "meme_editor_dark_mode.rs".to_string(), + } + p { + "Now, in a top-level component (like " + code { "App" } + "), we can provide the " + code { "DarkMode" } + " context to all children components:" + } + CodeBlock { + contents: "
\nuse_shared_state_provider(cx, || DarkMode(false));
\n", + name: "meme_editor_dark_mode.rs".to_string(), + } + p { + "As a result, any child component of " + code { "App" } + " (direct or not), can access the " + code { "DarkMode" } + " context." + } + CodeBlock { + contents: "
\nlet dark_mode_context = use_shared_state::<DarkMode>(cx);
\n", + name: "meme_editor_dark_mode.rs".to_string(), + } + blockquote { + p { + code { "use_shared_state" } + " returns " + code { "Option>" } + " here. If the context has been provided, the value is " + code { "Some(UseSharedState)" } + ", which you can call " + code { ".read" } + " or " + code { ".write" } + " on, similarly to " + code { "UseRef" } + ". Otherwise, the value is " + code { "None" } + "." + } + } + p { + "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):" + } + CodeBlock { + contents: "
\npub fn DarkModeToggle(cx: Scope) -> Element {{\n    let dark_mode = use_shared_state::<DarkMode>(cx).unwrap();\n\n    let style = if dark_mode.read().0 {{\n        "color:white"\n    }} else {{\n        ""\n    }};\n\n    cx.render(rsx!(label {{\n        style: "{{style}}",\n        "Dark Mode",\n        input {{\n            r#type: "checkbox",\n            oninput: move |event| {{\n                let is_enabled = event.value == "true";\n                dark_mode.write().0 = is_enabled;\n            }},\n        }},\n    }}))\n}}
\n", + name: "meme_editor_dark_mode.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceDynamicRenderingSection { + #[default] + Empty, + DynamicRendering, + ConditionalRendering, + ImprovingTheIfElseExample, + InspectingElementProps, + RenderingNothing, + RenderingLists, + InlineForLoops, + TheKeyAttribute, +} +impl std::str::FromStr for ReferenceDynamicRenderingSection { + type Err = ReferenceDynamicRenderingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "dynamic-rendering" => Ok(Self::DynamicRendering), + "conditional-rendering" => Ok(Self::ConditionalRendering), + "improving-the-if-else-example" => Ok(Self::ImprovingTheIfElseExample), + "inspecting-element-props" => Ok(Self::InspectingElementProps), + "rendering-nothing" => Ok(Self::RenderingNothing), + "rendering-lists" => Ok(Self::RenderingLists), + "inline-for-loops" => Ok(Self::InlineForLoops), + "the-key-attribute" => Ok(Self::TheKeyAttribute), + _ => Err(ReferenceDynamicRenderingSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceDynamicRenderingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::DynamicRendering => f.write_str("dynamic-rendering"), + Self::ConditionalRendering => f.write_str("conditional-rendering"), + Self::ImprovingTheIfElseExample => f.write_str("improving-the-if-else-example"), + Self::InspectingElementProps => f.write_str("inspecting-element-props"), + Self::RenderingNothing => f.write_str("rendering-nothing"), + Self::RenderingLists => f.write_str("rendering-lists"), + Self::InlineForLoops => f.write_str("inline-for-loops"), + Self::TheKeyAttribute => f.write_str("the-key-attribute"), + } + } +} +#[derive(Debug)] +pub struct ReferenceDynamicRenderingSectionParseError; +impl std::fmt::Display for ReferenceDynamicRenderingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceDynamicRenderingSectiondynamic-rendering, conditional-rendering, improving-the-if-else-example, inspecting-element-props, rendering-nothing, rendering-lists, inline-for-loops, the-key-attribute", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceDynamicRenderingSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceDynamicRendering( + section: ReferenceDynamicRenderingSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "dynamic-rendering", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::DynamicRendering, + }, + class: "header", + "Dynamic Rendering" + } + } + p { + "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!" + } + h2 { id: "conditional-rendering", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::ConditionalRendering, + }, + class: "header", + "Conditional Rendering" + } + } + p { + "To render different elements based on a condition, you could use an " + code { "if-else" } + " statement:" + } + CodeBlock { + contents: "
\nif *is_logged_in {{\n    cx.render(rsx! {{\n        "Welcome!"\n        button {{\n            onclick: move |_| on_log_out.call(()),\n            "Log Out",\n        }}\n    }})\n}} else {{\n    cx.render(rsx! {{\n        button {{\n            onclick: move |_| on_log_in.call(()),\n            "Log In",\n        }}\n    }})\n}}
\n", + name: "conditional_rendering.rs".to_string(), + } + DemoFrame { conditional_rendering::App {} } + blockquote { + p { + "You could also use " + code { "match" } + " statements, or any Rust function to conditionally render different things." + } + } + h3 { id: "improving-the-if-else-example", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::ImprovingTheIfElseExample, + }, + class: "header", + "Improving the if-else Example" + } + } + p { + "You may have noticed some repeated code in the " + code { "if-else" } + " example above. Repeating code like this is both bad for maintainability and performance. Dioxus will skip diffing static elements like the button, but when switching between multiple " + code { "rsx" } + " calls it cannot perform this optimization. For this example either approach is fine, but for components with large parts that are reused between conditionals, it can be more of an issue." + } + p { + "We can improve this example by splitting up the dynamic parts and inserting them where they are needed." + } + CodeBlock { + contents: "
\ncx.render(rsx! {{\n    // We only render the welcome message if we are logged in\n    // You can use if statements in the middle of a render block to conditionally render elements\n    if *is_logged_in {{\n        // Notice the body of this if statement is rsx code, not an expression\n        "Welcome!"\n    }}\n    button {{\n        // depending on the value of `is_logged_in`, we will call a different event handler\n        onclick: move |_| if *is_logged_in {{\n            on_log_in.call(())\n        }}\n        else{{\n            on_log_out.call(())\n        }},\n        if *is_logged_in {{\n            // if we are logged in, the button should say "Log Out"\n            "Log Out"\n        }} else {{\n            // if we are not logged in, the button should say "Log In"\n            "Log In"\n        }}\n    }}\n}})
\n", + name: "conditional_rendering.rs".to_string(), + } + DemoFrame { conditional_rendering::LogInImprovedApp {} } + h3 { id: "inspecting-element-props", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::InspectingElementProps, + }, + class: "header", + "Inspecting Element props" + } + } + p { + "Since " + code { "Element" } + " is a " + code { "Option" } + ", components accepting " + code { "Element" } + " as a prop can inspect its contents, and render different things based on that. Example:" + } + CodeBlock { + contents: "
\nfn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {{\n    match cx.props.children {{\n        Some(VNode {{ dynamic_nodes, .. }}) => {{\n            todo!("render some stuff")\n        }}\n        _ => {{\n            todo!("render some other stuff")\n        }}\n    }}\n}}
\n", + name: "component_children_inspect.rs".to_string(), + } + p { + "You can't mutate the " + code { "Element" } + ", but if you need a modified version of it, you can construct a new one based on its attributes/children/etc." + } + h2 { id: "rendering-nothing", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::RenderingNothing, + }, + class: "header", + "Rendering Nothing" + } + } + p { + "To render nothing, you can return " + code { "None" } + " from a component. This is useful if you want to conditionally hide something:" + } + CodeBlock { + contents: "
\nif *is_logged_in {{\n    return None;\n}}\n\ncx.render(rsx! {{\n    a {{\n        "You must be logged in to comment"\n    }}\n}})
\n", + name: "conditional_rendering.rs".to_string(), + } + DemoFrame { conditional_rendering::LogInWarningApp {} } + p { + "This works because the " + code { "Element" } + " type is just an alias for " + code { "Option" } + } + blockquote { + p { + "Again, you may use a different method to conditionally return " + code { "None" } + ". For example the boolean's " + Link { to: "https://doc.rust-lang.org/std/primitive.bool.html#method.then", + code { "then()" } + } + " function could be used." + } + } + h2 { id: "rendering-lists", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::RenderingLists, + }, + class: "header", + "Rendering Lists" + } + } + p { + "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." + } + p { + "For this, Dioxus accepts iterators that produce " + code { "Element" } + "s. So we need to:" + } + ul { + li { + "Get an iterator over all of our items (e.g., if you have a " + code { "Vec" } + " of comments, iterate over it with " + code { "iter()" } + ")" + } + li { + code { ".map" } + " the iterator to convert each item into a " + code { "LazyNode" } + " using " + code { "rsx!(...)" } + ul { + li { + "Add a unique " + code { "key" } + " attribute to each iterator item" + } + } + } + li { "Include this iterator in the final RSX (or use it inline)" } + } + p { + "Example: suppose you have a list of comments you want to render. Then, you can render them like this:" + } + CodeBlock { + contents: "
\nlet comment_field = use_state(cx, String::new);\nlet mut next_id = use_state(cx, || 0);\nlet comments = use_ref(cx, Vec::<Comment>::new);\n\nlet comments_lock = comments.read();\nlet comments_rendered = comments_lock.iter().map(|comment| {{\n    rsx!(CommentComponent {{\n        key: "{{comment.id}}",\n        comment: comment.clone(),\n    }})\n}});\n\ncx.render(rsx!(\n    form {{\n        onsubmit: move |_| {{\n            comments.write().push(Comment {{\n                content: comment_field.get().clone(),\n                id: *next_id.get(),\n            }});\n            next_id += 1;\n\n            comment_field.set(String::new());\n        }},\n        input {{\n            value: "{{comment_field}}",\n            oninput: move |event| comment_field.set(event.value.clone()),\n        }}\n        input {{\n            r#type: "submit",\n        }}\n    }},\n    comments_rendered,\n))
\n", + name: "rendering_lists.rs".to_string(), + } + DemoFrame { rendering_lists::App {} } + h3 { id: "inline-for-loops", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::InlineForLoops, + }, + class: "header", + "Inline for loops" + } + } + p { + "Because of how common it is to render a list of items, Dioxus provides a shorthand for this. Instead of using " + code { ".iter" } + ", " + code { ".map" } + ", and " + code { "rsx" } + ", you can use a " + code { "for" } + " loop with a body of rsx code:" + } + CodeBlock { + contents: "
\nlet comment_field = use_state(cx, String::new);\nlet mut next_id = use_state(cx, || 0);\nlet comments = use_ref(cx, Vec::<Comment>::new);\n\ncx.render(rsx!(\n    form {{\n        onsubmit: move |_| {{\n            comments.write().push(Comment {{\n                content: comment_field.get().clone(),\n                id: *next_id.get(),\n            }});\n            next_id += 1;\n\n            comment_field.set(String::new());\n        }},\n        input {{\n            value: "{{comment_field}}",\n            oninput: move |event| comment_field.set(event.value.clone()),\n        }}\n        input {{\n            r#type: "submit",\n        }}\n    }},\n    for comment in &*comments.read() {{\n        // Notice the body of this for loop is rsx code, not an expression\n        CommentComponent {{\n            key: "{{comment.id}}",\n            comment: comment.clone(),\n        }}\n    }}\n))
\n", + name: "rendering_lists.rs".to_string(), + } + DemoFrame { rendering_lists::AppForLoop {} } + h3 { id: "the-key-attribute", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::TheKeyAttribute, + }, + class: "header", + "The key Attribute" + } + } + p { + "Every time you re-render your list, Dioxus needs to keep track of which items go where to determine what updates need to be made to the UI." + } + p { + "For example, suppose the " + code { "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!" + } + p { + "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 matter where you get the key from, as long as it meets the requirements:" + } + ul { + li { "Keys must be unique in a list" } + li { "The same item should always get associated with the same key" } + li { + "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" + } + } + p { + "You might be tempted to use an item's index in the list as its key. 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." + } + blockquote { + p { + "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." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceRouterSection { + #[default] + Empty, + Router, + WhatIsIt, + UsingTheRouter, + Links, + MoreReading, +} +impl std::str::FromStr for ReferenceRouterSection { + type Err = ReferenceRouterSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "router" => Ok(Self::Router), + "what-is-it" => Ok(Self::WhatIsIt), + "using-the-router" => Ok(Self::UsingTheRouter), + "links" => Ok(Self::Links), + "more-reading" => Ok(Self::MoreReading), + _ => Err(ReferenceRouterSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceRouterSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Router => f.write_str("router"), + Self::WhatIsIt => f.write_str("what-is-it"), + Self::UsingTheRouter => f.write_str("using-the-router"), + Self::Links => f.write_str("links"), + Self::MoreReading => f.write_str("more-reading"), + } + } +} +#[derive(Debug)] +pub struct ReferenceRouterSectionParseError; +impl std::fmt::Display for ReferenceRouterSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceRouterSectionrouter, what-is-it, using-the-router, links, more-reading", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceRouterSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceRouter(section: ReferenceRouterSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "router", + Link { + to: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::Router, + }, + class: "header", + "Router" + } + } + p { + "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. For a desktop app, these scenes might be different views in your app." + } + p { + "To unify these platforms, Dioxus provides a first-party solution for scene management called Dioxus Router." + } + h2 { id: "what-is-it", + Link { + to: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::WhatIsIt, + }, + class: "header", + "What is it?" + } + } + p { + "For an app like the Dioxus landing page (https://dioxuslabs.com), we want to have several different scenes:" + } + ul { + li { "Homepage" } + li { "Blog" } + } + p { + "Each of these scenes is independent – we don't want to render both the homepage and blog at the same time." + } + p { + "The Dioxus router makes it easy to create these scenes. To make sure we're using the router, add the " + code { "dioxus-router" } + " package to your " + code { "Cargo.toml" } + "." + } + CodeBlock { contents: "
\ncargo add dioxus-router
\n" } + h2 { id: "using-the-router", + Link { + to: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::UsingTheRouter, + }, + class: "header", + "Using the router" + } + } + p { + "Unlike other routers in the Rust ecosystem, our router is built declaratively at compile time. This makes it possible to compose our app layout simply by defining an enum." + } + CodeBlock { + contents: "
\n// All of our routes will be a variant of this Route enum\nenum Route {{\n\t// if the current location is "/home", render the Home component\n\t#[route("/home")]\n\tHome {{}},\n\t// if the current location is "/blog", render the Blog component\n\t#[route("/blog")]\n\tBlog {{}},\n}}
\n", + } + p { + "Whenever we visit this app, we will get either the Home component or the Blog component rendered depending on which route we enter at. If neither of these routes match the current location, then nothing will render." + } + p { "We can fix this one of two ways:" } + ul { + li { "A fallback 404 page" } + } + CodeBlock { + contents: "
\nenum Route {{\n\t#[route("/home")]\n\tHome {{}},\n\t#[route("/blog")]\n\tBlog {{}},\n\t//  if the current location doesn't match any of the above routes, render the NotFound component\n\t#[route("/:..segments")]\n\tNotFound {{ segments: Vec<String> }},\n}}
\n", + } + ul { + li { "Redirect 404 to home" } + } + CodeBlock { + contents: "
\nenum Route {{\n\t#[route("/home")]\n\t//  if the current location doesn't match any of the above routes, redirect to "/home"\n\t#[redirect("/:..segments", |segments: Vec<String>| Route::Home {{}})]\n\tHome {{}},\n\t#[route("/blog")]\n\tBlog {{}},\n\t//  if the current location doesn't match any of the above routes, render the NotFound component\n\t#[route("/:..segments")]\n\tNotFound {{ segments: Vec<String> }},\n}}
\n", + } + h2 { id: "links", + Link { + to: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::Links, + }, + class: "header", + "Links" + } + } + p { + "For our app to navigate these routes, we can provide clickable elements called Links. These simply wrap " + code { "
" } + " elements that, when clicked, navigate the app to the given location. Because our route is an enum of valid routes, if you try to link to a page that doesn't exist, you will get a compiler error." + } + CodeBlock { contents: "
\nrsx! {{\n\tLink {{\n\t\tto: Route::Home {{}},\n\t\t"Go home!"\n\t}}\n}}
\n" } + h2 { id: "more-reading", + Link { + to: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::MoreReading, + }, + class: "header", + "More reading" + } + } + p { + "This page is just a very brief overview of the router. For more information, check out the " + Link { + to: BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }, + "router book" + } + " or some of the " + Link { to: "https://github.com/DioxusLabs/dioxus/blob/master/examples/router.rs", + "router examples" + } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceUseFutureSection { + #[default] + Empty, + Usefuture, + RestartingTheFuture, + Dependencies, +} +impl std::str::FromStr for ReferenceUseFutureSection { + type Err = ReferenceUseFutureSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "usefuture" => Ok(Self::Usefuture), + "restarting-the-future" => Ok(Self::RestartingTheFuture), + "dependencies" => Ok(Self::Dependencies), + _ => Err(ReferenceUseFutureSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceUseFutureSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Usefuture => f.write_str("usefuture"), + Self::RestartingTheFuture => f.write_str("restarting-the-future"), + Self::Dependencies => f.write_str("dependencies"), + } + } +} +#[derive(Debug)] +pub struct ReferenceUseFutureSectionParseError; +impl std::fmt::Display for ReferenceUseFutureSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceUseFutureSectionusefuture, restarting-the-future, dependencies", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceUseFutureSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceUseFuture(section: ReferenceUseFutureSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "usefuture", + Link { + to: BookRoute::ReferenceUseFuture { + section: ReferenceUseFutureSection::Usefuture, + }, + class: "header", + "UseFuture" + } + } + p { + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_future.html", + code { "use_future" } + } + " lets you run an async closure, and provides you with its result." + } + p { + "For example, we can make an API request (using " + Link { to: "https://docs.rs/reqwest/latest/reqwest/index.html", "reqwest" } + ") inside " + code { "use_future" } + ":" + } + CodeBlock { + contents: "
\nlet future = use_future(cx, (), |_| async move {{\n    reqwest::get("https://dog.ceo/api/breeds/image/random")\n        .await\n        .unwrap()\n        .json::<ApiResponse>()\n        .await\n}});
\n", + name: "use_future.rs".to_string(), + } + p { + "The code inside " + code { "use_future" } + " will be submitted to the Dioxus scheduler once the component has rendered." + } + p { + "We can use " + code { ".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 " + code { "None" } + ". However, once the future is finished, the component will be re-rendered and the value will now be " + code { "Some(...)" } + ", containing the return value of the closure." + } + p { "We can then render that result:" } + CodeBlock { + contents: "
\ncx.render(match future.value() {{\n    Some(Ok(response)) => rsx! {{\n        button {{\n            onclick: move |_| future.restart(),\n            "Click to fetch another doggo"\n        }}\n        div {{\n            img {{\n                max_width: "500px",\n                max_height: "500px",\n                src: "{{response.image_url}}",\n            }}\n        }}\n    }},\n    Some(Err(_)) => rsx! {{ div {{ "Loading dogs failed" }} }},\n    None => rsx! {{ div {{ "Loading dogs..." }} }},\n}})
\n", + name: "use_future.rs".to_string(), + } + DemoFrame { __interactive_04::use_future_ {} } + h2 { id: "restarting-the-future", + Link { + to: BookRoute::ReferenceUseFuture { + section: ReferenceUseFutureSection::RestartingTheFuture, + }, + class: "header", + "Restarting the Future" + } + } + p { + "The " + code { "UseFuture" } + " handle provides a " + code { "restart" } + " method. It can be used to execute the future again, producing a new value." + } + h2 { id: "dependencies", + Link { + to: BookRoute::ReferenceUseFuture { + section: ReferenceUseFutureSection::Dependencies, + }, + class: "header", + "Dependencies" + } + } + p { + "Often, you will need to run the future again every time some value (e.g. a prop) changes. Rather than calling " + code { "restart" } + " 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:" + } + CodeBlock { + contents: "
\nlet future = use_future(cx, (breed,), |(breed,)| async move {{\n    reqwest::get(format!("https://dog.ceo/api/breed/{{breed}}/images/random"))\n        .await\n        .unwrap()\n        .json::<ApiResponse>()\n        .await\n}});
\n", + name: "use_future.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceUseCoroutineSection { + #[default] + Empty, + Coroutines, + UseCoroutine, + YieldingValues, + SendingValues, + AutomaticInjectionIntoTheContextApi, +} +impl std::str::FromStr for ReferenceUseCoroutineSection { + type Err = ReferenceUseCoroutineSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "coroutines" => Ok(Self::Coroutines), + "use-coroutine" => Ok(Self::UseCoroutine), + "yielding-values" => Ok(Self::YieldingValues), + "sending-values" => Ok(Self::SendingValues), + "automatic-injection-into-the-context-api" => { + Ok(Self::AutomaticInjectionIntoTheContextApi) + } + _ => Err(ReferenceUseCoroutineSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceUseCoroutineSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Coroutines => f.write_str("coroutines"), + Self::UseCoroutine => f.write_str("use-coroutine"), + Self::YieldingValues => f.write_str("yielding-values"), + Self::SendingValues => f.write_str("sending-values"), + Self::AutomaticInjectionIntoTheContextApi => { + f.write_str("automatic-injection-into-the-context-api") + } + } + } +} +#[derive(Debug)] +pub struct ReferenceUseCoroutineSectionParseError; +impl std::fmt::Display for ReferenceUseCoroutineSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceUseCoroutineSectioncoroutines, use-coroutine, yielding-values, sending-values, automatic-injection-into-the-context-api", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceUseCoroutineSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceUseCoroutine(section: ReferenceUseCoroutineSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "coroutines", + Link { + to: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::Coroutines, + }, + class: "header", + "Coroutines" + } + } + p { + "Another tool in your async toolbox are coroutines. Coroutines are futures that can have values sent to them." + } + p { + "Like regular futures, code in a coroutine will run until the next " + code { "await" } + " point before yielding. This low-level control over asynchronous tasks is quite powerful, allowing for infinitely looping tasks like WebSocket polling, background timers, and other periodic actions." + } + h2 { id: "use-coroutine", + Link { + to: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::UseCoroutine, + }, + class: "header", + "use_coroutine" + } + } + p { + "The " + code { "use_coroutine" } + " hook allows you to create a coroutine. Most coroutines we write will be polling loops using await." + } + CodeBlock { + contents: "
\nuse futures_util::StreamExt;\n\nfn app(cx: Scope) {{\n    let ws: &Coroutine<()> = use_coroutine(cx, |rx| async move {{\n        // Connect to some sort of service\n        let mut conn = connect_to_ws_server().await;\n\n        // Wait for data on the service\n        while let Some(msg) = conn.next().await {{\n            // handle messages\n        }}\n    }});\n}}
\n", + name: "use_coroutine_reference.rs".to_string(), + } + p { "For many services, a simple async loop will handle the majority of use cases." } + h2 { id: "yielding-values", + Link { + to: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::YieldingValues, + }, + class: "header", + "Yielding Values" + } + } + p { + "To yield values from a coroutine, simply bring in a " + code { "UseState" } + " handle and set the value whenever your coroutine completes its work." + } + p { + "The future must be " + code { "'static" } + " – so any values captured by the task cannot carry any references to " + code { "cx" } + ", such as a " + code { "UseState" } + "." + } + p { + "You can use " + Link { to: "https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned", + "to_owned" + } + " to create a clone of the hook handle which can be moved into the async closure." + } + CodeBlock { + contents: "
\nlet sync_status = use_state(cx, || Status::Launching);\nlet sync_task = use_coroutine(cx, |rx: UnboundedReceiver<SyncAction>| {{\n    let sync_status = sync_status.to_owned();\n    async move {{\n        loop {{\n            tokio::time::sleep(Duration::from_secs(1)).await;\n            sync_status.set(Status::Working);\n        }}\n    }}\n}});
\n", + name: "use_coroutine_reference.rs".to_string(), + } + p { + "To make this a bit less verbose, Dioxus exports the " + code { "to_owned!" } + " macro which will create a binding as shown above, which can be quite helpful when dealing with many values." + } + CodeBlock { + contents: "
\nlet sync_status = use_state(cx, || Status::Launching);\nlet load_status = use_state(cx, || Status::Launching);\nlet sync_task = use_coroutine(cx, |rx: UnboundedReceiver<SyncAction>| {{\n    to_owned![sync_status, load_status];\n    async move {{\n        // ...\n    }}\n}});
\n", + name: "use_coroutine_reference.rs".to_string(), + } + h2 { id: "sending-values", + Link { + to: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::SendingValues, + }, + class: "header", + "Sending Values" + } + } + p { + "You might've noticed the " + code { "use_coroutine" } + " closure takes an argument called " + code { "rx" } + ". What is that? Well, a common pattern in complex apps is to handle a bunch of async code at once. With libraries like Redux Toolkit, managing multiple promises at once can be challenging and a common source of bugs." + } + p { + "With Coroutines, we can centralize our async logic. The " + code { "rx" } + " parameter is an Channel that allows code external to the coroutine to send data " + em { "into" } + " the coroutine. Instead of looping on an external service, we can loop on the channel itself, processing messages from within our app without needing to spawn a new future. To send data into the coroutine, we would call \"send\" on the handle." + } + CodeBlock { + contents: "
\nuse futures_util::StreamExt;\n\nenum ProfileUpdate {{\n    SetUsername(String),\n    SetAge(i32),\n}}\n\nlet profile = use_coroutine(cx, |mut rx: UnboundedReceiver<ProfileUpdate>| async move {{\n    let mut server = connect_to_server().await;\n\n    while let Some(msg) = rx.next().await {{\n        match msg {{\n            ProfileUpdate::SetUsername(name) => server.update_username(name).await,\n            ProfileUpdate::SetAge(age) => server.update_age(age).await,\n        }}\n    }}\n}});\n\ncx.render(rsx! {{\n    button {{\n        onclick: move |_| profile.send(ProfileUpdate::SetUsername("Bob".to_string())),\n        "Update username"\n    }}\n}})
\n", + name: "use_coroutine_reference.rs".to_string(), + } + blockquote { + p { + "Note: In order to use/run the " + code { "rx.next().await" } + " statement you will need to extend the " + "[ " + code { "Stream" } + "]" + " trait (used by " + "[ " + code { "UnboundedReceiver" } + "]" + " " + ") by adding 'futures_util' as a dependency to your project and adding the " + code { "use futures_util::stream::StreamExt;" } + "." + } + } + p { + "For sufficiently complex apps, we could build a bunch of different useful \"services\" that loop on channels to update the app." + } + CodeBlock { + contents: "
\nlet profile = use_coroutine(cx, profile_service);\nlet editor = use_coroutine(cx, editor_service);\nlet sync = use_coroutine(cx, sync_service);\n\nasync fn profile_service(rx: UnboundedReceiver<ProfileCommand>) {{\n    // do stuff\n}}\n\nasync fn sync_service(rx: UnboundedReceiver<SyncCommand>) {{\n    // do stuff\n}}\n\nasync fn editor_service(rx: UnboundedReceiver<EditorCommand>) {{\n    // do stuff\n}}
\n", + name: "use_coroutine_reference.rs".to_string(), + } + p { + "We can combine coroutines with " + Link { to: "https://docs.rs/fermi/latest/fermi/index.html", "Fermi" } + " to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state " + em { "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 " + em { "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." + } + CodeBlock { + contents: "
\nstatic USERNAME: Atom<String> = Atom(|_| "default".to_string());\n\nfn app(cx: Scope) -> Element {{\n    let atoms = use_atom_root(cx);\n\n    use_coroutine(cx, |rx| sync_service(rx, atoms.clone()));\n\n    cx.render(rsx! {{\n        Banner {{}}\n    }})\n}}\n\nfn Banner(cx: Scope) -> Element {{\n    let username = use_read(cx, &USERNAME);\n\n    cx.render(rsx! {{\n        h1 {{ "Welcome back, {{username}}" }}\n    }})\n}}
\n", + name: "use_coroutine_reference.rs".to_string(), + } + p { + "Now, in our sync service, we can structure our state however we want. We only need to update the view values when ready." + } + CodeBlock { + contents: "
\n
\n", + name: "use_coroutine_reference.rs".to_string(), + } + h2 { id: "automatic-injection-into-the-context-api", + Link { + to: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::AutomaticInjectionIntoTheContextApi, + }, + class: "header", + "Automatic injection into the Context API" + } + } + p { + "Coroutine handles are automatically injected through the context API. You can use the " + code { "use_coroutine_handle" } + " hook with the message type as a generic to fetch a handle." + } + CodeBlock { + contents: "
\nfn Child(cx: Scope) -> Element {{\n    let sync_task = use_coroutine_handle::<SyncAction>(cx).unwrap();\n\n    sync_task.send(SyncAction::SetUsername);\n\n    todo!()\n}}
\n", + name: "use_coroutine_reference.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceSpawnSection { + #[default] + Empty, + SpawningFutures, + SpawningTokioTasks, +} +impl std::str::FromStr for ReferenceSpawnSection { + type Err = ReferenceSpawnSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "spawning-futures" => Ok(Self::SpawningFutures), + "spawning-tokio-tasks" => Ok(Self::SpawningTokioTasks), + _ => Err(ReferenceSpawnSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceSpawnSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::SpawningFutures => f.write_str("spawning-futures"), + Self::SpawningTokioTasks => f.write_str("spawning-tokio-tasks"), + } + } +} +#[derive(Debug)] +pub struct ReferenceSpawnSectionParseError; +impl std::fmt::Display for ReferenceSpawnSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceSpawnSectionspawning-futures, spawning-tokio-tasks", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceSpawnSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceSpawn(section: ReferenceSpawnSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "spawning-futures", + Link { + to: BookRoute::ReferenceSpawn { + section: ReferenceSpawnSection::SpawningFutures, + }, + class: "header", + "Spawning Futures" + } + } + p { + "The " + code { "use_future" } + " and " + code { "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 " + code { "cx.spawn" } + ":" + } + CodeBlock { + contents: "
\nlet response = use_state(cx, || String::from("..."));\n\nlet log_in = move |_| {{\n    cx.spawn({{\n        to_owned![response];\n\n        async move {{\n            let resp = reqwest::Client::new()\n                .get("https://dioxuslabs.com")\n                .send()\n                .await;\n\n            match resp {{\n                Ok(_data) => {{\n                    log::info!("dioxuslabs.com responded!");\n                    response.set("dioxuslabs.com responded!".into());\n                }}\n                Err(err) => {{\n                    log::info!("Request failed with error: {{err:?}}")\n                }}\n            }}\n        }}\n    }});\n}};\n\nrender! {{\n    button {{\n        onclick: log_in,\n        "Response: {{response}}",\n    }}\n}}
\n", + name: "spawn.rs".to_string(), + } + DemoFrame { spawn::App {} } + blockquote { + p { + "Note: " + code { "spawn" } + " will always spawn a " + em { "new" } + " future. You most likely don't want to call it on every render." + } + } + p { + "Calling " + code { "spawn" } + " will give you a " + code { "JoinHandle" } + " which lets you cancel or pause the future." + } + h2 { id: "spawning-tokio-tasks", + Link { + to: BookRoute::ReferenceSpawn { + section: ReferenceSpawnSection::SpawningTokioTasks, + }, + class: "header", + "Spawning Tokio Tasks" + } + } + p { + "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:" + } + CodeBlock { + contents: "
\ncx.spawn(async {{\n    let _ = tokio::spawn(async {{}}).await;\n\n    let _ = tokio::task::spawn_local(async {{\n        // some !Send work\n    }})\n    .await;\n}});
\n", + name: "spawn.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceDesktopIndexSection { + #[default] + Empty, + Desktop, + RunningJavascript, + CustomAssets, + IntegratingWithWry, +} +impl std::str::FromStr for ReferenceDesktopIndexSection { + type Err = ReferenceDesktopIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "desktop" => Ok(Self::Desktop), + "running-javascript" => Ok(Self::RunningJavascript), + "custom-assets" => Ok(Self::CustomAssets), + "integrating-with-wry" => Ok(Self::IntegratingWithWry), + _ => Err(ReferenceDesktopIndexSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceDesktopIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Desktop => f.write_str("desktop"), + Self::RunningJavascript => f.write_str("running-javascript"), + Self::CustomAssets => f.write_str("custom-assets"), + Self::IntegratingWithWry => f.write_str("integrating-with-wry"), + } + } +} +#[derive(Debug)] +pub struct ReferenceDesktopIndexSectionParseError; +impl std::fmt::Display for ReferenceDesktopIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceDesktopIndexSectiondesktop, running-javascript, custom-assets, integrating-with-wry", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceDesktopIndexSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceDesktopIndex(section: ReferenceDesktopIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "desktop", + Link { + to: BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::Desktop, + }, + class: "header", + "Desktop" + } + } + p { "This guide will cover concepts specific to the Dioxus desktop renderer." } + h2 { id: "running-javascript", + Link { + to: BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::RunningJavascript, + }, + class: "header", + "Running Javascript" + } + } + p { + "Dioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose." + } + p { + "For these cases, Dioxus desktop exposes the use_eval hook that allows you to run raw Javascript in the webview:" + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\nfn main() {{\n    dioxus_desktop::launch(app);\n}}\n\nfn app(cx: Scope) -> Element {{\n    // Use eval returns a function that can spawn eval instances\n    let create_eval = use_eval(cx);\n\n    // You can create as many eval instances as you want\n    let mut eval = create_eval(\n        r#"\n        // You can send messages from JavaScript to Rust with the dioxus.send function\n        dioxus.send("Hi from JS!");\n        // You can receive messages from Rust to JavaScript with the dioxus.recv function\n        let msg = await dioxus.recv();\n        console.log(msg);\n        "#,\n    )\n    .unwrap();\n\n    // You can send messages to JavaScript with the send method\n    eval.send("Hi from Rust!".into()).unwrap();\n\n    let future = use_future(cx, (), |_| {{\n        to_owned![eval];\n        async move {{\n            // You can receive any message from JavaScript with the recv method\n            eval.recv().await.unwrap()\n        }}\n    }});\n\n    match future.value() {{\n        Some(v) => cx.render(rsx!(\n            p {{ "{{v}}" }}\n        )),\n        _ => cx.render(rsx!(\n            p {{ "hello" }}\n        )),\n    }}\n}}
\n", + name: "eval.rs".to_string(), + } + h2 { id: "custom-assets", + Link { + to: BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::CustomAssets, + }, + class: "header", + "Custom Assets" + } + } + p { "You can link to local assets in dioxus desktop instead of using a url:" } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\nfn main() {{\n    dioxus_desktop::launch(app);\n}}\n\nfn app(cx: Scope) -> Element {{\n    cx.render(rsx! {{\n        div {{\n            img {{ src: "examples/assets/logo.png" }}\n        }}\n    }})\n}}
\n", + name: "custom_assets.rs".to_string(), + } + h2 { id: "integrating-with-wry", + Link { + to: BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::IntegratingWithWry, + }, + class: "header", + "Integrating with Wry" + } + } + p { + "In cases where you need more low level control over your window, you can use wry APIs exposed through the " + Link { to: "https://docs.rs/dioxus-desktop/0.3.0/dioxus_desktop/struct.Config.html", + "Desktop Config" + } + " and the " + Link { to: "https://docs.rs/dioxus-desktop/0.4.0/dioxus_desktop/fn.use_window.html", + "use_window hook" + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceWebIndexSection { + #[default] + Empty, + Web, + RunningJavascript, + CustomizingIndexTemplate, +} +impl std::str::FromStr for ReferenceWebIndexSection { + type Err = ReferenceWebIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "web" => Ok(Self::Web), + "running-javascript" => Ok(Self::RunningJavascript), + "customizing-index-template" => Ok(Self::CustomizingIndexTemplate), + _ => Err(ReferenceWebIndexSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceWebIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Web => f.write_str("web"), + Self::RunningJavascript => f.write_str("running-javascript"), + Self::CustomizingIndexTemplate => f.write_str("customizing-index-template"), + } + } +} +#[derive(Debug)] +pub struct ReferenceWebIndexSectionParseError; +impl std::fmt::Display for ReferenceWebIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceWebIndexSectionweb, running-javascript, customizing-index-template", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceWebIndexSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceWebIndex(section: ReferenceWebIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "web", + Link { + to: BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::Web, + }, + class: "header", + "Web" + } + } + p { "This guide will cover concepts specific to the Dioxus web renderer." } + h2 { id: "running-javascript", + Link { + to: BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::RunningJavascript, + }, + class: "header", + "Running Javascript" + } + } + p { + "Dioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose." + } + p { + "For these cases, Dioxus desktop exposes the use_eval hook that allows you to run raw Javascript in the webview:" + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\nfn main() {{\n    dioxus_desktop::launch(app);\n}}\n\nfn app(cx: Scope) -> Element {{\n    // Use eval returns a function that can spawn eval instances\n    let create_eval = use_eval(cx);\n\n    // You can create as many eval instances as you want\n    let mut eval = create_eval(\n        r#"\n        // You can send messages from JavaScript to Rust with the dioxus.send function\n        dioxus.send("Hi from JS!");\n        // You can receive messages from Rust to JavaScript with the dioxus.recv function\n        let msg = await dioxus.recv();\n        console.log(msg);\n        "#,\n    )\n    .unwrap();\n\n    // You can send messages to JavaScript with the send method\n    eval.send("Hi from Rust!".into()).unwrap();\n\n    let future = use_future(cx, (), |_| {{\n        to_owned![eval];\n        async move {{\n            // You can receive any message from JavaScript with the recv method\n            eval.recv().await.unwrap()\n        }}\n    }});\n\n    match future.value() {{\n        Some(v) => cx.render(rsx!(\n            p {{ "{{v}}" }}\n        )),\n        _ => cx.render(rsx!(\n            p {{ "hello" }}\n        )),\n    }}\n}}
\n", + name: "eval.rs".to_string(), + } + p { + "If you are targeting web, but don't plan on targeting any other Dioxus renderer you can also use the generated wrappers in the " + Link { to: "https://rustwasm.github.io/wasm-bindgen/web-sys/index.html", + "web-sys" + } + " and " + Link { to: "https://gloo-rs.web.app/", "gloo" } + " crates." + } + h2 { id: "customizing-index-template", + Link { + to: BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::CustomizingIndexTemplate, + }, + class: "header", + "Customizing Index Template" + } + } + p { + "Dioxus supports providing custom index.html templates. The index.html must include a " + code { "div" } + " with the id " + code { "main" } + " to be used. Hot Reload is still supported. An example" + " " + "is provided in the " + Link { to: "https://github.com/DioxusLabs/Dioxus/examples/PWA-example/index.html", + "PWA-Example" + } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceSsrSection { + #[default] + Empty, + ServerSideRendering, + Setup, + MultithreadedSupport, +} +impl std::str::FromStr for ReferenceSsrSection { + type Err = ReferenceSsrSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "server-side-rendering" => Ok(Self::ServerSideRendering), + "setup" => Ok(Self::Setup), + "multithreaded-support" => Ok(Self::MultithreadedSupport), + _ => Err(ReferenceSsrSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceSsrSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ServerSideRendering => f.write_str("server-side-rendering"), + Self::Setup => f.write_str("setup"), + Self::MultithreadedSupport => f.write_str("multithreaded-support"), + } + } +} +#[derive(Debug)] +pub struct ReferenceSsrSectionParseError; +impl std::fmt::Display for ReferenceSsrSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceSsrSectionserver-side-rendering, setup, multithreaded-support", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceSsrSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceSsr(section: ReferenceSsrSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "server-side-rendering", + Link { + to: BookRoute::ReferenceSsr { + section: ReferenceSsrSection::ServerSideRendering, + }, + class: "header", + "Server-Side Rendering" + } + } + p { + "For lower-level control over the rendering process, you can use the " + code { "dioxus-ssr" } + " crate directly. This can be useful when integrating with a web framework that " + code { "dioxus-fullstack" } + " does not support, or pre-rendering pages." + } + h2 { id: "setup", + Link { + to: BookRoute::ReferenceSsr { + section: ReferenceSsrSection::Setup, + }, + class: "header", + "Setup" + } + } + p { + "For this guide, we're going to show how to use Dioxus SSR with " + Link { to: "https://docs.rs/axum/latest/axum/", "Axum" } + "." + } + p { "Make sure you have Rust and Cargo installed, and then create a new project:" } + CodeBlock { contents: "
\ncargo new --bin demo\ncd demo
\n" } + p { "Add Dioxus and the ssr renderer as dependencies:" } + CodeBlock { contents: "
\ncargo add dioxus\ncargo add dioxus-ssr
\n" } + p { + "Next, add all the Axum dependencies. This will be different if you're using a different Web Framework" + } + CodeBlock { contents: "
\ncargo add tokio --features full\ncargo add axum
\n" } + p { "Your dependencies should look roughly like this:" } + CodeBlock { + contents: "
\n[dependencies]\naxum = "0.4.5"\ndioxus = {{ version = "*" }}\ndioxus-ssr = {{ version = "*" }}\ntokio = {{ version = "1.15.0", features = ["full"] }}
\n", + } + p { "Now, set up your Axum app to respond on an endpoint." } + CodeBlock { + contents: "
\nuse axum::{{response::Html, routing::get, Router}};\nuse dioxus::prelude::*;\n\n#[tokio::main]\nasync fn main() {{\n\tlet addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));\n\tprintln!("listening on http://{{}}", addr);\n\n\taxum::Server::bind(&addr)\n\t\t.serve(\n\t\t\tRouter::new()\n\t\t\t\t.route("/", get(app_endpoint))\n\t\t\t\t.into_make_service(),\n\t\t)\n\t\t.await\n\t\t.unwrap();\n}}
\n", + } + p { + "And then add our endpoint. We can either render " + code { "rsx!" } + " directly:" + } + CodeBlock { contents: "
\nasync fn app_endpoint() -> Html<String> {{\n\t// render the rsx! macro to HTML\n\tHtml(dioxus_ssr::render_lazy(rsx! {{\n\t\tdiv {{ "hello world!" }}\n\t}}))\n}}
\n" } + p { "Or we can render VirtualDoms." } + CodeBlock { + contents: "
\nasync fn app_endpoint() -> Html<String> {{\n\t// create a component that renders a div with the text "hello world"\n\tfn app(cx: Scope) -> Element {{\n\t\tcx.render(rsx!(div {{ "hello world" }}))\n\t}}\n\t// create a VirtualDom with the app component\n\tlet mut app = VirtualDom::new(app);\n\t// rebuild the VirtualDom before rendering\n\tlet _ = app.rebuild();\n\n\t// render the VirtualDom to HTML\n\tHtml(dioxus_ssr::render_vdom(&app))\n}}
\n", + } + h2 { id: "multithreaded-support", + Link { + to: BookRoute::ReferenceSsr { + section: ReferenceSsrSection::MultithreadedSupport, + }, + class: "header", + "Multithreaded Support" + } + } + p { + "The Dioxus VirtualDom, sadly, is not currently " + code { "Send" } + ". Internally, we use quite a bit of interior mutability which is not thread-safe." + " " + "When working with web frameworks that require " + code { "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 spawn a VirtualDom on its own thread and communicate with it via channels or create a pool of VirtualDoms." + " " + "You might notice that you cannot hold the VirtualDom across an await point. Because Dioxus is currently not ThreadSafe, it " + em { "must" } + " remain on the thread it started. We are working on loosening this requirement." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceLiveviewSection { + #[default] + Empty, + Liveview, + RouterIntegration, + ManagingLatency, +} +impl std::str::FromStr for ReferenceLiveviewSection { + type Err = ReferenceLiveviewSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "liveview" => Ok(Self::Liveview), + "router-integration" => Ok(Self::RouterIntegration), + "managing-latency" => Ok(Self::ManagingLatency), + _ => Err(ReferenceLiveviewSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceLiveviewSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Liveview => f.write_str("liveview"), + Self::RouterIntegration => f.write_str("router-integration"), + Self::ManagingLatency => f.write_str("managing-latency"), + } + } +} +#[derive(Debug)] +pub struct ReferenceLiveviewSectionParseError; +impl std::fmt::Display for ReferenceLiveviewSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceLiveviewSectionliveview, router-integration, managing-latency", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceLiveviewSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceLiveview(section: ReferenceLiveviewSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "liveview", + Link { + to: BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::Liveview, + }, + class: "header", + "Liveview" + } + } + p { "This guide will cover concepts specific to the Dioxus liveview renderer." } + h2 { id: "router-integration", + Link { + to: BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::RouterIntegration, + }, + class: "header", + "Router Integration" + } + } + p { + "Currently, the Dioxus router does not integrate with the browser history in the liveview renderer. If you are interested in contributing this feature to Dioxus this issue is tracked " + Link { to: "https://github.com/DioxusLabs/dioxus/issues/1038", "here" } + "." + } + h2 { id: "managing-latency", + Link { + to: BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::ManagingLatency, + }, + class: "header", + "Managing Latency" + } + } + p { + "Liveview makes it incredibly convenient to talk to your server from the client, but there are some downsides. Mainly in Dioxus Liveview every interaction goes through the server by default." + } + p { + "Because of this, with the liveview renderer you need to be very deliberate about managing latency. Events that would be fast enough on other renderers like " + Link { + to: BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::Empty, + }, + "controlled inputs" + } + ", can be frustrating to use in the liveview renderer." + } + p { + "To get around this issue you can inject bits of javascript in your liveview application. If you use a raw attribute as a listener, you can inject some javascript that will be run when the event is triggered:" + } + CodeBlock { contents: "
\nrender! {{\n    div {{\n        input {{\n            "oninput": "console.log('input changed!')"\n        }}\n    }}\n}}
\n" } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceFullstackIndexSection { + #[default] + Empty, + FullstackDevelopment, +} +impl std::str::FromStr for ReferenceFullstackIndexSection { + type Err = ReferenceFullstackIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "fullstack-development" => Ok(Self::FullstackDevelopment), + _ => Err(ReferenceFullstackIndexSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceFullstackIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::FullstackDevelopment => f.write_str("fullstack-development"), + } + } +} +#[derive(Debug)] +pub struct ReferenceFullstackIndexSectionParseError; +impl std::fmt::Display for ReferenceFullstackIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceFullstackIndexSectionfullstack-development", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceFullstackIndexSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceFullstackIndex( + section: ReferenceFullstackIndexSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "fullstack-development", + Link { + to: BookRoute::ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection::FullstackDevelopment, + }, + class: "header", + "Fullstack development" + } + } + p { "Dioxus Fullstack contains helpers for:" } + ul { + li { "Incremental, static, and server side rendering" } + li { "Hydrating your application on the Client" } + li { "Communicating between a server and a client" } + } + p { + "This guide will teach you everything you need to know about how to use the utilities in Dioxus fullstack to create amazing fullstack applications." + } + blockquote { + p { + "In addition to this guide, you can find more examples of full-stack apps and information about how to integrate with other frameworks and desktop renderers in the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/fullstack/examples", + "dioxus-fullstack examples directory" + } + "." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceFullstackServerFunctionsSection { + #[default] + Empty, + CommunicatingWithTheServer, + CachedDataFetching, + RunningTheClientWithDioxusDesktop, + ClientCode, + ServerCode, +} +impl std::str::FromStr for ReferenceFullstackServerFunctionsSection { + type Err = ReferenceFullstackServerFunctionsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "communicating-with-the-server" => Ok(Self::CommunicatingWithTheServer), + "cached-data-fetching" => Ok(Self::CachedDataFetching), + "running-the-client-with-dioxus-desktop" => Ok(Self::RunningTheClientWithDioxusDesktop), + "client-code" => Ok(Self::ClientCode), + "server-code" => Ok(Self::ServerCode), + _ => Err(ReferenceFullstackServerFunctionsSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceFullstackServerFunctionsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::CommunicatingWithTheServer => f.write_str("communicating-with-the-server"), + Self::CachedDataFetching => f.write_str("cached-data-fetching"), + Self::RunningTheClientWithDioxusDesktop => { + f.write_str("running-the-client-with-dioxus-desktop") + } + Self::ClientCode => f.write_str("client-code"), + Self::ServerCode => f.write_str("server-code"), + } + } +} +#[derive(Debug)] +pub struct ReferenceFullstackServerFunctionsSectionParseError; +impl std::fmt::Display for ReferenceFullstackServerFunctionsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceFullstackServerFunctionsSectioncommunicating-with-the-server, cached-data-fetching, running-the-client-with-dioxus-desktop, client-code, server-code", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceFullstackServerFunctionsSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceFullstackServerFunctions( + section: ReferenceFullstackServerFunctionsSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "communicating-with-the-server", + Link { + to: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::CommunicatingWithTheServer, + }, + class: "header", + "Communicating with the server" + } + } + p { + code { "dioxus-fullstack" } + " provides server functions that allow you to call an automatically generated API on the server from the client as if it were a local function." + } + p { + "To make a server function, simply add the " + code { "#[server(YourUniqueType)]" } + " attribute to a function. The function must:" + } + ul { + li { "Be an async function" } + li { + "Have arguments and a return type that both implement serialize and deserialize (with " + Link { to: "https://serde.rs/", "serde" } + ")." + } + li { + "Return a " + code { "Result" } + " with an error type of ServerFnError" + } + } + p { + "You must call " + code { "register" } + " on the type you passed into the server macro in your main function before starting your server to tell Dioxus about the server function." + } + p { + "Let's continue building on the app we made in the " + Link { + to: BookRoute::GettingStartedFullstack { + section: GettingStartedFullstackSection::Empty, + }, + "getting started" + } + " guide. We will add a server function to our app that allows us to double the count on the server." + } + p { "First, add serde as a dependency:" } + CodeBlock { contents: "
\ncargo add serde
\n" } + p { + "Next, add the server function to your " + code { "main.rs" } + ":" + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\nuse dioxus_fullstack::prelude::*;\n\nfn main() {{\n    let config = LaunchBuilder::new(App);\n    #[cfg(feature = "ssr")]\n    let config = config.incremental(\n        IncrementalRendererConfig::default().invalidate_after(std::time::Duration::from_secs(120)),\n    );\n\n    config.launch();\n}}\n\nfn App(cx: Scope) -> Element {{\n    let mut count = use_state(cx, || 0);\n\n    cx.render(rsx! {{\n        h1 {{ "High-Five counter: {{count}}" }}\n        button {{ onclick: move |_| count += 1, "Up high!" }}\n        button {{ onclick: move |_| count -= 1, "Down low!" }}\n        button {{\n            onclick: move |_| {{\n                to_owned![count];\n                async move {{\n                    // Call the server function just like a local async function\n                    if let Ok(new_count) = double_server(*count.current()).await {{\n                        count.set(new_count);\n                    }}\n                }}\n            }},\n            "Double"\n        }}\n    }})\n}}\n\n#[server]\nasync fn double_server(number: i32) -> Result<i32, ServerFnError> {{\n    // Perform some expensive computation or access a database on the server\n    tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n    let result = number * 2;\n    println!("server calculated {{result}}");\n    Ok(result)\n}}
\n", + name: "server_function.rs".to_string(), + } + p { + "Now, build your client-side bundle with " + code { "dx build --features web" } + " and run your server with " + code { "cargo run --features ssr" } + ". You should see a new button that multiplies the count by 2." + } + h2 { id: "cached-data-fetching", + Link { + to: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::CachedDataFetching, + }, + class: "header", + "Cached data fetching" + } + } + p { "One common use case for server functions is fetching data from the server:" } + CodeBlock { + contents: "
\n#![allow(non_snake_case, unused)]\n\nuse dioxus::prelude::*;\nuse dioxus_fullstack::prelude::*;\n\nfn main() {{\n    LaunchBuilder::new(app).launch();\n}}\n\nfn app(cx: Scope) -> Element {{\n    let mut count = use_future(cx, (), |_| async {{ get_server_data().await }});\n\n    cx.render(rsx! {{\n        "server data is {{count.value():?}}"\n    }})\n}}\n\n#[server]\nasync fn get_server_data() -> Result<String, ServerFnError> {{\n    // Access a database\n    tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n    Ok("Hello from the server!".to_string())\n}}
\n", + name: "server_data_fetch.rs".to_string(), + } + p { + "If you navigate to the site above, you will first see " + code { "server data is None" } + ", then after the " + code { "WASM" } + " has loaded and the request to the server has finished, you will see " + code { "server data is Some(Ok(\"Hello from the server!\"))" } + "." + } + p { + "This approach works, but it can be slow. Instead of waiting for the client to load and send a request to the server, what if we could get all of the data we needed for the page on the server and send it down to the client with the initial HTML page?" + } + p { + "This is exactly what the " + code { "use_server_future" } + " hook allows us to do! " + code { "use_server_future" } + " is similar to the " + code { "use_future" } + " hook, but it allows you to wait for a future on the server and send the result of the future down to the client." + } + p { + "Let's change our data fetching to use " + code { "use_server_future" } + ":" + } + CodeBlock { + contents: "
\n#![allow(non_snake_case, unused)]\n\nuse dioxus::prelude::*;\nuse dioxus_fullstack::prelude::*;\n\nfn main() {{\n    LaunchBuilder::new(app).launch();\n}}\n\nfn app(cx: Scope) -> Element {{\n    let mut count = use_server_future(cx, (), |_| async {{ get_server_data().await }})?;\n\n    cx.render(rsx! {{\n        "server data is {{count.value():?}}"\n    }})\n}}\n\n#[server]\nasync fn get_server_data() -> Result<String, ServerFnError> {{\n    // Access a database\n    tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n    Ok("Hello from the server!".to_string())\n}}
\n", + name: "server_data_prefetch.rs".to_string(), + } + blockquote { + p { + "Notice the " + code { "?" } + " after " + code { "use_server_future" } + ". This is what tells Dioxus fullstack to wait for the future to resolve before continuing rendering. If you want to not wait for a specific future, you can just remove the ? and deal with the " + code { "Option" } + " manually." + } + } + p { + "Now when you load the page, you should see " + code { "server data is Ok(\"Hello from the server!\")" } + ". No need to wait for the " + code { "WASM" } + " to load or wait for the request to finish!" + } + SandBoxFrame { url: "https://codesandbox.io/p/sandbox/dioxus-fullstack-server-future-qwpp4p?file=/src/main.rs:3,24" } + h2 { id: "running-the-client-with-dioxus-desktop", + Link { + to: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::RunningTheClientWithDioxusDesktop, + }, + class: "header", + "Running the client with dioxus-desktop" + } + } + p { + "The project presented so far makes a web browser interact with the server, but it is also possible to make a desktop program interact with the server in a similar fashion. (The full example code is available in the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/fullstack/examples/axum-desktop", + "Dioxus repo" + } + ")" + } + p { + "First, we need to make two binary targets, one for the desktop program (the " + code { "client.rs" } + " file), one for the server (the " + code { "server.rs" } + " file). The client app and the server functions are written in a shared " + code { "lib.rs" } + " file." + } + p { + "The desktop and server targets have slightly different build configuration to enable additional dependencies or features." + " " + "The Cargo.toml in the full example has more information, but the main points are:" + } + ul { + li { + "the client.rs has to be run with the " + code { "desktop" } + " feature, so that the optional " + code { "dioxus-desktop" } + " dependency is included" + } + li { + "the server.rs has to be run with the " + code { "ssr" } + " features; this will generate the server part of the server functions and will include the " + code { "axum" } + " dependency to run as a server." + } + } + p { "Once you create your project, you can run the server executable with:" } + CodeBlock { contents: "
\ncargo run --bin server --features ssr
\n" } + p { "and the client desktop executable with:" } + CodeBlock { contents: "
\ncargo run --bin client --features desktop
\n" } + h3 { id: "client-code", + Link { + to: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::ClientCode, + }, + class: "header", + "Client code" + } + } + p { + "The client file is pretty straightforward. You only need to set the server url in the client code, so it knows where to send the network requests. Then, dioxus_desktop launches the app." + } + p { + "For development, the example project runs the server on " + code { "localhost:8080" } + ". " + strong { "Before you release remember to update the url to your production url." } + } + h3 { id: "server-code", + Link { + to: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::ServerCode, + }, + class: "header", + "Server code" + } + } + p { + "In the server code, first you have to set the network address and port where the server will listen to." + } + CodeBlock { + contents: "
\nlet addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
\n", + name: "server_function_desktop_client.rs".to_string(), + } + p { + "Then, you have to register the types declared in the server function macros into the axum server." + " " + "For example, consider this server function:" + } + CodeBlock { + contents: "
\n#[server(GetServerData)]\nasync fn get_server_data() -> Result<String, ServerFnError> {{\n    Ok("Hello from the server!".to_string())\n}}
\n", + name: "server_function_desktop_client.rs".to_string(), + } + p { + "The " + code { "GetServerData" } + " type has to be registered in the axum server, which will add the corresponding route to the server." + } + CodeBlock { + contents: "
\nlet _ = GetServerData::register_explicit();
\n", + name: "server_function_desktop_client.rs".to_string(), + } + p { "Finally, the server is started and it begins responding to requests." } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceFullstackExtractorsSection { + #[default] + Empty, + Extractors, +} +impl std::str::FromStr for ReferenceFullstackExtractorsSection { + type Err = ReferenceFullstackExtractorsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "extractors" => Ok(Self::Extractors), + _ => Err(ReferenceFullstackExtractorsSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceFullstackExtractorsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Extractors => f.write_str("extractors"), + } + } +} +#[derive(Debug)] +pub struct ReferenceFullstackExtractorsSectionParseError; +impl std::fmt::Display for ReferenceFullstackExtractorsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceFullstackExtractorsSectionextractors", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceFullstackExtractorsSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceFullstackExtractors( + section: ReferenceFullstackExtractorsSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "extractors", + Link { + to: BookRoute::ReferenceFullstackExtractors { + section: ReferenceFullstackExtractorsSection::Extractors, + }, + class: "header", + "Extractors" + } + } + p { + "Server functions are an ergonomic way to call a function on the server. Server function work by registering an endpoint on the server and using requests on the client. Most of the time, you shouldn't need to worry about how server functions operate, but there are some times when you need to get some value from the request other than the data passed in the server function." + } + p { + "For example, requests contain information about the user's browser (called the " + Link { to: "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent", + "user agent" + } + "). We can use an extractor to retrieve that information." + } + p { + "You can use the " + code { "extract" } + " method within a server function to extract something from the request. You can extract any type that implements " + code { "FromServerContext" } + " (or when axum is enabled, you can use axum extractors directly):" + } + CodeBlock { + contents: "
\n#[server]\npub async fn log_user_agent() -> Result<(), ServerFnError> {{\n    let axum::TypedHeader(user_agent): axum::TypedHeader<axum::headers::UserAgent> =\n        extract().await?;\n    log::info!("{{:?}}", user_agent);\n    Ok(())\n}}
\n", + name: "server_function_extract.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceFullstackMiddlewareSection { + #[default] + Empty, + Middleware, +} +impl std::str::FromStr for ReferenceFullstackMiddlewareSection { + type Err = ReferenceFullstackMiddlewareSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "middleware" => Ok(Self::Middleware), + _ => Err(ReferenceFullstackMiddlewareSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceFullstackMiddlewareSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Middleware => f.write_str("middleware"), + } + } +} +#[derive(Debug)] +pub struct ReferenceFullstackMiddlewareSectionParseError; +impl std::fmt::Display for ReferenceFullstackMiddlewareSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceFullstackMiddlewareSectionmiddleware", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceFullstackMiddlewareSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceFullstackMiddleware( + section: ReferenceFullstackMiddlewareSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "middleware", + Link { + to: BookRoute::ReferenceFullstackMiddleware { + section: ReferenceFullstackMiddlewareSection::Middleware, + }, + class: "header", + "Middleware" + } + } + p { + "Extractors allow you to wrap your server function in some code that changes either the request or the response. Dioxus fullstack integrates with " + Link { to: "https://docs.rs/tower/latest/tower/index.html", "Tower" } + " to allow you to wrap your server functions in middleware." + } + p { + "You can use the " + code { "#[middleware]" } + " attribute to add a layer of middleware to your server function. Let's add a timeout middleware to a server function. This middleware will stop running the server function if it reaches a certain timeout:" + } + CodeBlock { + contents: "
\n#[server]\n// Add a timeout middleware to the server function that will return an error if the function takes longer than 1 second to execute\n#[middleware(tower_http::timeout::TimeoutLayer::new(std::time::Duration::from_secs(1)))]\npub async fn timeout() -> Result<(), ServerFnError> {{\n    tokio::time::sleep(std::time::Duration::from_secs(2)).await;\n    Ok(())\n}}
\n", + name: "server_function_middleware.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceFullstackAuthenticationSection { + #[default] + Empty, + Authentication, +} +impl std::str::FromStr for ReferenceFullstackAuthenticationSection { + type Err = ReferenceFullstackAuthenticationSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "authentication" => Ok(Self::Authentication), + _ => Err(ReferenceFullstackAuthenticationSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceFullstackAuthenticationSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Authentication => f.write_str("authentication"), + } + } +} +#[derive(Debug)] +pub struct ReferenceFullstackAuthenticationSectionParseError; +impl std::fmt::Display for ReferenceFullstackAuthenticationSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceFullstackAuthenticationSectionauthentication", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceFullstackAuthenticationSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceFullstackAuthentication( + section: ReferenceFullstackAuthenticationSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "authentication", + Link { + to: BookRoute::ReferenceFullstackAuthentication { + section: ReferenceFullstackAuthenticationSection::Authentication, + }, + class: "header", + "Authentication" + } + } + p { + "You can use " + Link { + to: BookRoute::ReferenceFullstackExtractors { + section: ReferenceFullstackExtractorsSection::Empty, + }, + "extractors" + } + " to integrate auth with your Fullstack application." + } + p { + "You can create a custom extractors that extracts the auth session from the request. From that auth session, you can check if the user has the required privileges before returning the private data." + } + p { + "A " + Link { to: "https://github.com/dioxuslabs/dioxus/blob/master/packages/fullstack/examples/axum-auth/src/main.rs", + "full auth example" + } + " with the complete implementation is available in the fullstack examples." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceFullstackRoutingSection { + #[default] + Empty, + Routing, +} +impl std::str::FromStr for ReferenceFullstackRoutingSection { + type Err = ReferenceFullstackRoutingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "routing" => Ok(Self::Routing), + _ => Err(ReferenceFullstackRoutingSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceFullstackRoutingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Routing => f.write_str("routing"), + } + } +} +#[derive(Debug)] +pub struct ReferenceFullstackRoutingSectionParseError; +impl std::fmt::Display for ReferenceFullstackRoutingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceFullstackRoutingSectionrouting", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceFullstackRoutingSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceFullstackRouting( + section: ReferenceFullstackRoutingSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "routing", + Link { + to: BookRoute::ReferenceFullstackRouting { + section: ReferenceFullstackRoutingSection::Routing, + }, + class: "header", + "Routing" + } + } + p { + "You can easily integrate your fullstack application with a client side router using the " + code { "launch_router" } + " macro. The " + code { "launch_router" } + " macro works the same as the " + code { "launch" } + " macro except it accepts a Router instead of a Component:" + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\nuse dioxus_fullstack::prelude::*;\nuse dioxus_router::prelude::*;\nuse serde::{{Deserialize, Serialize}};\n\nfn main() {{\n    let config = LaunchBuilder::<FullstackRouterConfig<Route>>::router();\n    #[cfg(feature = "ssr")]\n    config\n        .incremental(\n            IncrementalRendererConfig::default()\n                .invalidate_after(std::time::Duration::from_secs(120)),\n        )\n        .launch();\n\n    #[cfg(not(feature = "ssr"))]\n    config.launch();\n}}\n\n#[derive(Clone, Routable, Debug, PartialEq, Serialize, Deserialize)]\nenum Route {{\n    #[route("/")]\n    Home {{}},\n    #[route("/blog/:id")]\n    Blog {{ id: i32 }},\n}}\n\n#[component]\nfn Blog(cx: Scope, id: i32) -> Element {{\n    render! {{\n        Link {{ to: Route::Home {{}}, "Go to counter" }}\n        table {{\n            tbody {{\n                for _ in 0..*id {{\n                    tr {{\n                        for _ in 0..*id {{\n                            td {{ "hello world!" }}\n                        }}\n                    }}\n                }}\n            }}\n        }}\n    }}\n}}\n\n#[component]\nfn Home(cx: Scope) -> Element {{\n    let mut count = use_state(cx, || 0);\n    let text = use_state(cx, || "...".to_string());\n\n    cx.render(rsx! {{\n        Link {{\n            to: Route::Blog {{\n                id: *count.get()\n            }},\n            "Go to blog"\n        }}\n        div {{\n            h1 {{ "High-Five counter: {{count}}" }}\n            button {{ onclick: move |_| count += 1, "Up high!" }}\n            button {{ onclick: move |_| count -= 1, "Down low!" }}\n            button {{\n                onclick: move |_| {{\n                    to_owned![text];\n                    async move {{\n                        if let Ok(data) = get_server_data().await {{\n                            println!("Client received: {{}}", data);\n                            text.set(data.clone());\n                            post_server_data(data).await.unwrap();\n                        }}\n                    }}\n                }},\n                "Run server function!"\n            }}\n            "Server said: {{text}}"\n        }}\n    }})\n}}\n\n#[server(PostServerData)]\nasync fn post_server_data(data: String) -> Result<(), ServerFnError> {{\n    println!("Server received: {{}}", data);\n\n    Ok(())\n}}\n\n#[server(GetServerData)]\nasync fn get_server_data() -> Result<String, ServerFnError> {{\n    Ok("Hello from the server!".to_string())\n}}
\n", + name: "server_router.rs".to_string(), + } + SandBoxFrame { url: "https://codesandbox.io/p/sandbox/dioxus-fullstack-router-s75v5q?file=%2Fsrc%2Fmain.rs%3A7%2C1" } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterIndexSection { + #[default] + Empty, + Introduction, +} +impl std::str::FromStr for RouterIndexSection { + type Err = RouterIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "introduction" => Ok(Self::Introduction), + _ => Err(RouterIndexSectionParseError), + } + } +} +impl std::fmt::Display for RouterIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Introduction => f.write_str("introduction"), + } + } +} +#[derive(Debug)] +pub struct RouterIndexSectionParseError; +impl std::fmt::Display for RouterIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of RouterIndexSectionintroduction")?; + Ok(()) + } +} +impl std::error::Error for RouterIndexSectionParseError {} +#[component(no_case_check)] +pub fn RouterIndex(section: RouterIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "introduction", + Link { + to: BookRoute::RouterIndex { + section: RouterIndexSection::Introduction, + }, + class: "header", + "Introduction" + } + } + blockquote { + p { + "If you are not familiar with Dioxus itself, check out the " + Link { + to: BookRoute::GuideIndex { + section: GuideIndexSection::Empty, + }, + "Dioxus guide" + } + " first." + } + } + p { + "Whether you are building a website, desktop app, or mobile app, splitting your app's views into \"pages\" can be an effective method for organization and maintainability." + } + p { + "For this purpose, Dioxus provides a router. Use the " + code { "cargo add" } + " command to add the dependency:" + } + CodeBlock { contents: "
\ncargo add dioxus-router
\n" } + p { + "Then, add this to your " + code { "Dioxus.toml" } + " (learn more about configuration " + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Empty, + }, + "here" + } + "):" + } + CodeBlock { contents: "
\n[web.watcher]\nindex_on_404 = true
\n" } + blockquote { + p { + "This configuration only works when using " + code { "dx serve" } + ". If you host your app in a different way (which you most likely do in production), you need to find out how to add a fallback 404 page to your app, and make it a copy of the generated " + code { "dist/index.html" } + "." + } + } + p { + "This will instruct " + code { "dx serve" } + " to redirect any unknown route to the index, to then be resolved by the router." + " " + "The router works on the client. If we connect through the index route (e.g., " + code { "localhost:8080" } + ", then click a link to go to " + code { "localhost:8080/contact" } + "), the app renders the new route without reloading." + " " + "However, when we go to a route " + em { "before" } + " going to the index (go straight to " + code { "localhost:8080/contact" } + "), we are trying to access a static route from the server, but the only static route on our server is the index (because the Dioxus frontend is a Single Page Application) and it will fail unless we redirect all missing routes to the index." + } + p { + "This book is intended to get you up to speed with Dioxus Router. It is split" + " " + "into two sections:" + } + ol { + li { + "The " + Link { + to: BookRoute::RouterReferenceIndex { + section: RouterReferenceIndexSection::Empty, + }, + "reference" + } + " section explains individual features in" + " " + "depth. You can read it from start to finish, or you can read individual chapters" + " " + "in whatever order you want." + } + li { + "If you prefer a learning-by-doing approach, you can check out the" + em { + Link { + to: BookRoute::RouterExampleIndex { + section: RouterExampleIndexSection::Empty, + }, + "example project" + } + } + ". It guides you through" + " " + "creating a dioxus app, setting up the router, and using some of its" + " " + "functionality." + } + } + blockquote { + p { + "Please note that this is not the only documentation for the Dioxus Router. You" + " " + "can also check out the " + Link { to: "https://docs.rs/dioxus-router/", "API Docs" } + "." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterExampleIndexSection { + #[default] + Empty, + Overview, + YoullLearnHowTo, +} +impl std::str::FromStr for RouterExampleIndexSection { + type Err = RouterExampleIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "overview" => Ok(Self::Overview), + "youll-learn-how-to" => Ok(Self::YoullLearnHowTo), + _ => Err(RouterExampleIndexSectionParseError), + } + } +} +impl std::fmt::Display for RouterExampleIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Overview => f.write_str("overview"), + Self::YoullLearnHowTo => f.write_str("youll-learn-how-to"), + } + } +} +#[derive(Debug)] +pub struct RouterExampleIndexSectionParseError; +impl std::fmt::Display for RouterExampleIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterExampleIndexSectionoverview, youll-learn-how-to", + )?; + Ok(()) + } +} +impl std::error::Error for RouterExampleIndexSectionParseError {} +#[component(no_case_check)] +pub fn RouterExampleIndex(section: RouterExampleIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "overview", + Link { + to: BookRoute::RouterExampleIndex { + section: RouterExampleIndexSection::Overview, + }, + class: "header", + "Overview" + } + } + p { + "In this guide, you'll learn to effectively use Dioxus Router whether you're" + " " + "building a small todo app or the next FAANG company. We will create a small" + " " + "website with a blog, homepage, and more!" + } + blockquote { + p { + "To follow along with the router example, you'll need a working Dioxus app." + " " + "Check out the " + Link { to: "https://dioxuslabs.com/learn/0.4/getting_started", "Dioxus book" } + " to get started." + } + } + blockquote { + p { + "Make sure to add Dioxus Router as a dependency, as explained in the" + Link { + to: BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }, + "introduction" + } + "." + } + } + h2 { id: "youll-learn-how-to", + Link { + to: BookRoute::RouterExampleIndex { + section: RouterExampleIndexSection::YoullLearnHowTo, + }, + class: "header", + "You'll learn how to" + } + } + ul { + li { "Create routes and render \"pages\"." } + li { + "Utilize nested routes, create a navigation bar, and render content for a" + " " + "set of routes." + } + li { "Parse URL parameters to dynamically display content." } + li { "Redirect visitors to different routes." } + } + blockquote { + p { + strong { "Disclaimer" } + } + p { + "The example will only display the features of Dioxus Router. It will not" + " " + "include any actual functionality. To keep things simple we will only be using" + " " + "a single file, this is not the recommended way of doing things with a real" + " " + "application." + } + } + p { + "You can find the complete application in the " + Link { + to: BookRoute::RouterExampleFullCode { + section: RouterExampleFullCodeSection::Empty, + }, + "full code" + } + " chapter." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterExampleFirstRouteSection { + #[default] + Empty, + CreatingOurFirstRoute, + Fundamentals, + CreatingRoutes, + FallbackRoute, + Conclusion, +} +impl std::str::FromStr for RouterExampleFirstRouteSection { + type Err = RouterExampleFirstRouteSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "creating-our-first-route" => Ok(Self::CreatingOurFirstRoute), + "fundamentals" => Ok(Self::Fundamentals), + "creating-routes" => Ok(Self::CreatingRoutes), + "fallback-route" => Ok(Self::FallbackRoute), + "conclusion" => Ok(Self::Conclusion), + _ => Err(RouterExampleFirstRouteSectionParseError), + } + } +} +impl std::fmt::Display for RouterExampleFirstRouteSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::CreatingOurFirstRoute => f.write_str("creating-our-first-route"), + Self::Fundamentals => f.write_str("fundamentals"), + Self::CreatingRoutes => f.write_str("creating-routes"), + Self::FallbackRoute => f.write_str("fallback-route"), + Self::Conclusion => f.write_str("conclusion"), + } + } +} +#[derive(Debug)] +pub struct RouterExampleFirstRouteSectionParseError; +impl std::fmt::Display for RouterExampleFirstRouteSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterExampleFirstRouteSectioncreating-our-first-route, fundamentals, creating-routes, fallback-route, conclusion", + )?; + Ok(()) + } +} +impl std::error::Error for RouterExampleFirstRouteSectionParseError {} +#[component(no_case_check)] +pub fn RouterExampleFirstRoute( + section: RouterExampleFirstRouteSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "creating-our-first-route", + Link { + to: BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::CreatingOurFirstRoute, + }, + class: "header", + "Creating Our First Route" + } + } + p { + "In this chapter, we will start utilizing Dioxus Router and add a homepage and a" + " " + "404 page to our project." + } + h2 { id: "fundamentals", + Link { + to: BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::Fundamentals, + }, + class: "header", + "Fundamentals" + } + } + p { + "The core of the Dioxus Router is the " + "[ " + code { "Routable" } + "]" + " macro and the " + "[ " + code { "Router" } + "]" + " component." + } + p { "Routable is a trait for anything that can:" } + ul { + li { "Be parsed from a URL" } + li { "Be turned into a URL" } + li { "Be rendered as to a Element" } + } + p { + "Let's create a new router. First, we need an actual page to route to! Let's add a homepage component:" + } + CodeBlock { + contents: "
\n#[component]\nfn Home(cx: Scope) -> Element {{\n    render! {{\n        h1 {{ "Welcome to the Dioxus Blog!" }}\n    }}\n}}
\n", + name: "first_route.rs".to_string(), + } + h2 { id: "creating-routes", + Link { + to: BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::CreatingRoutes, + }, + class: "header", + "Creating Routes" + } + } + p { + "We want to use Dioxus Router to separate our application into different \"pages\"." + " " + "Dioxus Router will then determine which page to render based on the URL path." + } + p { + "To start using Dioxus Router, we need to use the " + "[ " + code { "Routable" } + "]" + " macro." + } + p { + "The " + "[ " + code { "Routable" } + "]" + " macro takes an enum with all of the possible routes in our application. Each variant of the enum represents a route and must be annotated with the " + "[ " + code { "route(path)" } + "]" + " attribute." + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n/// An enum of all of the possible routes in the app.\n#[derive(Routable, Clone)]\nenum Route {{\n    // The home page is at the / route\n    #[route("/")]\n    // If the name of the component and variant are the same you can omit the component and props name\n    // If they are different you can specify them like this:\n    // #[route("/", ComponentName, PropsName)]\n    Home {{}},\n}}
\n", + name: "first_route.rs".to_string(), + } + p { + "All other hooks and components the router provides can only be used as a descendant of a " + "[ " + code { "Router" } + "]" + " component." + } + p { + "If you head to your application's browser tab, you should now see the text" + code { "Welcome to Dioxus Blog!" } + " when on the root URL ( " + code { "http://localhost:8080/" } + "). If" + " " + "you enter a different path for the URL, nothing should be displayed." + } + p { + "This is because we told Dioxus Router to render the " + code { "Home" } + " component only when" + " " + "the URL path is " + code { "/" } + "." + } + h2 { id: "fallback-route", + Link { + to: BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::FallbackRoute, + }, + class: "header", + "Fallback Route" + } + } + p { + "In our example, when a route doesn't exist Dioxus Router doesn't render anything. Many sites also have a \"404\" page when a path does not exist. Let's add one to our site." + } + p { + "First, we create a new " + code { "PageNotFound" } + " component." + } + CodeBlock { + contents: "
\n#[component]\nfn PageNotFound(cx: Scope, route: Vec<String>) -> Element {{\n    render! {{\n        h1 {{ "Page not found" }}\n        p {{ "We are terribly sorry, but the page you requested doesn't exist." }}\n        pre {{\n            color: "red",\n            "log:\\nattemped to navigate to: {{route:?}}"\n        }}\n    }}\n}}
\n", + name: "catch_all.rs".to_string(), + } + p { "Next, register the route in the Route enum to match if all other routes fail." } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\nenum Route {{\n    #[route("/")]\n    Home {{}},\n    // PageNotFound is a catch all route that will match any route and placing the matched segments in the route field\n    #[route("/:..route")]\n    PageNotFound {{ route: Vec<String> }},\n}}
\n", + name: "catch_all.rs".to_string(), + } + p { + "Now when you go to a route that doesn't exist, you should see the page not found" + " " + "text." + } + h2 { id: "conclusion", + Link { + to: BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::Conclusion, + }, + class: "header", + "Conclusion" + } + } + p { + "In this chapter, we learned how to create a route and tell Dioxus Router what" + " " + "component to render when the URL path is " + code { "/" } + ". We also created a 404 page to" + " " + "handle when a route doesn't exist. Next, we'll create the blog portion of our" + " " + "site. We will utilize nested routes and URL parameters." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterExampleBuildingANestSection { + #[default] + Empty, + BuildingANest, + SiteNavigation, + UrlParametersAndNestedRoutes, + Conclusion, +} +impl std::str::FromStr for RouterExampleBuildingANestSection { + type Err = RouterExampleBuildingANestSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "building-a-nest" => Ok(Self::BuildingANest), + "site-navigation" => Ok(Self::SiteNavigation), + "url-parameters-and-nested-routes" => Ok(Self::UrlParametersAndNestedRoutes), + "conclusion" => Ok(Self::Conclusion), + _ => Err(RouterExampleBuildingANestSectionParseError), + } + } +} +impl std::fmt::Display for RouterExampleBuildingANestSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::BuildingANest => f.write_str("building-a-nest"), + Self::SiteNavigation => f.write_str("site-navigation"), + Self::UrlParametersAndNestedRoutes => f.write_str("url-parameters-and-nested-routes"), + Self::Conclusion => f.write_str("conclusion"), + } + } +} +#[derive(Debug)] +pub struct RouterExampleBuildingANestSectionParseError; +impl std::fmt::Display for RouterExampleBuildingANestSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterExampleBuildingANestSectionbuilding-a-nest, site-navigation, url-parameters-and-nested-routes, conclusion", + )?; + Ok(()) + } +} +impl std::error::Error for RouterExampleBuildingANestSectionParseError {} +#[component(no_case_check)] +pub fn RouterExampleBuildingANest( + section: RouterExampleBuildingANestSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "building-a-nest", + Link { + to: BookRoute::RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection::BuildingANest, + }, + class: "header", + "Building a Nest" + } + } + p { + "In this chapter, we will begin to build the blog portion of our site which will" + " " + "include links, nested routes, and route parameters." + } + h2 { id: "site-navigation", + Link { + to: BookRoute::RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection::SiteNavigation, + }, + class: "header", + "Site Navigation" + } + } + p { + "Our site visitors won't know all the available pages and blogs on our site so we" + " " + "should provide a navigation bar for them. Our navbar will be a list of links going between our pages." + } + p { + "We want our navbar component to be rendered on several different pages on our site. Instead of duplicating the code, we can create a component that wraps all children routes. This is called a layout component. To tell the router where to render the child routes, we use the " + "[ " + code { "Outlet" } + "]" + " component." + } + p { + "Let's create a new " + code { "NavBar" } + " component:" + } + CodeBlock { + contents: "
\n#[component]\nfn NavBar(cx: Scope) -> Element {{\n    render! {{\n        nav {{\n            ul {{\n                li {{ "links" }}\n            }}\n        }}\n        // The Outlet component will render child routes (In this case just the Home component) inside the Outlet component\n        Outlet::<Route> {{}}\n    }}\n}}
\n", + name: "nested_routes.rs".to_string(), + } + p { + "Next, let's add our " + code { "NavBar" } + " component as a layout to our Route enum:" + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    // All routes under the NavBar layout will be rendered inside of the NavBar Outlet\n    #[layout(NavBar)]\n        #[route("/")]\n        Home {{}},\n    #[end_layout]\n    #[route("/:..route")]\n    PageNotFound {{ route: Vec<String> }},\n}}
\n", + name: "nested_routes.rs".to_string(), + } + p { + "To add links to our " + code { "NavBar" } + ", we could always use an HTML anchor element but that has two issues:" + } + ol { + li { "It causes a full-page reload" } + li { "We can accidentally link to a page that doesn't exist" } + } + p { + "Instead, we want to use the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html", + code { "Link" } + } + " component provided by Dioxus Router." + } + p { + "The " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html", + code { "Link" } + } + " is similar to a regular " + code { "
" } + " tag. It takes a target and children." + } + p { + "Unlike a regular " + code { "" } + " tag, we can pass in our Route enum as the target. Because we annotated our routes with the " + "[ " + code { "route(path)" } + "]" + " attribute, the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html", + code { "Link" } + } + " will know how to generate the correct URL. If we use the Route enum, the rust compiler will prevent us from linking to a page that doesn't exist." + } + p { "Let's add our links:" } + CodeBlock { + contents: "
\n#[component]\nfn NavBar(cx: Scope) -> Element {{\n    render! {{\n        nav {{\n            ul {{\n                li {{\n                    Link {{\n                        // The Link component will navigate to the route specified\n                        // in the target prop which is checked to exist at compile time\n                        to: Route::Home {{}},\n                        "Home"\n                    }}\n                }}\n            }}\n        }}\n        Outlet::<Route> {{}}\n    }}\n}}
\n", + name: "links.rs".to_string(), + } + blockquote { + p { + "Using this method, the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html", + code { "Link" } + } + " component only works for links within our" + " " + "application. To learn more about navigation targets see" + Link { + to: BookRoute::RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection::Empty, + }, + "here" + } + "." + } + } + p { + "Now you should see a list of links near the top of your page. Click on one and" + " " + "you should seamlessly travel between pages." + } + h2 { id: "url-parameters-and-nested-routes", + Link { + to: BookRoute::RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection::UrlParametersAndNestedRoutes, + }, + class: "header", + "URL Parameters and Nested Routes" + } + } + p { + "Many websites such as GitHub put parameters in their URL. For example," + code { "https://github.com/DioxusLabs" } + " utilizes the text after the domain to" + " " + "dynamically search and display content about an organization." + } + p { + "We want to store our blogs in a database and load them as needed. We also" + " " + "want our users to be able to send people a link to a specific blog post." + " " + "Instead of listing all of the blog titles at compile time, we can make a dynamic route." + } + p { + "We could utilize a search page that loads a blog when clicked but then our users" + " " + "won't be able to share our blogs easily. This is where URL parameters come in." + } + p { + "The path to our blog will look like " + code { "/blog/myBlogPage" } + ", " + code { "myBlogPage" } + " being the" + " " + "URL parameter." + } + p { + "First, let's create a layout component (similar to the navbar) that wraps the blog content. This allows us to add a heading that tells the user they are on the blog." + } + CodeBlock { + contents: "
\n#[component]\nfn Blog(cx: Scope) -> Element {{\n    render! {{\n        h1 {{ "Blog" }}\n        Outlet::<Route> {{}}\n    }}\n}}
\n", + name: "dynamic_route.rs".to_string(), + } + p { + "Now we'll create another index component, that'll be displayed when no blog post" + " " + "is selected:" + } + CodeBlock { + contents: "
\n#[component]\nfn BlogList(cx: Scope) -> Element {{\n    render! {{\n        h2 {{ "Choose a post" }}\n        ul {{\n            li {{\n                Link {{\n                    to: Route::BlogPost {{ name: "Blog post 1".into() }},\n                    "Read the first blog post"\n                }}\n            }}\n            li {{\n                Link {{\n                    to: Route::BlogPost {{ name: "Blog post 2".into() }},\n                    "Read the second blog post"\n                }}\n            }}\n        }}\n    }}\n}}
\n", + name: "dynamic_route.rs".to_string(), + } + p { + "We also need to create a component that displays an actual blog post. This component will accept the URL parameters as props:" + } + CodeBlock { + contents: "
\n// The name prop comes from the /:name route segment\n#[component]\nfn BlogPost(cx: Scope, name: String) -> Element {{\n    render! {{\n        h2 {{ "Blog Post: {{name}}"}}\n    }}\n}}
\n", + name: "dynamic_route.rs".to_string(), + } + p { "Finally, let's tell our router about those components:" } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    #[layout(NavBar)]\n        #[route("/")]\n        Home {{}},\n        #[nest("/blog")]\n            #[layout(Blog)]\n            #[route("/")]\n            BlogList {{}},\n            #[route("/post/:name")]\n            BlogPost {{ name: String }},\n            #[end_layout]\n        #[end_nest]\n    #[end_layout]\n    #[route("/:..route")]\n    PageNotFound {{\n        route: Vec<String>,\n    }},\n}}
\n", + name: "dynamic_route.rs".to_string(), + } + p { + "That's it! If you head to " + code { "/blog/1" } + " you should see our sample post." + } + h2 { id: "conclusion", + Link { + to: BookRoute::RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection::Conclusion, + }, + class: "header", + "Conclusion" + } + } + p { + "In this chapter, we utilized Dioxus Router's Link, and Route Parameter" + " " + "functionality to build the blog portion of our application. In the next chapter," + " " + "we will go over how navigation targets (like the one we passed to our links)" + " " + "work." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterExampleNavigationTargetsSection { + #[default] + Empty, + NavigationTargets, + WhatIsANavigationTarget, + ExternalNavigation, +} +impl std::str::FromStr for RouterExampleNavigationTargetsSection { + type Err = RouterExampleNavigationTargetsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "navigation-targets" => Ok(Self::NavigationTargets), + "what-is-a-navigation-target" => Ok(Self::WhatIsANavigationTarget), + "external-navigation" => Ok(Self::ExternalNavigation), + _ => Err(RouterExampleNavigationTargetsSectionParseError), + } + } +} +impl std::fmt::Display for RouterExampleNavigationTargetsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::NavigationTargets => f.write_str("navigation-targets"), + Self::WhatIsANavigationTarget => f.write_str("what-is-a-navigation-target"), + Self::ExternalNavigation => f.write_str("external-navigation"), + } + } +} +#[derive(Debug)] +pub struct RouterExampleNavigationTargetsSectionParseError; +impl std::fmt::Display for RouterExampleNavigationTargetsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterExampleNavigationTargetsSectionnavigation-targets, what-is-a-navigation-target, external-navigation", + )?; + Ok(()) + } +} +impl std::error::Error for RouterExampleNavigationTargetsSectionParseError {} +#[component(no_case_check)] +pub fn RouterExampleNavigationTargets( + section: RouterExampleNavigationTargetsSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "navigation-targets", + Link { + to: BookRoute::RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection::NavigationTargets, + }, + class: "header", + "Navigation Targets" + } + } + p { + "In the previous chapter, we learned how to create links to pages within our app." + " " + "We told them where to go using the " + code { "target" } + " property. This property takes something that can be converted to a " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html", + code { "NavigationTarget" } + } + "." + } + h2 { id: "what-is-a-navigation-target", + Link { + to: BookRoute::RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection::WhatIsANavigationTarget, + }, + class: "header", + "What is a navigation target?" + } + } + p { + "A " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html", + code { "NavigationTarget" } + } + " is similar to the " + code { "href" } + " of an HTML anchor element. It" + " " + "tells the router where to navigate to. The Dioxus Router knows two kinds of" + " " + "navigation targets:" + } + ul { + li { + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.Internal", + code { "Internal" } + } + ": We used internal links in the previous chapter. It's a link to a page within our" + " " + "app represented as a Route enum." + } + li { + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.External", + code { "External" } + } + ": This works exactly like an HTML anchors' " + code { "href" } + ". Don't use this for in-app" + " " + "navigation as it will trigger a page reload by the browser." + } + } + h2 { id: "external-navigation", + Link { + to: BookRoute::RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection::ExternalNavigation, + }, + class: "header", + "External navigation" + } + } + p { "If we need a link to an external page we can do it like this:" } + CodeBlock { + contents: "
\nfn GoToDioxus(cx: Scope) -> Element {{\n    render! {{\n        Link {{\n            to: "https://dioxuslabs.com",\n            "ExternalTarget target"\n        }}\n    }}\n}}
\n", + name: "external_link.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterExampleRedirectionPerfectionSection { + #[default] + Empty, + RedirectionPerfection, + CreatingRedirects, + Conclusion, + Challenges, +} +impl std::str::FromStr for RouterExampleRedirectionPerfectionSection { + type Err = RouterExampleRedirectionPerfectionSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "redirection-perfection" => Ok(Self::RedirectionPerfection), + "creating-redirects" => Ok(Self::CreatingRedirects), + "conclusion" => Ok(Self::Conclusion), + "challenges" => Ok(Self::Challenges), + _ => Err(RouterExampleRedirectionPerfectionSectionParseError), + } + } +} +impl std::fmt::Display for RouterExampleRedirectionPerfectionSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::RedirectionPerfection => f.write_str("redirection-perfection"), + Self::CreatingRedirects => f.write_str("creating-redirects"), + Self::Conclusion => f.write_str("conclusion"), + Self::Challenges => f.write_str("challenges"), + } + } +} +#[derive(Debug)] +pub struct RouterExampleRedirectionPerfectionSectionParseError; +impl std::fmt::Display for RouterExampleRedirectionPerfectionSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterExampleRedirectionPerfectionSectionredirection-perfection, creating-redirects, conclusion, challenges", + )?; + Ok(()) + } +} +impl std::error::Error for RouterExampleRedirectionPerfectionSectionParseError {} +#[component(no_case_check)] +pub fn RouterExampleRedirectionPerfection( + section: RouterExampleRedirectionPerfectionSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "redirection-perfection", + Link { + to: BookRoute::RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection::RedirectionPerfection, + }, + class: "header", + "Redirection Perfection" + } + } + p { "You're well on your way to becoming a routing master!" } + p { "In this chapter, we will cover creating redirects" } + h2 { id: "creating-redirects", + Link { + to: BookRoute::RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection::CreatingRedirects, + }, + class: "header", + "Creating Redirects" + } + } + p { + "A redirect is very simple. When dioxus encounters a redirect while finding out" + " " + "what components to render, it will redirect the user to the target of the" + " " + "redirect." + } + p { + "As a simple example, let's say you want user to still land on your blog, even" + " " + "if they used the path " + code { "/myblog" } + " or " + code { "/myblog/:name" } + "." + } + p { + "Redirects are special attributes in the router enum that accept a route and a closure" + " " + "with the route parameters. The closure should return a route to redirect to." + } + p { "Let's add a redirect to our router enum:" } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    #[layout(NavBar)]\n        #[route("/")]\n        Home {{}},\n        #[nest("/blog")]\n            #[layout(Blog)]\n                #[route("/")]\n                BlogList {{}},\n                #[route("/post/:name")]\n                BlogPost {{ name: String }},\n            #[end_layout]\n        #[end_nest]\n    #[end_layout]\n    #[nest("/myblog")]\n        #[redirect("/", || Route::BlogList {{}})]\n        #[redirect("/:name", |name: String| Route::BlogPost {{ name }})]\n    #[end_nest]\n    #[route("/:..route")]\n    PageNotFound {{\n        route: Vec<String>,\n    }},\n}}
\n", + name: "full_example.rs".to_string(), + } + p { "That's it! Now your users will be redirected to the blog." } + h3 { id: "conclusion", + Link { + to: BookRoute::RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection::Conclusion, + }, + class: "header", + "Conclusion" + } + } + p { + "Well done! You've completed the Dioxus Router guide. You've built a small" + " " + "application and learned about the many things you can do with Dioxus Router." + " " + "To continue your journey, you attempt a challenge listed below, look at the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/router/examples", + "router examples" + } + ", or" + " " + "can check out the " + Link { to: "https://docs.rs/dioxus-router/", "API reference" } + "." + } + h3 { id: "challenges", + Link { + to: BookRoute::RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection::Challenges, + }, + class: "header", + "Challenges" + } + } + ul { + li { "Organize your components into separate files for better maintainability." } + li { "Give your app some style if you haven't already." } + li { "Build an about page so your visitors know who you are." } + li { "Add a user system that uses URL parameters." } + li { "Create a simple admin system to create, delete, and edit blogs." } + li { "If you want to go to the max, hook up your application to a rest API and database." } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterExampleFullCodeSection { + #[default] + Empty, + FullCode, +} +impl std::str::FromStr for RouterExampleFullCodeSection { + type Err = RouterExampleFullCodeSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "full-code" => Ok(Self::FullCode), + _ => Err(RouterExampleFullCodeSectionParseError), + } + } +} +impl std::fmt::Display for RouterExampleFullCodeSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::FullCode => f.write_str("full-code"), + } + } +} +#[derive(Debug)] +pub struct RouterExampleFullCodeSectionParseError; +impl std::fmt::Display for RouterExampleFullCodeSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of RouterExampleFullCodeSectionfull-code")?; + Ok(()) + } +} +impl std::error::Error for RouterExampleFullCodeSectionParseError {} +#[component(no_case_check)] +pub fn RouterExampleFullCode(section: RouterExampleFullCodeSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "full-code", + Link { + to: BookRoute::RouterExampleFullCode { + section: RouterExampleFullCodeSection::FullCode, + }, + class: "header", + "Full Code" + } + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n// ANCHOR: router\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    #[layout(NavBar)]\n        #[route("/")]\n        Home {{}},\n        #[nest("/blog")]\n            #[layout(Blog)]\n                #[route("/")]\n                BlogList {{}},\n                #[route("/post/:name")]\n                BlogPost {{ name: String }},\n            #[end_layout]\n        #[end_nest]\n    #[end_layout]\n    #[nest("/myblog")]\n        #[redirect("/", || Route::BlogList {{}})]\n        #[redirect("/:name", |name: String| Route::BlogPost {{ name }})]\n    #[end_nest]\n    #[route("/:..route")]\n    PageNotFound {{\n        route: Vec<String>,\n    }},\n}}\n// ANCHOR_END: router\n\npub fn App(cx: Scope) -> Element {{\n    render! {{\n        Router::<Route> {{}}\n    }}\n}}\n\n#[component]\nfn NavBar(cx: Scope) -> Element {{\n    render! {{\n        nav {{\n            ul {{\n                li {{ Link {{ to: Route::Home {{}}, "Home" }} }}\n                li {{ Link {{ to: Route::BlogList {{}}, "Blog" }} }}\n            }}\n        }}\n        Outlet::<Route> {{}}\n    }}\n}}\n\n#[component]\nfn Home(cx: Scope) -> Element {{\n    render! {{\n        h1 {{ "Welcome to the Dioxus Blog!" }}\n    }}\n}}\n\n#[component]\nfn Blog(cx: Scope) -> Element {{\n    render! {{\n        h1 {{ "Blog" }}\n        Outlet::<Route> {{}}\n    }}\n}}\n\n#[component]\nfn BlogList(cx: Scope) -> Element {{\n    render! {{\n        h2 {{ "Choose a post" }}\n        ul {{\n            li {{\n                Link {{\n                    to: Route::BlogPost {{ name: "Blog post 1".into() }},\n                    "Read the first blog post"\n                }}\n            }}\n            li {{\n                Link {{\n                    to: Route::BlogPost {{ name: "Blog post 2".into() }},\n                    "Read the second blog post"\n                }}\n            }}\n        }}\n    }}\n}}\n\n#[component]\nfn BlogPost(cx: Scope, name: String) -> Element {{\n    render! {{\n        h2 {{ "Blog Post: {{name}}"}}\n    }}\n}}\n\n#[component]\nfn PageNotFound(cx: Scope, route: Vec<String>) -> Element {{\n    render! {{\n        h1 {{ "Page not found" }}\n        p {{ "We are terribly sorry, but the page you requested doesn't exist." }}\n        pre {{\n            color: "red",\n            "log:\\nattemped to navigate to: {{route:?}}"\n        }}\n    }}\n}}
\n", + name: "full_example.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceIndexSection { + #[default] + Empty, + AddingTheRouterToYourApplication, +} +impl std::str::FromStr for RouterReferenceIndexSection { + type Err = RouterReferenceIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "adding-the-router-to-your-application" => Ok(Self::AddingTheRouterToYourApplication), + _ => Err(RouterReferenceIndexSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::AddingTheRouterToYourApplication => { + f.write_str("adding-the-router-to-your-application") + } + } + } +} +#[derive(Debug)] +pub struct RouterReferenceIndexSectionParseError; +impl std::fmt::Display for RouterReferenceIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceIndexSectionadding-the-router-to-your-application", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceIndexSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceIndex(section: RouterReferenceIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "adding-the-router-to-your-application", + Link { + to: BookRoute::RouterReferenceIndex { + section: RouterReferenceIndexSection::AddingTheRouterToYourApplication, + }, + class: "header", + "Adding the router to your application" + } + } + p { + "In this chapter, we will learn how to add the router to our app. By itself, this" + " " + "is not very useful. However, it is a prerequisite for all the functionality" + " " + "described in the other chapters." + } + blockquote { + p { + "Make sure you added the " + code { "dioxus-router" } + " dependency as explained in the" + Link { + to: BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }, + "introduction" + } + "." + } + } + p { + "In most cases, we want to add the router to the root component of our app. This" + " " + "way, we can ensure that we have access to all its functionality everywhere." + } + p { "First, we define the router with the router macro:" } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n/// An enum of all of the possible routes in the app.\n#[derive(Routable, Clone)]\nenum Route {{\n    // The home page is at the / route\n    #[route("/")]\n    // If the name of the component and variant are the same you can omit the component and props name\n    // If they are different you can specify them like this:\n    // #[route("/", ComponentName, PropsName)]\n    Home {{}},\n}}
\n", + name: "first_route.rs".to_string(), + } + p { + "Then we render the router with the " + "[ " + code { "Router" } + "]" + " component." + } + CodeBlock { + contents: "
\nfn App(cx: Scope) -> Element {{\n    render! {{\n        Router::<Route> {{}}\n    }}\n}}
\n", + name: "first_route.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceRoutesIndexSection { + #[default] + Empty, + DefiningRoutes, + RouteSegments, + StaticSegments, + DynamicSegments, + CatchAllSegments, + QuerySegments, +} +impl std::str::FromStr for RouterReferenceRoutesIndexSection { + type Err = RouterReferenceRoutesIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "defining-routes" => Ok(Self::DefiningRoutes), + "route-segments" => Ok(Self::RouteSegments), + "static-segments" => Ok(Self::StaticSegments), + "dynamic-segments" => Ok(Self::DynamicSegments), + "catch-all-segments" => Ok(Self::CatchAllSegments), + "query-segments" => Ok(Self::QuerySegments), + _ => Err(RouterReferenceRoutesIndexSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceRoutesIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::DefiningRoutes => f.write_str("defining-routes"), + Self::RouteSegments => f.write_str("route-segments"), + Self::StaticSegments => f.write_str("static-segments"), + Self::DynamicSegments => f.write_str("dynamic-segments"), + Self::CatchAllSegments => f.write_str("catch-all-segments"), + Self::QuerySegments => f.write_str("query-segments"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceRoutesIndexSectionParseError; +impl std::fmt::Display for RouterReferenceRoutesIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceRoutesIndexSectiondefining-routes, route-segments, static-segments, dynamic-segments, catch-all-segments, query-segments", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceRoutesIndexSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceRoutesIndex( + section: RouterReferenceRoutesIndexSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "defining-routes", + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::DefiningRoutes, + }, + class: "header", + "Defining Routes" + } + } + p { + "When creating a " + "[ " + code { "Routable" } + "]" + " enum, we can define routes for our application using the " + code { "route(\"path\")" } + " attribute." + } + h2 { id: "route-segments", + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::RouteSegments, + }, + class: "header", + "Route Segments" + } + } + p { + "Each route is made up of segments. Most segments are separated by " + code { "/" } + " characters in the path." + } + p { "There are four fundamental types of segments:" } + ol { + li { + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::StaticSegments, + }, + "Static segments" + } + " are fixed strings that must be present in the path." + } + li { + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::DynamicSegments, + }, + "Dynamic segments" + } + " are types that can be parsed from a segment." + } + li { + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::CatchAllSegments, + }, + "Catch-all segments" + } + " are types that can be parsed from multiple segments." + } + li { + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::QuerySegments, + }, + "Query segments" + } + " are types that can be parsed from the query string." + } + } + p { "Routes are matched:" } + ul { + li { + "First, from most specific to least specific (Static then Dynamic then Catch All) (Query is always matched)" + } + li { + "Then, if multiple routes match the same path, the order in which they are defined in the enum is followed." + } + } + h2 { id: "static-segments", + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::StaticSegments, + }, + class: "header", + "Static segments" + } + } + p { + "Fixed routes match a specific path. For example, the route " + code { "#[route(\"/about\")]" } + " will match the path " + code { "/about" } + "." + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    // Routes always start with a slash\n    #[route("/")]\n    Home {{}},\n    // You can have multiple segments in a route\n    #[route("/hello/world")]\n    HelloWorld {{}},\n}}\n\n#[component]\nfn Home(cx: Scope) -> Element {{\n    todo!()\n}}\n\n#[component]\nfn HelloWorld(cx: Scope) -> Element {{\n    todo!()\n}}
\n", + name: "static_segments.rs".to_string(), + } + h2 { id: "dynamic-segments", + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::DynamicSegments, + }, + class: "header", + "Dynamic Segments" + } + } + p { + "Dynamic segments are in the form of " + code { ":name" } + " where " + code { "name" } + " is" + " " + "the name of the field in the route variant. If the segment is parsed" + " " + "successfully then the route matches, otherwise the matching continues." + } + p { + "The segment can be of any type that implements " + code { "FromStr" } + "." + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    // segments that start with : are dynamic segments\n    #[route("/post/:name")]\n    BlogPost {{\n        // You must include dynamic segments in child variants\n        name: String,\n    }},\n    #[route("/document/:id")]\n    Document {{\n        // You can use any type that implements FromStr\n        // If the segment can't be parsed, the route will not match\n        id: usize,\n    }},\n}}\n\n// Components must contain the same dynamic segments as their corresponding variant\n#[component]\nfn BlogPost(cx: Scope, name: String) -> Element {{\n    todo!()\n}}\n\n#[component]\nfn Document(cx: Scope, id: usize) -> Element {{\n    todo!()\n}}
\n", + name: "dynamic_segments.rs".to_string(), + } + h2 { id: "catch-all-segments", + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::CatchAllSegments, + }, + class: "header", + "Catch All Segments" + } + } + p { + "Catch All segments are in the form of " + code { ":..name" } + " where " + code { "name" } + " is the name of the field in the route variant. If the segments are parsed successfully then the route matches, otherwise the matching continues." + } + p { + "The segment can be of any type that implements " + code { "FromSegments" } + ". (Vec" + p { class: "inline-html-block", dangerous_inner_html: "" } + " implements this by default)" + } + p { + "Catch All segments must be the " + em { "last route segment" } + " in the path (query segments are not counted) and cannot be included in nests." + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    // segments that start with :.. are catch all segments\n    #[route("/blog/:..segments")]\n    BlogPost {{\n        // You must include catch all segment in child variants\n        segments: Vec<String>,\n    }},\n}}\n\n// Components must contain the same catch all segments as their corresponding variant\n#[component]\nfn BlogPost(cx: Scope, segments: Vec<String>) -> Element {{\n    todo!()\n}}
\n", + name: "catch_all_segments.rs".to_string(), + } + h2 { id: "query-segments", + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::QuerySegments, + }, + class: "header", + "Query Segments" + } + } + p { + "Query segments are in the form of " + code { "?:name" } + " where " + code { "name" } + " is the name of the field in the route variant." + } + p { + "Unlike " + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::DynamicSegments, + }, + "Dynamic Segments" + } + " and " + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::CatchAllSegments, + }, + "Catch All Segments" + } + ", parsing a Query segment must not fail." + } + p { + "The segment can be of any type that implements " + code { "FromQuery" } + "." + } + p { + "Query segments must be the " + em { "after all route segments" } + " and cannot be included in nests." + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    // segments that start with ?: are query segments\n    #[route("/blog?:query_params")]\n    BlogPost {{\n        // You must include query segments in child variants\n        query_params: BlogQuerySegments,\n    }},\n}}\n\n#[derive(Debug, Clone, PartialEq)]\nstruct BlogQuerySegments {{\n    name: String,\n    surname: String,\n}}\n\n/// The display impl needs to display the query in a way that can be parsed:\nimpl Display for BlogQuerySegments {{\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{\n        write!(f, "name={{}}&surname={{}}", self.name, self.surname)\n    }}\n}}\n\n/// The query segment is anything that implements https://docs.rs/dioxus-router/latest/dioxus_router/routable/trait.FromQuery.html. You can implement that trait for a struct if you want to parse multiple query parameters.\nimpl FromQuery for BlogQuerySegments {{\n    fn from_query(query: &str) -> Self {{\n        let mut name = None;\n        let mut surname = None;\n        let pairs = form_urlencoded::parse(query.as_bytes());\n        pairs.for_each(|(key, value)| {{\n            if key == "name" {{\n                name = Some(value.clone().into());\n            }}\n            if key == "surname" {{\n                surname = Some(value.clone().into());\n            }}\n        }});\n        Self {{\n            name: name.unwrap(),\n            surname: surname.unwrap(),\n        }}\n    }}\n}}\n\n#[component]\nfn BlogPost(cx: Scope, query_params: BlogQuerySegments) -> Element {{\n    render! {{\n        div{{"This is your blogpost with a query segment:"}}\n        div{{format!("{{:?}}", query_params)}}\n    }}\n}}\n\nfn App(cx: Scope) -> Element {{\n    render! {{ Router::<Route>{{}} }}\n}}\n\nfn main() {{}}
\n", + name: "query_segments.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceRoutesNestedSection { + #[default] + Empty, + NestedRoutes, + Nesting, +} +impl std::str::FromStr for RouterReferenceRoutesNestedSection { + type Err = RouterReferenceRoutesNestedSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "nested-routes" => Ok(Self::NestedRoutes), + "nesting" => Ok(Self::Nesting), + _ => Err(RouterReferenceRoutesNestedSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceRoutesNestedSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::NestedRoutes => f.write_str("nested-routes"), + Self::Nesting => f.write_str("nesting"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceRoutesNestedSectionParseError; +impl std::fmt::Display for RouterReferenceRoutesNestedSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceRoutesNestedSectionnested-routes, nesting", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceRoutesNestedSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceRoutesNested( + section: RouterReferenceRoutesNestedSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "nested-routes", + Link { + to: BookRoute::RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection::NestedRoutes, + }, + class: "header", + "Nested Routes" + } + } + p { + "When developing bigger applications we often want to nest routes within each" + " " + "other. As an example, we might want to organize a settings menu using this" + " " + "pattern:" + } + CodeBlock { contents: "
\n└ Settings\n  ├ General Settings (displayed when opening the settings)\n  ├ Change Password\n  └ Privacy Settings
\n" } + p { "We might want to map this structure to these paths and components:" } + CodeBlock { contents: "
\n/settings\t\t  -> Settings {{ GeneralSettings }}\n/settings/password -> Settings {{ PWSettings }}\n/settings/privacy  -> Settings {{ PrivacySettings }}
\n" } + p { "Nested routes allow us to do this without repeating /settings in every route." } + h2 { id: "nesting", + Link { + to: BookRoute::RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection::Nesting, + }, + class: "header", + "Nesting" + } + } + p { + "To nest routes, we use the " + code { "#[nest(\"path\")]" } + " and " + code { "#[end_nest]" } + " attributes." + } + p { "The path in nest must not:" } + ol { + li { + "Contain a " + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::CatchAllSegments, + }, + "Catch All Segment" + } + } + li { + "Contain a " + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::QuerySegments, + }, + "Query Segment" + } + } + } + p { + "If you define a dynamic segment in a nest, it will be available to all child routes and layouts." + } + p { + "To finish a nest, we use the " + code { "#[end_nest]" } + " attribute or the end of the enum." + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n// Skipping formatting allows you to indent nests\n#[rustfmt::skip]\nenum Route {{\n    // Start the /blog nest\n    #[nest("/blog")]\n        // You can nest as many times as you want\n        #[nest("/:id")]\n            #[route("/post")]\n            PostId {{\n                // You must include parent dynamic segments in child variants\n                id: usize,\n            }},\n        // End nests manually with #[end_nest]\n        #[end_nest]\n        #[route("/:id")]\n        // The absolute route of BlogPost is /blog/:name\n        BlogPost {{\n            id: usize,\n        }},\n    // Or nests are ended automatically at the end of the enum\n}}\n\n#[component]\nfn BlogPost(cx: Scope, id: usize) -> Element {{\n    todo!()\n}}\n\n#[component]\nfn PostId(cx: Scope, id: usize) -> Element {{\n    todo!()\n}}
\n", + name: "nest.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceLayoutsSection { + #[default] + Empty, + Layouts, + LayoutsWithDynamicSegments, +} +impl std::str::FromStr for RouterReferenceLayoutsSection { + type Err = RouterReferenceLayoutsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "layouts" => Ok(Self::Layouts), + "layouts-with-dynamic-segments" => Ok(Self::LayoutsWithDynamicSegments), + _ => Err(RouterReferenceLayoutsSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceLayoutsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Layouts => f.write_str("layouts"), + Self::LayoutsWithDynamicSegments => f.write_str("layouts-with-dynamic-segments"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceLayoutsSectionParseError; +impl std::fmt::Display for RouterReferenceLayoutsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceLayoutsSectionlayouts, layouts-with-dynamic-segments", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceLayoutsSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceLayouts(section: RouterReferenceLayoutsSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "layouts", + Link { + to: BookRoute::RouterReferenceLayouts { + section: RouterReferenceLayoutsSection::Layouts, + }, + class: "header", + "Layouts" + } + } + p { + "Layouts allow you to wrap all child routes in a component. This can be useful when creating something like a header that will be used in many different routes." + } + p { + "[ " + code { "Outlet" } + "]" + " tells the router where to render content in layouts. In the following example," + " " + "the Index will be rendered within the " + "[ " + code { "Outlet" } + "]" + " " + "." + } + p { + "This page is built with the Dioxus. It uses Layouts in several different places. Here is an outline of how layouts are used on the current page. Hover over different layouts to see what elements they are on the page." + } + LayoutsExplanation {} + p { "Here is a more complete example of a layout wrapping the body of a page." } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    #[layout(Wrapper)]\n        #[route("/")]\n        Index {{}},\n}}\n\n#[component]\nfn Wrapper(cx: Scope) -> Element {{\n    render! {{\n        header {{ "header" }}\n        // The index route will be rendered here\n        Outlet::<Route> {{ }}\n        footer {{ "footer" }}\n    }}\n}}\n\n#[component]\nfn Index(cx: Scope) -> Element {{\n    render! {{\n        h1 {{ "Index" }}\n    }}\n}}
\n", + name: "outlet.rs".to_string(), + } + p { + "The example above will output the following HTML (line breaks added for" + " " + "readability):" + } + CodeBlock { + contents: "
\n<header>header</header>\n<h1>Index</h1>\n<footer>footer</footer>
\n", + } + h2 { id: "layouts-with-dynamic-segments", + Link { + to: BookRoute::RouterReferenceLayouts { + section: RouterReferenceLayoutsSection::LayoutsWithDynamicSegments, + }, + class: "header", + "Layouts with dynamic segments" + } + } + p { + "You can combine layouts with " + Link { + to: BookRoute::RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection::Empty, + }, + "nested routes" + } + " to create dynamic layouts with content that changes based on the current route." + } + p { + "Just like routes, layouts components must accept a prop for each dynamic segment in the route. For example, if you have a route with a dynamic segment like " + code { "/:name" } + ", your layout component must accept a " + code { "name" } + " prop:" + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    #[nest("/:name")]\n        #[layout(Wrapper)]\n            #[route("/")]\n            Index {{\n                name: String,\n            }},\n}}\n\n#[component]\nfn Wrapper(cx: Scope, name: String) -> Element {{\n    render! {{\n        header {{ "Welcome {{name}}!" }}\n        // The index route will be rendered here\n        Outlet::<Route> {{ }}\n        footer {{ "footer" }}\n    }}\n}}\n\n#[component]\nfn Index(cx: Scope, name: String) -> Element {{\n    render! {{\n        h1 {{ "This is a homepage for {{name}}" }}\n    }}\n}}
\n", + name: "outlet.rs".to_string(), + } + p { + "Or to get the full route, you can use the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_route.html", + code { "use_route" } + } + " hook." + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    #[layout(Wrapper)]\n        #[route("/:name")]\n        Index {{\n            name: String,\n        }},\n}}\n\n#[component]\nfn Wrapper(cx: Scope) -> Element {{\n    let full_route = use_route::<Route>(cx).unwrap();\n    render! {{\n        header {{ "Welcome to {{full_route}}!" }}\n        // The index route will be rendered here\n        Outlet::<Route> {{ }}\n        footer {{ "footer" }}\n    }}\n}}\n\n#[component]\nfn Index(cx: Scope, name: String) -> Element {{\n    render! {{\n        h1 {{ "This is a homepage for {{name}}" }}\n    }}\n}}
\n", + name: "outlet.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceNavigationIndexSection { + #[default] + Empty, + LinksNavigation, +} +impl std::str::FromStr for RouterReferenceNavigationIndexSection { + type Err = RouterReferenceNavigationIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "links--navigation" => Ok(Self::LinksNavigation), + _ => Err(RouterReferenceNavigationIndexSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceNavigationIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::LinksNavigation => f.write_str("links--navigation"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceNavigationIndexSectionParseError; +impl std::fmt::Display for RouterReferenceNavigationIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceNavigationIndexSectionlinks--navigation", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceNavigationIndexSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceNavigationIndex( + section: RouterReferenceNavigationIndexSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "links--navigation", + Link { + to: BookRoute::RouterReferenceNavigationIndex { + section: RouterReferenceNavigationIndexSection::LinksNavigation, + }, + class: "header", + "Links & Navigation" + } + } + p { + "When we split our app into pages, we need to provide our users with a way to" + " " + "navigate between them. On regular web pages, we'd use an anchor element for that," + " " + "like this:" + } + CodeBlock { contents: "
\n<a href="/other">Link to an other page</a>
\n" } + p { "However, we cannot do that when using the router for three reasons:" } + ol { + li { + "Anchor tags make the browser load a new page from the server. This takes a" + " " + "lot of time, and it is much faster to let the router handle the navigation" + " " + "client-side." + } + li { + "Navigation using anchor tags only works when the app is running inside a" + " " + "browser. This means we cannot use them inside apps using Dioxus Desktop." + } + li { + "Anchor tags cannot check if the target page exists. This means we cannot" + " " + "prevent accidentally linking to non-existent pages." + } + } + p { + "To solve these problems, the router provides us with a " + "[ " + code { "Link" } + "]" + " component we can" + " " + "use like this:" + } + CodeBlock { + contents: "
\n#[component]\nfn NavBar(cx: Scope) -> Element {{\n    render! {{\n        nav {{\n            ul {{\n                li {{\n                    Link {{\n                        // The Link component will navigate to the route specified\n                        // in the target prop which is checked to exist at compile time\n                        to: Route::Home {{}},\n                        "Home"\n                    }}\n                }}\n            }}\n        }}\n        Outlet::<Route> {{}}\n    }}\n}}
\n", + name: "links.rs".to_string(), + } + p { + "The " + code { "target" } + " in the example above is similar to the " + code { "href" } + " of a regular anchor" + " " + "element. However, it tells the router more about what kind of navigation it" + " " + "should perform. It accepts something that can be converted into a" + " " + "[ " + code { "NavigationTarget" } + "]" + " " + ":" + } + ul { + li { + "The example uses a Internal route. This is the most common type of navigation." + " " + "It tells the router to navigate to a page within our app by passing a variant of a " + "[" + code { "Routable" } + "]" + " enum. This type of navigation can never fail if the link component is used inside a router component." + } + li { + "[" + code { "External" } + "]" + " allows us to navigate to URLs outside of our app. This is useful" + " " + "for links to external websites. NavigationTarget::External accepts an URL to navigate to. This type of navigation can fail if the URL is invalid." + } + } + blockquote { + p { + "The " + "[ " + code { "Link" } + "]" + " accepts several props that modify its behavior. See the API docs" + " " + "for more details." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceNavigationProgrammaticSection { + #[default] + Empty, + ProgrammaticNavigation, + UsingANavigator, + ExternalNavigationTargets, +} +impl std::str::FromStr for RouterReferenceNavigationProgrammaticSection { + type Err = RouterReferenceNavigationProgrammaticSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "programmatic-navigation" => Ok(Self::ProgrammaticNavigation), + "using-a-navigator" => Ok(Self::UsingANavigator), + "external-navigation-targets" => Ok(Self::ExternalNavigationTargets), + _ => Err(RouterReferenceNavigationProgrammaticSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceNavigationProgrammaticSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ProgrammaticNavigation => f.write_str("programmatic-navigation"), + Self::UsingANavigator => f.write_str("using-a-navigator"), + Self::ExternalNavigationTargets => f.write_str("external-navigation-targets"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceNavigationProgrammaticSectionParseError; +impl std::fmt::Display for RouterReferenceNavigationProgrammaticSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceNavigationProgrammaticSectionprogrammatic-navigation, using-a-navigator, external-navigation-targets", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceNavigationProgrammaticSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceNavigationProgrammatic( + section: RouterReferenceNavigationProgrammaticSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "programmatic-navigation", + Link { + to: BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::ProgrammaticNavigation, + }, + class: "header", + "Programmatic Navigation" + } + } + p { + "Sometimes we want our application to navigate to another page without having the" + " " + "user click on a link. This is called programmatic navigation." + } + h2 { id: "using-a-navigator", + Link { + to: BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::UsingANavigator, + }, + class: "header", + "Using a Navigator" + } + } + p { + "We can get a navigator with the " + "[ " + code { "use_navigator" } + "]" + " hook. This hook returns a " + "[ " + code { "Navigator" } + "]" + " " + "." + } + p { + "We can use the " + "[ " + code { "Navigator" } + "]" + " to trigger four different kinds of navigation:" + } + ul { + li { + code { "push" } + " will navigate to the target. It works like a regular anchor tag." + } + li { + code { "replace" } + " works like " + code { "push" } + ", except that it replaces the current history entry" + " " + "instead of adding a new one. This means the prior page cannot be restored with the browser's back button." + } + li { + code { "Go back" } + " works like the browser's back button." + } + li { + code { "Go forward" } + " works like the browser's forward button." + } + } + CodeBlock { + contents: "
\n#[component]\nfn Home(cx: Scope) -> Element {{\n    let nav = use_navigator(cx);\n\n    // push\n    nav.push(Route::PageNotFound {{ route: vec![] }});\n\n    // replace\n    nav.replace(Route::Home {{}});\n\n    // go back\n    nav.go_back();\n\n    // go forward\n    nav.go_forward();\n\n    render! {{\n        h1 {{ "Welcome to the Dioxus Blog!" }}\n    }}\n}}
\n", + name: "navigator.rs".to_string(), + } + p { + "You might have noticed that, like " + "[ " + code { "Link" } + "]" + " " + ", the " + "[ " + code { "Navigator" } + "]" + " " + "s " + code { "push" } + " and" + code { "replace" } + " functions take a " + "[ " + code { "NavigationTarget" } + "]" + " " + ". This means we can use either" + " " + "[ " + code { "Internal" } + "]" + " " + ", or " + "[ " + code { "External" } + "]" + " targets." + } + h2 { id: "external-navigation-targets", + Link { + to: BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::ExternalNavigationTargets, + }, + class: "header", + "External Navigation Targets" + } + } + p { + "Unlike a " + "[ " + code { "Link" } + "]" + " " + ", the " + "[ " + code { "Navigator" } + "]" + " cannot rely on the browser (or webview) to" + " " + "handle navigation to external targets via a generated anchor element." + } + p { + "This means, that under certain conditions, navigation to external targets can" + " " + "fail." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceHistoryProvidersSection { + #[default] + Empty, + HistoryProviders, +} +impl std::str::FromStr for RouterReferenceHistoryProvidersSection { + type Err = RouterReferenceHistoryProvidersSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "history-providers" => Ok(Self::HistoryProviders), + _ => Err(RouterReferenceHistoryProvidersSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceHistoryProvidersSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::HistoryProviders => f.write_str("history-providers"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceHistoryProvidersSectionParseError; +impl std::fmt::Display for RouterReferenceHistoryProvidersSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceHistoryProvidersSectionhistory-providers", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceHistoryProvidersSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceHistoryProviders( + section: RouterReferenceHistoryProvidersSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "history-providers", + Link { + to: BookRoute::RouterReferenceHistoryProviders { + section: RouterReferenceHistoryProvidersSection::HistoryProviders, + }, + class: "header", + "History Providers" + } + } + p { + "[ " + code { "HistoryProvider" } + "]" + " " + "s are used by the router to keep track of the navigation history" + " " + "and update any external state (e.g. the browser's URL)." + } + p { + "The router provides two " + "[ " + code { "HistoryProvider" } + "]" + " " + "s, but you can also create your own." + " " + "The two default implementations are:" + } + ul { + li { + "The " + "[" + code { "MemoryHistory" } + "]" + " is a custom implementation that works in memory." + } + li { + "The " + "[" + code { "WebHistory" } + "]" + " integrates with the browser's URL." + } + } + p { + "By default, the router uses the " + "[ " + code { "MemoryHistory" } + "]" + " " + ". It might be changed to use" + " " + "[ " + code { "WebHistory" } + "]" + " when the " + code { "web" } + " feature is active, but that is not guaranteed." + } + p { "You can override the default history:" } + CodeBlock { + contents: "
\n#[component]\nfn App(cx: Scope) -> Element {{\n    render! {{\n        Router::<Route> {{\n            config: || RouterConfig::default().history(WebHistory::default())\n        }}\n    }}\n}}
\n", + name: "history_provider.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceHistoryButtonsSection { + #[default] + Empty, + HistoryButtons, +} +impl std::str::FromStr for RouterReferenceHistoryButtonsSection { + type Err = RouterReferenceHistoryButtonsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "history-buttons" => Ok(Self::HistoryButtons), + _ => Err(RouterReferenceHistoryButtonsSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceHistoryButtonsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::HistoryButtons => f.write_str("history-buttons"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceHistoryButtonsSectionParseError; +impl std::fmt::Display for RouterReferenceHistoryButtonsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceHistoryButtonsSectionhistory-buttons", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceHistoryButtonsSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceHistoryButtons( + section: RouterReferenceHistoryButtonsSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "history-buttons", + Link { + to: BookRoute::RouterReferenceHistoryButtons { + section: RouterReferenceHistoryButtonsSection::HistoryButtons, + }, + class: "header", + "History Buttons" + } + } + p { + "Some platforms, like web browsers, provide users with an easy way to navigate" + " " + "through an app's history. They have UI elements or integrate with the OS." + } + p { + "However, native platforms usually don't provide such amenities, which means that" + " " + "apps wanting users to have access to them, need to implement them. For this" + " " + "reason, the router comes with two components, which emulate a browser's back and" + " " + "forward buttons:" + } + ul { + li { + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoBackButton.html", + code { "GoBackButton" } + } + } + li { + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoForwardButton.html", + code { "GoForwardButton" } + } + } + } + blockquote { + p { + "If you want to navigate through the history programmatically, take a look at" + Link { + to: BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::Empty, + }, + code { "programmatic navigation" } + } + "." + } + } + CodeBlock { + contents: "
\nfn HistoryNavigation(cx: Scope) -> Element {{\n    render! {{\n        GoBackButton {{\n            "Back to the Past"\n        }}\n        GoForwardButton {{\n            "Back to the Future" /* You see what I did there? 😉 */\n        }}\n    }}\n}}
\n", + name: "history_buttons.rs".to_string(), + } + p { + "As you might know, browsers usually disable the back and forward buttons if" + " " + "there is no history to navigate to. The router's history buttons try to do that" + " " + "too, but depending on the " + "[" + " " + "history provider" + " " + "]" + " that might not be possible." + } + p { + "Importantly, neither " + "[ " + code { "WebHistory" } + "]" + " supports that feature." + " " + "This is due to limitations of the browser History API." + } + p { + "However, in both cases, the router will just ignore button presses, if there is" + " " + "no history to navigate to." + } + p { + "Also, when using " + "[ " + code { "WebHistory" } + "]" + " " + ", the history buttons might" + " " + "navigate a user to a history entry outside your app." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceStaticGenerationSection { + #[default] + Empty, + StaticGeneration, + GettingTheSitemap, + GeneratingASitemap, + Example, +} +impl std::str::FromStr for RouterReferenceStaticGenerationSection { + type Err = RouterReferenceStaticGenerationSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "static-generation" => Ok(Self::StaticGeneration), + "getting-the-sitemap" => Ok(Self::GettingTheSitemap), + "generating-a-sitemap" => Ok(Self::GeneratingASitemap), + "example" => Ok(Self::Example), + _ => Err(RouterReferenceStaticGenerationSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceStaticGenerationSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::StaticGeneration => f.write_str("static-generation"), + Self::GettingTheSitemap => f.write_str("getting-the-sitemap"), + Self::GeneratingASitemap => f.write_str("generating-a-sitemap"), + Self::Example => f.write_str("example"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceStaticGenerationSectionParseError; +impl std::fmt::Display for RouterReferenceStaticGenerationSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceStaticGenerationSectionstatic-generation, getting-the-sitemap, generating-a-sitemap, example", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceStaticGenerationSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceStaticGeneration( + section: RouterReferenceStaticGenerationSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "static-generation", + Link { + to: BookRoute::RouterReferenceStaticGeneration { + section: RouterReferenceStaticGenerationSection::StaticGeneration, + }, + class: "header", + "Static Generation" + } + } + h2 { id: "getting-the-sitemap", + Link { + to: BookRoute::RouterReferenceStaticGeneration { + section: RouterReferenceStaticGenerationSection::GettingTheSitemap, + }, + class: "header", + "Getting the Sitemap" + } + } + p { + "The " + "[ " + code { "Routable" } + "]" + " trait includes an associated " + "[ " + code { "SITE_MAP" } + "]" + " constant that contains the map of all of the routes in the enum." + } + p { + "By default, the sitemap is a tree of (static or dynamic) RouteTypes, but it can be flattened into a list of individual routes with the " + code { ".flatten()" } + " method." + } + h2 { id: "generating-a-sitemap", + Link { + to: BookRoute::RouterReferenceStaticGeneration { + section: RouterReferenceStaticGenerationSection::GeneratingASitemap, + }, + class: "header", + "Generating a Sitemap" + } + } + p { + "To statically render pages, we need to flatten the route tree and generate a file for each route that contains only static segments:" + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\nuse dioxus_ssr::incremental::{{DefaultRenderer, IncrementalRendererConfig}};\n\n#[tokio::main]\nasync fn main() {{\n    let mut renderer = IncrementalRendererConfig::new()\n        .static_dir("./static")\n        .build();\n\n    println!(\n        "SITE MAP:\\n{{}}",\n        Route::SITE_MAP\n            .iter()\n            .flat_map(|route| route.flatten().into_iter())\n            .map(|route| {{\n                route\n                    .iter()\n                    .map(|segment| segment.to_string())\n                    .collect::<Vec<_>>()\n                    .join("")\n            }})\n            .collect::<Vec<_>>()\n            .join("\\n")\n    );\n\n    pre_cache_static_routes::<Route, _>(\n        &mut renderer,\n        &DefaultRenderer {{\n            before_body: r#"<!DOCTYPE html>\n        <html lang="en">\n        <head>\n            <meta charset="UTF-8">\n            <meta name="viewport" content="width=device-width,\n            initial-scale=1.0">\n            <title>Dioxus Application</title>\n        </head>\n        <body>"#\n                .to_string(),\n            after_body: r#"</body>\n        </html>"#\n                .to_string(),\n        }},\n    )\n    .await\n    .unwrap();\n}}\n\n#[component]\nfn Blog(cx: Scope) -> Element {{\n    render! {{\n        div {{\n            "Blog"\n        }}\n    }}\n}}\n\n#[component]\nfn Post(cx: Scope, id: usize) -> Element {{\n    render! {{\n        div {{\n            "PostId: {{id}}"\n        }}\n    }}\n}}\n\n#[component]\nfn PostHome(cx: Scope) -> Element {{\n    render! {{\n        div {{\n            "Post"\n        }}\n    }}\n}}\n\n#[component]\nfn Home(cx: Scope) -> Element {{\n    render! {{\n        div {{\n            "Home"\n        }}\n    }}\n}}\n\n#[rustfmt::skip]\n#[derive(Clone, Debug, PartialEq, Routable)]\nenum Route {{\n    #[nest("/blog")]\n        #[route("/")]\n        Blog {{}},\n        #[route("/post/index")]\n        PostHome {{}},\n        #[route("/post/:id")]\n        Post {{\n            id: usize,\n        }},\n    #[end_nest]\n    #[route("/")]\n    Home {{}},\n}}
\n", + name: "static_generation.rs".to_string(), + } + h2 { id: "example", + Link { + to: BookRoute::RouterReferenceStaticGeneration { + section: RouterReferenceStaticGenerationSection::Example, + }, + class: "header", + "Example" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages%2Ffullstack%2Fexamples%2Fstatic-hydrated", + "examples/static-hydrated" + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceRoutingUpdateCallbackSection { + #[default] + Empty, + RoutingUpdateCallback, + HowDoesTheCallbackBehave, + CodeExample, +} +impl std::str::FromStr for RouterReferenceRoutingUpdateCallbackSection { + type Err = RouterReferenceRoutingUpdateCallbackSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "routing-update-callback" => Ok(Self::RoutingUpdateCallback), + "how-does-the-callback-behave" => Ok(Self::HowDoesTheCallbackBehave), + "code-example" => Ok(Self::CodeExample), + _ => Err(RouterReferenceRoutingUpdateCallbackSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceRoutingUpdateCallbackSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::RoutingUpdateCallback => f.write_str("routing-update-callback"), + Self::HowDoesTheCallbackBehave => f.write_str("how-does-the-callback-behave"), + Self::CodeExample => f.write_str("code-example"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceRoutingUpdateCallbackSectionParseError; +impl std::fmt::Display for RouterReferenceRoutingUpdateCallbackSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceRoutingUpdateCallbackSectionrouting-update-callback, how-does-the-callback-behave, code-example", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceRoutingUpdateCallbackSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceRoutingUpdateCallback( + section: RouterReferenceRoutingUpdateCallbackSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "routing-update-callback", + Link { + to: BookRoute::RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection::RoutingUpdateCallback, + }, + class: "header", + "Routing Update Callback" + } + } + p { + "In some cases, we might want to run custom code when the current route changes. For this reason, the " + "[ " + code { "RouterConfig" } + "]" + " exposes an " + code { "on_update" } + " field." + } + h2 { id: "how-does-the-callback-behave", + Link { + to: BookRoute::RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection::HowDoesTheCallbackBehave, + }, + class: "header", + "How does the callback behave?" + } + } + p { + "The " + code { "on_update" } + " is called whenever the current routing information changes. It is called after the router updated its internal state, but before dependent components and hooks are updated." + } + p { + "If the callback returns a " + "[ " + code { "NavigationTarget" } + "]" + " " + ", the router will replace the current location with the specified target. It will not call the " + code { "on_update" } + " again." + } + p { + "If at any point the router encounters a navigation failure, it will go to the appropriate state without calling the " + code { "on_update" } + ". It doesn't matter if the invalid target initiated the navigation, was found as a redirect target, or was returned by the " + code { "on_update" } + " itself." + } + h2 { id: "code-example", + Link { + to: BookRoute::RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection::CodeExample, + }, + class: "header", + "Code Example" + } + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone, PartialEq)]\nenum Route {{\n    #[route("/")]\n    Index {{}},\n    #[route("/home")]\n    Home {{}},\n}}\n\n#[component]\nfn Home(cx: Scope) -> Element {{\n    render! {{\n        p {{ "Home" }}\n    }}\n}}\n\n#[component]\nfn Index(cx: Scope) -> Element {{\n    render! {{\n        p {{ "Index" }}\n    }}\n}}\n\nfn app(cx: Scope) -> Element {{\n    render! {{\n        Router::<Route> {{\n            config: || RouterConfig::default().on_update(|state|{{\n                (state.current() == Route::Index {{}}).then_some(\n                    NavigationTarget::Internal(Route::Home {{}})\n                )\n            }})\n        }}\n    }}\n}}
\n", + name: "routing_update.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookIndexSection { + #[default] + Empty, + Cookbook, +} +impl std::str::FromStr for CookbookIndexSection { + type Err = CookbookIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "cookbook" => Ok(Self::Cookbook), + _ => Err(CookbookIndexSectionParseError), + } + } +} +impl std::fmt::Display for CookbookIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Cookbook => f.write_str("cookbook"), + } + } +} +#[derive(Debug)] +pub struct CookbookIndexSectionParseError; +impl std::fmt::Display for CookbookIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of CookbookIndexSectioncookbook")?; + Ok(()) + } +} +impl std::error::Error for CookbookIndexSectionParseError {} +#[component(no_case_check)] +pub fn CookbookIndex(section: CookbookIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "cookbook", + Link { + to: BookRoute::CookbookIndex { + section: CookbookIndexSection::Cookbook, + }, + class: "header", + "Cookbook" + } + } + p { "The cookbook contains common recipes for different patterns within Dioxus." } + p { "There are a few different sections in the cookbook:" } + ul { + li { + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::Empty, + }, + "Publishing" + } + " will teach you how to present your app in a variety of delicious forms." + } + li { + "Explore the " + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::Empty, + }, + "Anti-patterns" + } + " section to discover what ingredients to avoid when preparing your application." + } + li { + "Within " + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::Empty, + }, + "Error Handling" + } + ", we'll master the fine art of managing spoiled ingredients in Dioxus." + } + li { + "Take a culinary journey through " + Link { + to: BookRoute::CookbookStateIndex { + section: CookbookStateIndexSection::Empty, + }, + "State management" + } + ", where we'll explore the world of handling local, global, and external state in Dioxus." + } + li { + Link { + to: BookRoute::CookbookIntegrationsIndex { + section: CookbookIntegrationsIndexSection::Empty, + }, + "Integrations" + } + " will guide you how to seamlessly blend external libraries into your Dioxus culinary creations." + } + li { + Link { + to: BookRoute::CookbookTesting { + section: CookbookTestingSection::Empty, + }, + "Testing" + } + " explains how to examine the unique flavor of Dioxus-specific features, like components." + } + li { + Link { + to: BookRoute::CookbookExamples { + section: CookbookExamplesSection::Empty, + }, + "Examples" + } + " is a curated list of delightful recipes that demonstrate the various ways of using Dioxus ingredients." + } + li { + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Empty, + }, + "Tailwind" + } + " reveals the secrets of combining your Tailwind and Dioxus ingredients into a complete meal. You will also learn about using other NPM ingredients (packages) with Dioxus." + } + li { + "In the " + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Empty, + }, + "Custom Renderer" + } + " section, we embark on a cooking adventure, inventing new ways to cook with Dioxus!" + } + li { + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Empty, + }, + "Optimizing" + } + " will show you how to maximize the quality of your ingredients." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookPublishingSection { + #[default] + Empty, + Publishing, + WebPublishingWithGithubPages, + DesktopCreatingAnInstaller, + PreparingYourApplicationForBundling, + InstallDioxusCli, + Building, +} +impl std::str::FromStr for CookbookPublishingSection { + type Err = CookbookPublishingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "publishing" => Ok(Self::Publishing), + "web-publishing-with-github-pages" => Ok(Self::WebPublishingWithGithubPages), + "desktop-creating-an-installer" => Ok(Self::DesktopCreatingAnInstaller), + "preparing-your-application-for-bundling" => { + Ok(Self::PreparingYourApplicationForBundling) + } + "install-dioxus-cli" => Ok(Self::InstallDioxusCli), + "building" => Ok(Self::Building), + _ => Err(CookbookPublishingSectionParseError), + } + } +} +impl std::fmt::Display for CookbookPublishingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Publishing => f.write_str("publishing"), + Self::WebPublishingWithGithubPages => f.write_str("web-publishing-with-github-pages"), + Self::DesktopCreatingAnInstaller => f.write_str("desktop-creating-an-installer"), + Self::PreparingYourApplicationForBundling => { + f.write_str("preparing-your-application-for-bundling") + } + Self::InstallDioxusCli => f.write_str("install-dioxus-cli"), + Self::Building => f.write_str("building"), + } + } +} +#[derive(Debug)] +pub struct CookbookPublishingSectionParseError; +impl std::fmt::Display for CookbookPublishingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookPublishingSectionpublishing, web-publishing-with-github-pages, desktop-creating-an-installer, preparing-your-application-for-bundling, install-dioxus-cli, building", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookPublishingSectionParseError {} +#[component(no_case_check)] +pub fn CookbookPublishing(section: CookbookPublishingSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "publishing", + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::Publishing, + }, + class: "header", + "Publishing" + } + } + p { + "After you have build your application, you will need to publish it somewhere. This reference will outline different methods of publishing your desktop or web application." + } + h2 { id: "web-publishing-with-github-pages", + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::WebPublishingWithGithubPages, + }, + class: "header", + "Web: Publishing with GitHub Pages" + } + } + p { + "Edit your " + code { "Dioxus.toml" } + " to point your " + code { "out_dir" } + " to the " + code { "docs" } + " folder and the " + code { "base_path" } + " to the name of your repo:" + } + CodeBlock { contents: "
\n[application]\n# ...\nout_dir = "docs"\n\n[web.app]\nbase_path = "your_repo"
\n" } + p { "Then build your app and publish it to Github:" } + ul { + li { + "Make sure GitHub Pages is set up for your repo to publish any static files in the docs directory" + } + li { "Build your app with:" } + } + CodeBlock { contents: "
\ndx build --release
\n" } + ul { + li { "Add and commit with git" } + li { "Push to GitHub" } + } + h2 { id: "desktop-creating-an-installer", + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::DesktopCreatingAnInstaller, + }, + class: "header", + "Desktop: Creating an installer" + } + } + p { + "Dioxus desktop app uses your operating system's WebView library, so it's portable to be distributed for other platforms." + } + p { "In this section, we'll cover how to bundle your app for macOS, Windows, and Linux." } + h2 { id: "preparing-your-application-for-bundling", + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::PreparingYourApplicationForBundling, + }, + class: "header", + "Preparing your application for bundling" + } + } + p { + "Depending on your platform, you may need to add some additional code to your " + code { "main.rs" } + " file to make sure your app is ready for bundling. On Windows, you'll need to add the " + code { "#![windows_subsystem = \"windows\"]" } + " attribute to your " + code { "main.rs" } + " file to hide the terminal window that pops up when you run your app. " + strong { "If you're developing on Windows, only use this when bundling." } + " It will disable the terminal, so you will not get logs of any kind. You can gate it behind a feature, like so:" + } + CodeBlock { contents: "
\n# Cargo.toml\n[features]\nbundle = []
\n" } + p { + "And then your " + code { "main.rs" } + ":" + } + CodeBlock { contents: "
\n#![cfg_attr(feature = "bundle", windows_subsystem = "windows")]
\n" } + h2 { id: "install-dioxus-cli", + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::InstallDioxusCli, + }, + class: "header", + "Install dioxus CLI" + } + } + p { + "The first thing we'll do is install the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/cli", + "dioxus-cli" + } + ". This extension to cargo will make it very easy to package our app for the various platforms." + } + p { "To install, simply run" } + p { + code { "cargo install dioxus-cli" } + } + h2 { id: "building", + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::Building, + }, + class: "header", + "Building" + } + } + p { + "To bundle your application you can simply run " + code { "dx bundle --release" } + " (also add " + code { "--features bundle" } + " if you're using that, see the " + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::PreparingYourApplicationForBundling, + }, + "this" + } + " for more) to produce a final app with all the optimizations and assets builtin." + } + p { + "Once you've ran the command, your app should be accessible in " + code { "dist/bundle/" } + "." + } + p { "For example, a macOS app would look like this:" } + p { + img { + src: asset!("/assets/static/publish.png", ImageAssetOptions::new().with_webp()), + alt: "Published App", + title: "", + } + } + p { + "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." + } + blockquote { + p { + "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." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookAntipatternsSection { + #[default] + Empty, + Antipatterns, + UnnecessarilyNestedFragments, + IncorrectIteratorKeys, + AvoidInteriorMutabilityInProps, + AvoidUpdatingStateDuringRender, +} +impl std::str::FromStr for CookbookAntipatternsSection { + type Err = CookbookAntipatternsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "antipatterns" => Ok(Self::Antipatterns), + "unnecessarily-nested-fragments" => Ok(Self::UnnecessarilyNestedFragments), + "incorrect-iterator-keys" => Ok(Self::IncorrectIteratorKeys), + "avoid-interior-mutability-in-props" => Ok(Self::AvoidInteriorMutabilityInProps), + "avoid-updating-state-during-render" => Ok(Self::AvoidUpdatingStateDuringRender), + _ => Err(CookbookAntipatternsSectionParseError), + } + } +} +impl std::fmt::Display for CookbookAntipatternsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Antipatterns => f.write_str("antipatterns"), + Self::UnnecessarilyNestedFragments => f.write_str("unnecessarily-nested-fragments"), + Self::IncorrectIteratorKeys => f.write_str("incorrect-iterator-keys"), + Self::AvoidInteriorMutabilityInProps => { + f.write_str("avoid-interior-mutability-in-props") + } + Self::AvoidUpdatingStateDuringRender => { + f.write_str("avoid-updating-state-during-render") + } + } + } +} +#[derive(Debug)] +pub struct CookbookAntipatternsSectionParseError; +impl std::fmt::Display for CookbookAntipatternsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookAntipatternsSectionantipatterns, unnecessarily-nested-fragments, incorrect-iterator-keys, avoid-interior-mutability-in-props, avoid-updating-state-during-render", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookAntipatternsSectionParseError {} +#[component(no_case_check)] +pub fn CookbookAntipatterns(section: CookbookAntipatternsSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "antipatterns", + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::Antipatterns, + }, + class: "header", + "Antipatterns" + } + } + p { + "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 for performance or code re-usability reasons." + } + h2 { id: "unnecessarily-nested-fragments", + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::UnnecessarilyNestedFragments, + }, + class: "header", + "Unnecessarily Nested Fragments" + } + } + p { + "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." + } + p { + "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." + } + CodeBlock { + contents: "
\n// ❌ Don't unnecessarily nest fragments\nlet _ = cx.render(rsx!(\n    Fragment {{\n        Fragment {{\n            Fragment {{\n                Fragment {{\n                    Fragment {{\n                        div {{ "Finally have a real node!" }}\n                    }}\n                }}\n            }}\n        }}\n    }}\n));\n\n// ✅ Render shallow structures\ncx.render(rsx!(\n    div {{ "Finally have a real node!" }}\n))
\n", + name: "anti_patterns.rs".to_string(), + } + h2 { id: "incorrect-iterator-keys", + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::IncorrectIteratorKeys, + }, + class: "header", + "Incorrect Iterator Keys" + } + } + p { + "As described in the " + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::TheKeyAttribute, + }, + "dynamic 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 will never change." + } + CodeBlock { + contents: "
\nlet data: &HashMap<_, _> = &cx.props.data;\n\n// ❌ No keys\ncx.render(rsx! {{\n    ul {{\n        data.values().map(|value| rsx!(\n            li {{ "List item: {{value}}" }}\n        ))\n    }}\n}});\n\n// ❌ Using index as keys\ncx.render(rsx! {{\n    ul {{\n        cx.props.data.values().enumerate().map(|(index, value)| rsx!(\n            li {{ key: "{{index}}", "List item: {{value}}" }}\n        ))\n    }}\n}});\n\n// ✅ Using unique IDs as keys:\ncx.render(rsx! {{\n    ul {{\n        cx.props.data.iter().map(|(key, value)| rsx!(\n            li {{ key: "{{key}}", "List item: {{value}}" }}\n        ))\n    }}\n}})
\n", + name: "anti_patterns.rs".to_string(), + } + h2 { id: "avoid-interior-mutability-in-props", + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::AvoidInteriorMutabilityInProps, + }, + class: "header", + "Avoid Interior Mutability in Props" + } + } + p { + "While it is technically acceptable to have a " + code { "Mutex" } + " or a " + code { "RwLock" } + " in the props, they will be difficult to use." + } + p { + "Suppose you have a struct " + code { "User" } + " containing the field " + code { "username: String" } + ". If you pass a " + code { "Mutex" } + " prop to a " + code { "UserComponent" } + " component, that component may wish to pass the username as a " + code { "&str" } + " prop to a child component. However, it cannot pass that borrowed field down, since it only would live as long as the " + code { "Mutex" } + "'s lock, which belongs to the " + code { "UserComponent" } + " function. Therefore, the component will be forced to clone the " + code { "username" } + " field." + } + h2 { id: "avoid-updating-state-during-render", + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::AvoidUpdatingStateDuringRender, + }, + class: "header", + "Avoid Updating State During Render" + } + } + p { + "Every time you update the state, Dioxus needs to re-render the component – this is inefficient! Consider refactoring your code to avoid this." + } + p { + "Also, if you unconditionally update the state during render, it will be re-rendered in an infinite loop." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookErrorHandlingSection { + #[default] + Empty, + ErrorHandling, + TheSimplestReturningNone, + EarlyReturnOnResult, + MatchResults, + PassingErrorStatesThroughComponents, + GoingGlobal, +} +impl std::str::FromStr for CookbookErrorHandlingSection { + type Err = CookbookErrorHandlingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "error-handling" => Ok(Self::ErrorHandling), + "the-simplest--returning-none" => Ok(Self::TheSimplestReturningNone), + "early-return-on-result" => Ok(Self::EarlyReturnOnResult), + "match-results" => Ok(Self::MatchResults), + "passing-error-states-through-components" => { + Ok(Self::PassingErrorStatesThroughComponents) + } + "going-global" => Ok(Self::GoingGlobal), + _ => Err(CookbookErrorHandlingSectionParseError), + } + } +} +impl std::fmt::Display for CookbookErrorHandlingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ErrorHandling => f.write_str("error-handling"), + Self::TheSimplestReturningNone => f.write_str("the-simplest--returning-none"), + Self::EarlyReturnOnResult => f.write_str("early-return-on-result"), + Self::MatchResults => f.write_str("match-results"), + Self::PassingErrorStatesThroughComponents => { + f.write_str("passing-error-states-through-components") + } + Self::GoingGlobal => f.write_str("going-global"), + } + } +} +#[derive(Debug)] +pub struct CookbookErrorHandlingSectionParseError; +impl std::fmt::Display for CookbookErrorHandlingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookErrorHandlingSectionerror-handling, the-simplest--returning-none, early-return-on-result, match-results, passing-error-states-through-components, going-global", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookErrorHandlingSectionParseError {} +#[component(no_case_check)] +pub fn CookbookErrorHandling(section: CookbookErrorHandlingSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "error-handling", + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::ErrorHandling, + }, + class: "header", + "Error handling" + } + } + p { + "A selling point of Rust for web development is the reliability of always knowing where errors can occur and being forced to handle them" + } + p { + "However, we haven't talked about error handling at all in this guide! In this chapter, we'll cover some strategies in handling errors to ensure your app never crashes." + } + h2 { id: "the-simplest--returning-none", + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::TheSimplestReturningNone, + }, + class: "header", + "The simplest – returning None" + } + } + p { + "Astute observers might have noticed that " + code { "Element" } + " is actually a type alias for " + code { "Option" } + ". You don't need to know what a " + code { "VNode" } + " is, but it's important to recognize that we could actually return nothing at all:" + } + CodeBlock { contents: "
\nfn App(cx: Scope) -> Element {{\n\tNone\n}}
\n" } + p { + "This lets us add in some syntactic sugar for operations we think " + em { "shouldn't" } + " fail, but we're still not confident enough to \"unwrap\" on." + } + blockquote { + p { + "The nature of " + code { "Option" } + " might change in the future as the " + code { "try" } + " trait gets upgraded." + } + } + CodeBlock { + contents: "
\nfn App(cx: Scope) -> Element {{\n\t// immediately return "None"\n\tlet name = cx.use_hook(|_| Some("hi"))?;\n}}
\n", + } + h2 { id: "early-return-on-result", + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::EarlyReturnOnResult, + }, + class: "header", + "Early return on result" + } + } + p { + "Because Rust can't accept both Options and Results with the existing try infrastructure, you'll need to manually handle Results. This can be done by converting them into Options or by explicitly handling them." + } + CodeBlock { + contents: "
\nfn App(cx: Scope) -> Element {{\n\t// Convert Result to Option\n\tlet name = cx.use_hook(|_| "1.234").parse().ok()?;\n\n\n\t// Early return\n\tlet count = cx.use_hook(|_| "1.234");\n\tlet val = match count.parse() {{\n\t\tOk(val) => val\n\t\tErr(err) => return cx.render(rsx!{{ "Parsing failed" }})\n\t}};\n}}
\n", + } + p { + "Notice that while hooks in Dioxus do not like being called in conditionals or loops, they " + em { "are" } + " okay with early returns. Returning an error state early is a completely valid way of handling errors." + } + h2 { id: "match-results", + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::MatchResults, + }, + class: "header", + "Match results" + } + } + p { + "The next \"best\" way of handling errors in Dioxus is to match on the error locally. This is the most robust way of handling errors, though it doesn't scale to architectures beyond a single component." + } + p { "To do this, we simply have an error state built into our component:" } + CodeBlock { contents: "
\nlet err = use_state(cx, || None);
\n" } + p { + "Whenever we perform an action that generates an error, we'll set that error state. We can then match on the error in a number of ways (early return, return Element, etc)." + } + CodeBlock { + contents: "
\nfn Commandline(cx: Scope) -> Element {{\n\tlet error = use_state(cx, || None);\n\n\tcx.render(match *error {{\n\t\tSome(error) => rsx!(\n\t\t\th1 {{ "An error occurred" }}\n\t\t)\n\t\tNone => rsx!(\n\t\t\tinput {{\n\t\t\t\toninput: move |_| error.set(Some("bad thing happened!")),\n\t\t\t}}\n\t\t)\n\t}})\n}}
\n", + } + h2 { id: "passing-error-states-through-components", + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::PassingErrorStatesThroughComponents, + }, + class: "header", + "Passing error states through components" + } + } + p { + "If you're dealing with a handful of components with minimal nesting, you can just pass the error handle into child components." + } + CodeBlock { + contents: "
\nfn Commandline(cx: Scope) -> Element {{\n\tlet error = use_state(cx, || None);\n\n\tif let Some(error) = **error {{\n\t\treturn cx.render(rsx!{{ "An error occurred" }});\n\t}}\n\n\tcx.render(rsx!{{\n\t\tChild {{ error: error.clone() }}\n\t\tChild {{ error: error.clone() }}\n\t\tChild {{ error: error.clone() }}\n\t\tChild {{ error: error.clone() }}\n\t}})\n}}
\n", + } + p { + "Much like before, our child components can manually set the error during their own actions. The advantage to this pattern is that we can easily isolate error states to a few components at a time, making our app more predictable and robust." + } + h2 { id: "going-global", + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::GoingGlobal, + }, + class: "header", + "Going global" + } + } + p { + "A strategy for handling cascaded errors in larger apps is through signaling an error using global state. This particular pattern involves creating an \"error\" context, and then setting it wherever relevant. This particular method is not as \"sophisticated\" as React's error boundary, but it is more fitting for Rust." + } + p { + "To get started, consider using a built-in hook like " + code { "use_context" } + " and " + code { "use_context_provider" } + " or Fermi. Of course, it's pretty easy to roll your own hook too." + } + p { + "At the \"top\" of our architecture, we're going to want to explicitly declare a value that could be an error." + } + CodeBlock { contents: "
\nenum InputError {{\n\tNone,\n\tTooLong,\n\tTooShort,\n}}\n\nstatic INPUT_ERROR: Atom<InputError> = |_| InputError::None;
\n" } + p { + "Then, in our top level component, we want to explicitly handle the possible error state for this part of the tree." + } + CodeBlock { + contents: "
\nfn TopLevel(cx: Scope) -> Element {{\n\tlet error = use_read(cx, INPUT_ERROR);\n\n\tmatch error {{\n\t\tTooLong => return cx.render(rsx!{{ "FAILED: Too long!" }}),\n\t\tTooShort => return cx.render(rsx!{{ "FAILED: Too Short!" }}),\n\t\t_ => {{}}\n\t}}\n}}
\n", + } + p { + "Now, whenever a downstream component has an error in its actions, it can simply just set its own error state:" + } + CodeBlock { + contents: "
\nfn Commandline(cx: Scope) -> Element {{\n\tlet set_error = use_set(cx, INPUT_ERROR);\n\n\tcx.render(rsx!{{\n\t\tinput {{\n\t\t\toninput: move |evt| {{\n\t\t\t\tif evt.value.len() > 20 {{\n\t\t\t\t\tset_error(InputError::TooLong);\n\t\t\t\t}}\n\t\t\t}}\n\t\t}}\n\t}})\n}}
\n", + } + p { + "This approach to error handling is best in apps that have \"well defined\" error states. Consider using a crate like " + code { "thiserror" } + " or " + code { "anyhow" } + " to simplify the generation of the error types." + } + p { + "This pattern is widely popular in many contexts and is particularly helpful whenever your code generates a non-recoverable error. You can gracefully capture these \"global\" error states without panicking or mucking up state." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookIntegrationsIndexSection { + #[default] + Empty, +} +impl std::str::FromStr for CookbookIntegrationsIndexSection { + type Err = CookbookIntegrationsIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + _ => Err(CookbookIntegrationsIndexSectionParseError), + } + } +} +impl std::fmt::Display for CookbookIntegrationsIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + } + } +} +#[derive(Debug)] +pub struct CookbookIntegrationsIndexSectionParseError; +impl std::fmt::Display for CookbookIntegrationsIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of CookbookIntegrationsIndexSection")?; + Ok(()) + } +} +impl std::error::Error for CookbookIntegrationsIndexSectionParseError {} +#[component(no_case_check)] +pub fn CookbookIntegrationsIndex( + section: CookbookIntegrationsIndexSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + p { + "This section of the guide provides getting started guides for common tools used with Dioxus." + } + ul { + li { + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Empty, + }, + "Logging" + } + } + li { + Link { + to: BookRoute::CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection::Empty, + }, + "Internationalization" + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookIntegrationsLoggingSection { + #[default] + Empty, + Logging, + TheLogCrate, + DioxusLogger, + CustomFormat, + Timestamps, + DioxusLoggerPlatformIntricacies, + FinalNotes, + DesktopAndServer, + Web, + WasmLoggerPlatformIntricacies, + Android, + ViewingAndroidLogs, + Ios, + ViewingIosLogs, +} +impl std::str::FromStr for CookbookIntegrationsLoggingSection { + type Err = CookbookIntegrationsLoggingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "logging" => Ok(Self::Logging), + "the-log-crate" => Ok(Self::TheLogCrate), + "dioxus-logger" => Ok(Self::DioxusLogger), + "custom-format" => Ok(Self::CustomFormat), + "timestamps" => Ok(Self::Timestamps), + "dioxus-logger-platform-intricacies" => Ok(Self::DioxusLoggerPlatformIntricacies), + "final-notes" => Ok(Self::FinalNotes), + "desktop-and-server" => Ok(Self::DesktopAndServer), + "web" => Ok(Self::Web), + "wasm-logger-platform-intricacies" => Ok(Self::WasmLoggerPlatformIntricacies), + "android" => Ok(Self::Android), + "viewing-android-logs" => Ok(Self::ViewingAndroidLogs), + "ios" => Ok(Self::Ios), + "viewing-ios-logs" => Ok(Self::ViewingIosLogs), + _ => Err(CookbookIntegrationsLoggingSectionParseError), + } + } +} +impl std::fmt::Display for CookbookIntegrationsLoggingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Logging => f.write_str("logging"), + Self::TheLogCrate => f.write_str("the-log-crate"), + Self::DioxusLogger => f.write_str("dioxus-logger"), + Self::CustomFormat => f.write_str("custom-format"), + Self::Timestamps => f.write_str("timestamps"), + Self::DioxusLoggerPlatformIntricacies => { + f.write_str("dioxus-logger-platform-intricacies") + } + Self::FinalNotes => f.write_str("final-notes"), + Self::DesktopAndServer => f.write_str("desktop-and-server"), + Self::Web => f.write_str("web"), + Self::WasmLoggerPlatformIntricacies => f.write_str("wasm-logger-platform-intricacies"), + Self::Android => f.write_str("android"), + Self::ViewingAndroidLogs => f.write_str("viewing-android-logs"), + Self::Ios => f.write_str("ios"), + Self::ViewingIosLogs => f.write_str("viewing-ios-logs"), + } + } +} +#[derive(Debug)] +pub struct CookbookIntegrationsLoggingSectionParseError; +impl std::fmt::Display for CookbookIntegrationsLoggingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookIntegrationsLoggingSectionlogging, the-log-crate, dioxus-logger, custom-format, timestamps, dioxus-logger-platform-intricacies, final-notes, desktop-and-server, web, wasm-logger-platform-intricacies, android, viewing-android-logs, ios, viewing-ios-logs", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookIntegrationsLoggingSectionParseError {} +#[component(no_case_check)] +pub fn CookbookIntegrationsLogging( + section: CookbookIntegrationsLoggingSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "logging", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Logging, + }, + class: "header", + "Logging" + } + } + p { + "Dioxus has a wide range of supported platforms, each with their own logging requirements. We'll discuss the different options available to you." + } + h4 { id: "the-log-crate", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::TheLogCrate, + }, + class: "header", + "The Log Crate" + } + } + p { + "The " + Link { to: "https://crates.io/crates/log", "Log" } + " crate is the most universally recognized logging facade in Rust. It is also the easiest to work with in Dioxus; therefore we will be focusing on loggers that work with this crate." + } + p { + "The log crate provides a variety of simple " + code { "println" } + "-like macros with varying levels of severity." + " " + "The available macros are as follows with the highest severity on the bottom:" + } + CodeBlock { + contents: "
\nfn main() {{\n    log::trace!("trace");\n    log::debug!("debug");\n    log::info!("info");\n    log::warn!("warn");\n    log::error!("error");\n}}
\n", + } + p { + "All the loggers provided on this page are, besides configuration and initialization, interfaced using these macros. Often you will also utilize the log crate's " + code { "LevelFilter" } + " enum. This enum usually represents the lowest log severity you want your application to emit and can be loaded from a configuration file, environment variable, or other." + } + p { + "For more information, visit log crate's " + Link { to: "https://docs.rs/log/latest/log/", "docs" } + "." + } + h2 { id: "dioxus-logger", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::DioxusLogger, + }, + class: "header", + "Dioxus Logger" + } + } + p { + Link { to: "https://crates.io/crates/dioxus-logger", "Dioxus Logger" } + " is a planned-to-be feature-rich logger that supports all of Dioxus' platforms. Currently only Desktop, Web, and any server-based targets work with Dioxus Logger." + } + p { + "The easiest way to use Dioxus Logger is by calling the " + code { "init()" } + " function:" + } + CodeBlock { contents: "
\nuse log::LevelFilter;\n\nfn main() {{\n    // Init logger\n    dioxus_logger::init(LevelFilter::Info).expect("failed to init logger");\n    // Dioxus code\n}}
\n" } + p { + "The " + code { "dioxus_logger::init()" } + " function initializes Dioxus Logger with the log crate using the default configuration and provided " + code { "LevelFilter" } + "." + } + h4 { id: "custom-format", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::CustomFormat, + }, + class: "header", + "Custom Format" + } + } + p { + "Dioxus Logger allows you more control with the ability to set a custom format using the " + code { "new" } + " function on the " + code { "DioxusLogger" } + " struct:" + } + CodeBlock { + contents: "
\nuse log::LevelFilter;\n\nfn main() {{\n    // Init logger\n    dioxus_logger::DioxusLogger::new(LevelFilter::Info)\n        .use_format("[{{LEVEL}}] {{PATH}} - {{ARGS}}")\n        .build()\n        .expect("failed to init logger");\n\n    // Dioxus code\n}}
\n", + } + p { + "In this example, we are building a new " + code { "DioxusLogger" } + " struct, providing the " + code { "LevelFilter" } + ", calling the " + code { "use_format()" } + " function, and initializing the logger with the " + code { "build()" } + " function (acts as " + code { "init()" } + " in the previous example)." + } + p { + "The key function call in this example is " + code { "use_format()" } + ". This function takes a " + code { "&str" } + " that specifies how you want your logs to be formatted. To specify a variable in the format, you wrap it's name in " + code { "{{}}" } + "." + } + p { "The available variables are:" } + ul { + li { + "LEVEL - The " + code { "LevelFilter" } + " of the emitted log." + } + li { "PATH - The file path of where the log was emitted, or the crate name." } + li { "ARGS - The arguments passed through the log macro." } + li { + "TIMESTAMP - A timestamp of when the log was emitted. (Requires " + code { "timestamps" } + " feature)" + } + } + h4 { id: "timestamps", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Timestamps, + }, + class: "header", + "Timestamps" + } + } + p { + "Another feature of Dioxus Logger is the ability to include timestamps with your logs. By default, this feature is disabled and has to be enabled by adding " + code { "timestamps" } + " to your features section of the " + code { "dioxus-logger" } + " dependency:" + } + CodeBlock { contents: "
\ndioxus-logger = {{ version = "*", features = ["timestamps"] }}
\n" } + p { + "By enabling this feature, you gain access to the " + code { "TIMESTAMP" } + " format variable." + } + h4 { id: "dioxus-logger-platform-intricacies", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::DioxusLoggerPlatformIntricacies, + }, + class: "header", + "Dioxus Logger Platform Intricacies" + } + } + p { + "On web, Dioxus Logger will use " + Link { to: "https://crates.io/crates/web-sys", "web-sys" } + " to interact with " + code { "console.log()" } + " to output your logs to the browser's console. On Desktop and server-based targets, Dioxus Logger will output using " + code { "println()" } + "." + } + h4 { id: "final-notes", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::FinalNotes, + }, + class: "header", + "Final Notes" + } + } + p { + "Dioxus Logger is the preferred logger to use with Dioxus if it suites your needs. There are many more features to come and Dioxus Logger is planned to become an integral part of Dioxus. If there are any feature suggestions or issues with Dioxus Logger, feel free to reach out on the " + Link { to: "https://discord.gg/XgGxMSkvUM", "Dioxus Discord Server" } + "!" + } + p { + "For more information, visit Dioxus Logger's " + Link { to: "https://docs.rs/dioxus-logger/latest/dioxus_logger/", "docs" } + "." + } + h2 { id: "desktop-and-server", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::DesktopAndServer, + }, + class: "header", + "Desktop and Server" + } + } + p { "For Dioxus' desktop and server targets, you can generally use the logger of your choice." } + p { "Some popular options are:" } + ul { + li { + Link { to: "https://crates.io/crates/env_logger", "env_logger" } + } + li { + Link { to: "https://crates.io/crates/simple_logger", "simple_logger" } + } + li { + Link { to: "https://crates.io/crates/pretty_env_logger", "pretty_env_logger" } + } + } + p { "To keep this guide short, we will not be covering the usage of these loggers." } + p { + "For a full list of popular log-based logging crates, visit " + Link { to: "https://docs.rs/log/latest/log/#available-logging-implementations", + "this" + } + " list in the log crate's docs." + } + h2 { id: "web", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Web, + }, + class: "header", + "Web" + } + } + p { + Link { to: "https://crates.io/crates/wasm-logger", "WASM Logger" } + " is a logging interface that can be used with Dioxus' web platform." + } + p { + "The easiest way to use WASM Logger is with the " + code { "init" } + " function:" + } + CodeBlock { contents: "
\nfn main() {{\n    // Init logger\n    wasm_logger::init(wasm_logger::Config::default());\n\n    // Dioxus code\n}}
\n" } + p { + "This starts WASM Logger with a " + code { "LevelFilter" } + " of " + code { "Debug" } + "." + } + p { + "To specify a custom " + code { "LevelFilter" } + ", build the " + code { "Config" } + " struct:" + } + CodeBlock { contents: "
\nuse log::LevelFilter;\n\nfn main() {{\n    // Init logger\n    let log_config = wasm_logger::Config::new(LevelFilter::Info);\n    wasm_logger::init(log_config);\n\n    // Dioxus code\n}}
\n" } + h4 { id: "wasm-logger-platform-intricacies", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::WasmLoggerPlatformIntricacies, + }, + class: "header", + "Wasm Logger Platform Intricacies" + } + } + p { + "WASM Logger also uses the " + Link { to: "https://crates.io/crates/web-sys", "web-sys" } + " crate to output to the console." + } + p { + "For more information, visit wasm-logger's " + Link { to: "https://docs.rs/wasm-logger/latest/wasm_logger/", "docs" } + "." + } + h2 { id: "android", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Android, + }, + class: "header", + "Android" + } + } + p { + Link { to: "https://crates.io/crates/android_logger", "Android Logger" } + " is a logging interface that can be used when targeting Android. Android Logger runs whenever an event " + code { "native_activity_create" } + " is called by the Android system:" + } + CodeBlock { + contents: "
\nuse log::LevelFilter;\nuse android_logger::Config;\n\nfn native_activity_create() {{\n    android_logger::init_once(\n        Config::default()\n            .with_max_level(LevelFilter::Info)\n            .with_tag("myapp");\n    );\n}}
\n", + } + p { + "The " + code { "with_tag()" } + " is what your app's logs will show as." + } + h4 { id: "viewing-android-logs", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::ViewingAndroidLogs, + }, + class: "header", + "Viewing Android Logs" + } + } + p { "Android logs are sent to logcat. To use logcat through the Android debugger, run:" } + CodeBlock { contents: "
\nadb -d logcat
\n" } + p { "Your Android device will need developer options/usb debugging enabled." } + p { + "For more information, visit android_logger's " + Link { to: "https://docs.rs/android_logger/latest/android_logger/", "docs" } + "." + } + h2 { id: "ios", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Ios, + }, + class: "header", + "iOS" + } + } + p { + "The current option for iOS is the " + Link { to: "https://crates.io/crates/oslog", "oslog" } + " crate." + } + CodeBlock { + contents: "
\nfn main() {{\n    // Init logger\n    OsLogger::new("com.example.test")\n        .level_filter(LevelFilter::Debug)\n        .init()\n        .expect("failed to init logger");\n    // Dioxus code\n}}
\n", + } + h4 { id: "viewing-ios-logs", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::ViewingIosLogs, + }, + class: "header", + "Viewing IOS Logs" + } + } + p { "You can view the emitted logs in Xcode." } + p { + "For more information, visit " + Link { to: "https://crates.io/crates/oslog", "oslog" } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookIntegrationsInternationalizationSection { + #[default] + Empty, + Internationalization, + TheFullCodeForInternationalization, +} +impl std::str::FromStr for CookbookIntegrationsInternationalizationSection { + type Err = CookbookIntegrationsInternationalizationSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "internationalization" => Ok(Self::Internationalization), + "the-full-code-for-internationalization" => { + Ok(Self::TheFullCodeForInternationalization) + } + _ => Err(CookbookIntegrationsInternationalizationSectionParseError), + } + } +} +impl std::fmt::Display for CookbookIntegrationsInternationalizationSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Internationalization => f.write_str("internationalization"), + Self::TheFullCodeForInternationalization => { + f.write_str("the-full-code-for-internationalization") + } + } + } +} +#[derive(Debug)] +pub struct CookbookIntegrationsInternationalizationSectionParseError; +impl std::fmt::Display for CookbookIntegrationsInternationalizationSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookIntegrationsInternationalizationSectioninternationalization, the-full-code-for-internationalization", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookIntegrationsInternationalizationSectionParseError {} +#[component(no_case_check)] +pub fn CookbookIntegrationsInternationalization( + section: CookbookIntegrationsInternationalizationSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "internationalization", + Link { + to: BookRoute::CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection::Internationalization, + }, + class: "header", + "Internationalization" + } + } + p { + "If your application supports multiple languages, the " + Link { to: "https://github.com/DioxusLabs/sdk", "Dioxus SDK" } + " crate contains helpers to make working with translations in your application easier." + } + h2 { id: "the-full-code-for-internationalization", + Link { + to: BookRoute::CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection::TheFullCodeForInternationalization, + }, + class: "header", + "The full code for internationalization" + } + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\nuse dioxus_std::i18n::*;\nuse dioxus_std::translate;\nuse std::str::FromStr;\n\nfn main() {{\n    dioxus_web::launch(app);\n}}\n\nstatic EN_US: &str = r#"{{\n    "id": "en-US",\n    "texts": {{\n        "messages": {{\n            "hello_world": "Hello World!"\n        }},\n        "messages.hello": "Hello {{name}}"\n    }}\n}}"#;\nstatic ES_ES: &str = r#"{{\n    "id": "es-ES",\n    "texts": {{\n        "messages": {{\n            "hello_world": "Hola Mundo!"\n        }},\n        "messages.hello": "Hola {{name}}"\n    }}\n}}"#;\n\n#[allow(non_snake_case)]\nfn Body(cx: Scope) -> Element {{\n    let i18 = use_i18(cx);\n\n    let change_to_english = move |_| i18.set_language("en-US".parse().unwrap());\n    let change_to_spanish = move |_| i18.set_language("es-ES".parse().unwrap());\n\n    render!(\n        button {{\n            onclick: change_to_english,\n            label {{\n                "English"\n            }}\n        }}\n        button {{\n            onclick: change_to_spanish,\n            label {{\n                "Spanish"\n            }}\n        }}\n        p {{ translate!(i18, "messages.hello_world") }}\n        p {{ translate!(i18, "messages.hello", name: "Dioxus")  }}\n    )\n}}\n\nfn app(cx: Scope) -> Element {{\n    use_init_i18n(\n        cx,\n        "en-US".parse().unwrap(),\n        "en-US".parse().unwrap(),\n        || {{\n            let en_us = Language::from_str(EN_US).unwrap();\n            let es_es = Language::from_str(ES_ES).unwrap();\n            vec![en_us, es_es]\n        }},\n    );\n\n    render!(Body {{}})\n}}
\n", + name: "i18n.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookStateIndexSection { + #[default] + Empty, + StateCookbook, +} +impl std::str::FromStr for CookbookStateIndexSection { + type Err = CookbookStateIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "state-cookbook" => Ok(Self::StateCookbook), + _ => Err(CookbookStateIndexSectionParseError), + } + } +} +impl std::fmt::Display for CookbookStateIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::StateCookbook => f.write_str("state-cookbook"), + } + } +} +#[derive(Debug)] +pub struct CookbookStateIndexSectionParseError; +impl std::fmt::Display for CookbookStateIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookStateIndexSectionstate-cookbook", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookStateIndexSectionParseError {} +#[component(no_case_check)] +pub fn CookbookStateIndex(section: CookbookStateIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "state-cookbook", + Link { + to: BookRoute::CookbookStateIndex { + section: CookbookStateIndexSection::StateCookbook, + }, + class: "header", + "State Cookbook" + } + } + ul { + li { + Link { + to: BookRoute::CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection::Empty, + }, + "External State" + } + } + li { + Link { + to: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::Empty, + }, + "Custom Hook" + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookStateExternalIndexSection { + #[default] + Empty, + WorkingWithExternalState, + WorkingWithNonReactiveState, + MakingReactiveStateExternal, +} +impl std::str::FromStr for CookbookStateExternalIndexSection { + type Err = CookbookStateExternalIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "working-with-external-state" => Ok(Self::WorkingWithExternalState), + "working-with-non-reactive-state" => Ok(Self::WorkingWithNonReactiveState), + "making-reactive-state-external" => Ok(Self::MakingReactiveStateExternal), + _ => Err(CookbookStateExternalIndexSectionParseError), + } + } +} +impl std::fmt::Display for CookbookStateExternalIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::WorkingWithExternalState => f.write_str("working-with-external-state"), + Self::WorkingWithNonReactiveState => f.write_str("working-with-non-reactive-state"), + Self::MakingReactiveStateExternal => f.write_str("making-reactive-state-external"), + } + } +} +#[derive(Debug)] +pub struct CookbookStateExternalIndexSectionParseError; +impl std::fmt::Display for CookbookStateExternalIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookStateExternalIndexSectionworking-with-external-state, working-with-non-reactive-state, making-reactive-state-external", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookStateExternalIndexSectionParseError {} +#[component(no_case_check)] +pub fn CookbookStateExternalIndex( + section: CookbookStateExternalIndexSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "working-with-external-state", + Link { + to: BookRoute::CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection::WorkingWithExternalState, + }, + class: "header", + "Working with External State" + } + } + p { + "This guide will help you integrate your Dioxus application with some external state like a different thread or a websocket connection." + } + h2 { id: "working-with-non-reactive-state", + Link { + to: BookRoute::CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection::WorkingWithNonReactiveState, + }, + class: "header", + "Working with non-reactive State" + } + } + p { + Link { + to: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::Empty, + }, + "Coroutines" + } + " are great tool for dealing with non-reactive (state you don't render directly) state within your application." + } + p { + "You can store your state inside the coroutine async block and communicate with the coroutine with messages from any child components." + } + CodeBlock { + contents: "
\n// import futures::StreamExt to use the next() method\nuse futures::StreamExt;\nlet response_state = use_state(cx, || None);\nlet tx = use_coroutine(cx, |mut rx| {{\n    to_owned![response_state];\n    async move {{\n        // Define your state before the loop\n        let mut state = reqwest::Client::new();\n        let mut cache: HashMap<String, String> = HashMap::new();\n        loop {{\n            // Loop and wait for the next message\n            if let Some(request) = rx.next().await {{\n                // Resolve the message\n                let response = if let Some(response) = cache.get(&request) {{\n                    response.clone()\n                }} else {{\n                    let response = state\n                        .get(&request)\n                        .send()\n                        .await\n                        .unwrap()\n                        .text()\n                        .await\n                        .unwrap();\n                    cache.insert(request, response.clone());\n                    response\n                }};\n                response_state.set(Some(response));\n            }} else {{\n                break;\n            }}\n        }}\n    }}\n}});\n// Send a message to the coroutine\ntx.send("https://example.com".to_string());\n// Get the current state of the coroutine\nlet response = response_state.get();
\n", + name: "use_coroutine.rs".to_string(), + } + h2 { id: "making-reactive-state-external", + Link { + to: BookRoute::CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection::MakingReactiveStateExternal, + }, + class: "header", + "Making Reactive State External" + } + } + p { + "If you have some reactive state (state that is rendered), that you want to modify from another thread, you can use the " + Link { to: "https://github.com/DioxusLabs/dioxus-std/blob/master/src/utils/rw/use_rw.rs", + "use_rw" + } + " hook in the " + Link { to: "https://github.com/DioxusLabs/dioxus-std", "dioxus-std" } + " crate." + } + p { + "The use_rw hook works like the use_ref hook, but it is Send + Sync which makes it possible to move the hook into another thread." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookStateCustomHooksIndexSection { + #[default] + Empty, + CustomHooks, + ComposingHooks, + CustomHookLogic, + HookAntiPatterns, +} +impl std::str::FromStr for CookbookStateCustomHooksIndexSection { + type Err = CookbookStateCustomHooksIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "custom-hooks" => Ok(Self::CustomHooks), + "composing-hooks" => Ok(Self::ComposingHooks), + "custom-hook-logic" => Ok(Self::CustomHookLogic), + "hook-anti-patterns" => Ok(Self::HookAntiPatterns), + _ => Err(CookbookStateCustomHooksIndexSectionParseError), + } + } +} +impl std::fmt::Display for CookbookStateCustomHooksIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::CustomHooks => f.write_str("custom-hooks"), + Self::ComposingHooks => f.write_str("composing-hooks"), + Self::CustomHookLogic => f.write_str("custom-hook-logic"), + Self::HookAntiPatterns => f.write_str("hook-anti-patterns"), + } + } +} +#[derive(Debug)] +pub struct CookbookStateCustomHooksIndexSectionParseError; +impl std::fmt::Display for CookbookStateCustomHooksIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookStateCustomHooksIndexSectioncustom-hooks, composing-hooks, custom-hook-logic, hook-anti-patterns", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookStateCustomHooksIndexSectionParseError {} +#[component(no_case_check)] +pub fn CookbookStateCustomHooksIndex( + section: CookbookStateCustomHooksIndexSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "custom-hooks", + Link { + to: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::CustomHooks, + }, + class: "header", + "Custom Hooks" + } + } + p { + "Hooks are a great way to encapsulate business logic. If none of the existing hooks work for your problem, you can write your own." + } + p { + "When writing your hook, you can make a function that accepts " + code { "cx: &ScopeState" } + " as a parameter to accept a scope with any Props." + } + h2 { id: "composing-hooks", + Link { + to: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::ComposingHooks, + }, + class: "header", + "Composing Hooks" + } + } + p { + "To avoid repetition, you can encapsulate business logic based on existing hooks to create a new hook." + } + p { + "For example, if many components need to access an " + code { "AppSettings" } + " struct, you can create a \"shortcut\" hook:" + } + CodeBlock { + contents: "
\nfn use_settings(cx: &ScopeState) -> &UseSharedState<AppSettings> {{\n    use_shared_state::<AppSettings>(cx).expect("App settings not provided")\n}}
\n", + name: "hooks_composed.rs".to_string(), + } + p { + "Or if you want to wrap a hook that persists reloads with the storage API, you can build on top of the use_ref hook to work with mutable state:" + } + CodeBlock { + contents: "
\nuse gloo_storage::{{LocalStorage, Storage}};\nuse serde::{{de::DeserializeOwned, Serialize}};\n\n/// A persistent storage hook that can be used to store data across application reloads.\n#[allow(clippy::needless_return)]\npub fn use_persistent<T: Serialize + DeserializeOwned + Default + 'static>(\n    cx: &ScopeState,\n    // A unique key for the storage entry\n    key: impl ToString,\n    // A function that returns the initial value if the storage entry is empty\n    init: impl FnOnce() -> T,\n) -> &UsePersistent<T> {{\n    // Use the use_ref hook to create a mutable state for the storage entry\n    let state = use_ref(cx, move || {{\n        // This closure will run when the hook is created\n        let key = key.to_string();\n        let value = LocalStorage::get(key.as_str()).ok().unwrap_or_else(init);\n        StorageEntry {{ key, value }}\n    }});\n\n    // Wrap the state in a new struct with a custom API\n    // Note: We use use_hook here so that this hook is easier to use in closures in the rsx. Any values with the same lifetime as the ScopeState can be used in the closure without cloning.\n    cx.use_hook(|| UsePersistent {{\n        inner: state.clone(),\n    }})\n}}\n\nstruct StorageEntry<T> {{\n    key: String,\n    value: T,\n}}\n\n/// Storage that persists across application reloads\npub struct UsePersistent<T: 'static> {{\n    inner: UseRef<StorageEntry<T>>,\n}}\n\nimpl<T: Serialize + DeserializeOwned + Clone + 'static> UsePersistent<T> {{\n    /// Returns a reference to the value\n    pub fn get(&self) -> T {{\n        self.inner.read().value.clone()\n    }}\n\n    /// Sets the value\n    pub fn set(&self, value: T) {{\n        let mut inner = self.inner.write();\n        // Write the new value to local storage\n        LocalStorage::set(inner.key.as_str(), &value);\n        inner.value = value;\n    }}\n}}
\n", + name: "hooks_composed.rs".to_string(), + } + h2 { id: "custom-hook-logic", + Link { + to: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::CustomHookLogic, + }, + class: "header", + "Custom Hook Logic" + } + } + p { + "You can use " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html#method.use_hook", + code { "cx.use_hook" } + } + " to build your own hooks. In fact, this is what all the standard hooks are built on!" + } + p { + code { "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." + } + blockquote { + p { + "Note: You can implement " + Link { to: "https://doc.rust-lang.org/std/ops/trait.Drop.html", + code { "Drop" } + } + " for your hook value – it will be dropped then the component is unmounted (no longer in the UI)" + } + } + p { + "Inside the initialization closure, you will typically make calls to other " + code { "cx" } + " methods. For example:" + } + ul { + li { + "The " + code { "use_state" } + " hook tracks state in the hook value, and uses " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html#method.schedule_update", + code { "cx.schedule_update" } + } + " to make Dioxus re-render the component whenever it changes." + } + } + p { + "Here is a simplified implementation of the " + code { "use_state" } + " hook:" + } + CodeBlock { + contents: "
\nuse std::cell::RefCell;\nuse std::rc::Rc;\nuse std::sync::Arc;\n\n#[derive(Clone)]\nstruct UseState<T> {{\n    value: Rc<RefCell<T>>,\n    update: Arc<dyn Fn()>,\n}}\n\nfn my_use_state<T: 'static>(cx: &ScopeState, init: impl FnOnce() -> T) -> &UseState<T> {{\n    cx.use_hook(|| {{\n        // The update function will trigger a re-render in the component cx is attached to\n        let update = cx.schedule_update();\n        // Create the initial state\n        let value = Rc::new(RefCell::new(init()));\n\n        UseState {{ value, update }}\n    }})\n}}\n\nimpl<T: Clone> UseState<T> {{\n    fn get(&self) -> T {{\n        self.value.borrow().clone()\n    }}\n\n    fn set(&self, value: T) {{\n        // Update the state\n        *self.value.borrow_mut() = value;\n        // Trigger a re-render on the component the state is from\n        (self.update)();\n    }}\n}}
\n", + name: "hooks_custom_logic.rs".to_string(), + } + ul { + li { + "The " + code { "use_context" } + " hook calls " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html#method.consume_context", + code { "cx.consume_context" } + } + " (which would be expensive to call on every render) to get some context from the scope" + } + } + p { + "Here is an implementation of the " + code { "use_context" } + " and " + code { "use_context_provider" } + " hooks:" + } + CodeBlock { + contents: "
\npub fn use_context<T: 'static + Clone>(cx: &ScopeState) -> Option<&T> {{\n    cx.use_hook(|| cx.consume_context::<T>()).as_ref()\n}}\n\npub fn use_context_provider<T: 'static + Clone>(cx: &ScopeState, f: impl FnOnce() -> T) -> &T {{\n    cx.use_hook(|| {{\n        let val = f();\n        // Provide the context state to the scope\n        cx.provide_context(val.clone());\n        val\n    }})\n}}
\n", + name: "hooks_custom_logic.rs".to_string(), + } + h2 { id: "hook-anti-patterns", + Link { + to: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::HookAntiPatterns, + }, + class: "header", + "Hook Anti-Patterns" + } + } + p { "When writing a custom hook, you should avoid the following anti-patterns:" } + ul { + li { + "!Clone Hooks: To allow hooks to be used within async blocks, the hooks must be Clone. To make a hook clone, you can wrap data in Rc or Arc and avoid lifetimes in hooks." + } + } + p { "This version of use_state may seem more efficient, but it is not cloneable:" } + CodeBlock { + contents: "
\nuse std::cell::RefCell;\nuse std::rc::Rc;\nuse std::sync::Arc;\n\nstruct UseState<'a, T> {{\n    value: &'a RefCell<T>,\n    update: Arc<dyn Fn()>,\n}}\n\nfn my_use_state<T: 'static>(cx: &ScopeState, init: impl FnOnce() -> T) -> UseState<T> {{\n    // The update function will trigger a re-render in the component cx is attached to\n    let update = cx.schedule_update();\n    // Create the initial state\n    let value = cx.use_hook(|| RefCell::new(init()));\n\n    UseState {{ value, update }}\n}}\n\nimpl<T: Clone> UseState<'_, T> {{\n    fn get(&self) -> T {{\n        self.value.borrow().clone()\n    }}\n\n    fn set(&self, value: T) {{\n        // Update the state\n        *self.value.borrow_mut() = value;\n        // Trigger a re-render on the component the state is from\n        (self.update)();\n    }}\n}}
\n", + name: "hooks_anti_patterns.rs".to_string(), + } + p { "If we try to use this hook in an async block, we will get a compile error:" } + CodeBlock { + contents: "
\nfn FutureComponent(cx: &ScopeState) -> Element {{\n\tlet my_state = my_use_state(cx, || 0);\n\tcx.spawn({{\n\t\tto_owned![my_state];\n\t\tasync move {{\n\t\t\tmy_state.set(1);\n\t\t}}\n\t}});\n\n\ttodo!()\n}}
\n", + } + p { "But with the original version, we can use it in an async block:" } + CodeBlock { + contents: "
\nfn FutureComponent(cx: &ScopeState) -> Element {{\n\tlet my_state = use_state(cx, || 0);\n\tcx.spawn({{\n\t\tto_owned![my_state];\n\t\tasync move {{\n\t\t\tmy_state.set(1);\n\t\t}}\n\t}});\n\n\ttodo!()\n}}
\n", + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookTestingSection { + #[default] + Empty, + Testing, + ComponentTesting, + HookTesting, + EndToEndTesting, +} +impl std::str::FromStr for CookbookTestingSection { + type Err = CookbookTestingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "testing" => Ok(Self::Testing), + "component-testing" => Ok(Self::ComponentTesting), + "hook-testing" => Ok(Self::HookTesting), + "end-to-end-testing" => Ok(Self::EndToEndTesting), + _ => Err(CookbookTestingSectionParseError), + } + } +} +impl std::fmt::Display for CookbookTestingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Testing => f.write_str("testing"), + Self::ComponentTesting => f.write_str("component-testing"), + Self::HookTesting => f.write_str("hook-testing"), + Self::EndToEndTesting => f.write_str("end-to-end-testing"), + } + } +} +#[derive(Debug)] +pub struct CookbookTestingSectionParseError; +impl std::fmt::Display for CookbookTestingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookTestingSectiontesting, component-testing, hook-testing, end-to-end-testing", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookTestingSectionParseError {} +#[component(no_case_check)] +pub fn CookbookTesting(section: CookbookTestingSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "testing", + Link { + to: BookRoute::CookbookTesting { + section: CookbookTestingSection::Testing, + }, + class: "header", + "Testing" + } + } + p { + "When building application or libraries with Dioxus, you may want to include some tests to check the behavior of parts of your application. This guide will teach you how to test different parts of your Dioxus application." + } + h2 { id: "component-testing", + Link { + to: BookRoute::CookbookTesting { + section: CookbookTestingSection::ComponentTesting, + }, + class: "header", + "Component Testing" + } + } + p { + "You can use a combination of " + Link { to: "https://docs.rs/pretty_assertions/latest/pretty_assertions/", + "pretty-assertions" + } + " and " + Link { to: "https://docs.rs/dioxus-ssr/latest/dioxus_ssr/", "dioxus-ssr" } + " to check that two snippets of rsx are equal:" + } + CodeBlock { + contents: "
\nuse futures::FutureExt;\nuse std::{{cell::RefCell, sync::Arc}};\n\nuse dioxus::prelude::*;\n\n#[test]\nfn test() {{\n    assert_rsx_eq(\n        rsx! {{\n            div {{\n                "Hello world"\n            }}\n            div {{\n                "Hello world"\n            }}\n        }},\n        rsx! {{\n            for _ in 0..2 {{\n                div {{\n                    "Hello world"\n                }}\n            }}\n        }},\n    )\n}}\n\nfn assert_rsx_eq(first: LazyNodes<'static, 'static>, second: LazyNodes<'static, 'static>) {{\n    let first = dioxus_ssr::render_lazy(first);\n    let second = dioxus_ssr::render_lazy(second);\n    pretty_assertions::assert_str_eq!(first, second);\n}}
\n", + name: "component_test.rs".to_string(), + } + h2 { id: "hook-testing", + Link { + to: BookRoute::CookbookTesting { + section: CookbookTestingSection::HookTesting, + }, + class: "header", + "Hook Testing" + } + } + p { + "When creating libraries around Dioxus, it can be helpful to make tests for your " + Link { + to: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::Empty, + }, + "custom hooks" + } + "." + } + p { + "Dioxus does not currently have a full hook testing library, but you can build a bespoke testing framework by manually driving the virtual dom." + } + CodeBlock { + contents: "
\nuse futures::FutureExt;\nuse std::{{cell::RefCell, sync::Arc}};\n\nuse dioxus::prelude::*;\n\n#[test]\nfn test() {{\n    test_hook(\n        |cx| use_ref(cx, || 0).clone(),\n        |value, mut proxy| match proxy.generation {{\n            0 => {{\n                value.set(1);\n            }}\n            1 => {{\n                assert_eq!(*value.read(), 1);\n                value.set(2);\n            }}\n            2 => {{\n                proxy.rerun();\n            }}\n            3 => {{}}\n            _ => todo!(),\n        }},\n        |proxy| assert_eq!(proxy.generation, 4),\n    );\n}}\n\nfn test_hook<V: 'static>(\n    initialize: impl FnMut(&ScopeState) -> V + 'static,\n    check: impl FnMut(V, MockProxy) + 'static,\n    mut final_check: impl FnMut(MockProxy) + 'static,\n) {{\n    #[derive(Props)]\n    struct MockAppComponent<\n        I: FnMut(&ScopeState) -> V + 'static,\n        C: FnMut(V, MockProxy) + 'static,\n        V,\n    > {{\n        hook: RefCell<I>,\n        check: RefCell<C>,\n    }}\n\n    impl<I: FnMut(&ScopeState) -> V, C: FnMut(V, MockProxy), V> PartialEq\n        for MockAppComponent<I, C, V>\n    {{\n        fn eq(&self, _: &Self) -> bool {{\n            true\n        }}\n    }}\n\n    fn mock_app<I: FnMut(&ScopeState) -> V, C: FnMut(V, MockProxy), V>(\n        cx: Scope<MockAppComponent<I, C, V>>,\n    ) -> Element {{\n        let value = cx.props.hook.borrow_mut()(cx);\n\n        cx.props.check.borrow_mut()(value, MockProxy::new(cx));\n\n        render! {{\n            div {{}}\n        }}\n    }}\n\n    let mut vdom = VirtualDom::new_with_props(\n        mock_app,\n        MockAppComponent {{\n            hook: RefCell::new(initialize),\n            check: RefCell::new(check),\n        }},\n    );\n\n    let _ = vdom.rebuild();\n\n    while vdom.wait_for_work().now_or_never().is_some() {{\n        let _ = vdom.render_immediate();\n    }}\n\n    final_check(MockProxy::new(vdom.base_scope()));\n}}\n\nstruct MockProxy {{\n    rerender: Arc<dyn Fn()>,\n    pub generation: usize,\n}}\n\nimpl MockProxy {{\n    fn new(scope: &ScopeState) -> Self {{\n        let generation = scope.generation();\n        let rerender = scope.schedule_update();\n\n        Self {{\n            rerender,\n            generation,\n        }}\n    }}\n\n    pub fn rerun(&mut self) {{\n        (self.rerender)();\n    }}\n}}
\n", + name: "hook_test.rs".to_string(), + } + h2 { id: "end-to-end-testing", + Link { + to: BookRoute::CookbookTesting { + section: CookbookTestingSection::EndToEndTesting, + }, + class: "header", + "End to End Testing" + } + } + p { + "You can use " + Link { to: "https://playwright.dev/", "Playwright" } + " to create end to end tests for your dioxus application." + } + p { + "In your " + code { "playwright.config.js" } + ", you will need to run cargo run or dx serve instead of the default build command. Here is a snippet from the end to end web example:" + } + CodeBlock { + contents: "
\n//...\nwebServer: [\n    {{\n        cwd: path.join(process.cwd(), 'playwright-tests', 'web'),\n        command: 'dx serve',\n        port: 8080,\n        timeout: 10 * 60 * 1000,\n        reuseExistingServer: !process.env.CI,\n        stdout: "pipe",\n    }},\n],
\n", + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/playwright-tests/web", + "Web example" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/playwright-tests/liveview", + "Liveview example" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/playwright-tests/fullstack", + "Fullstack example" + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookExamplesSection { + #[default] + Empty, + Examples, +} +impl std::str::FromStr for CookbookExamplesSection { + type Err = CookbookExamplesSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "examples" => Ok(Self::Examples), + _ => Err(CookbookExamplesSectionParseError), + } + } +} +impl std::fmt::Display for CookbookExamplesSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Examples => f.write_str("examples"), + } + } +} +#[derive(Debug)] +pub struct CookbookExamplesSectionParseError; +impl std::fmt::Display for CookbookExamplesSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of CookbookExamplesSectionexamples")?; + Ok(()) + } +} +impl std::error::Error for CookbookExamplesSectionParseError {} +#[component(no_case_check)] +pub fn CookbookExamples(section: CookbookExamplesSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "examples", + Link { + to: BookRoute::CookbookExamples { + section: CookbookExamplesSection::Examples, + }, + class: "header", + "Examples" + } + } + p { + "There's a " + em { "lot" } + " of these, so if you're having trouble implementing something, or you just want to see cool things" + " " + "that you can do with Dioxus, make sure to give these a look!" + } + p { + "Each of the examples in the main repository also has a permalink attached, in case the main one doesn't work." + " " + "However, permalinks lead to an older \"archived\" version, so if you're reading this a long time in the future in a galaxy far, far away..." + " " + "The examples in the permalinks might not work." + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/examples", + "Main list" + } + " - " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/examples", + "permalink" + } + " - This is the largest list." + } + li { + "Package-specific examples from the " + Link { to: "https://github.com/DioxusLabs/dioxus/", "main repository" } + ". To learn more about these packages, search them up on " + Link { to: "https://crates.io/", "crates.io" } + ", or navigate from the examples to the root of the package." + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/web/examples", + "dioxus-web" + } + " - " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/web/examples", + "permalink" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/fullstack/examples", + "dioxus-fullstack" + } + " - " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/fullstack/examples", + "permalink" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview/examples", + "dioxus-liveview" + } + " - " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/liveview/examples", + "permalink" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/router/examples", + "dioxus-router" + } + " - " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/router/examples", + "permalink" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/dioxus-tui/examples", + "dioxus-tui" + } + " - " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/dioxus-tui/examples", + "permalink" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/rink/examples", + "plasmo (\"rink\" is the old name, it will be updated)" + } + " - " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/rink/examples", + "permalink" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/rsx-rosetta/examples", + "rsx-rosetta" + } + " - " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/rsx-rosetta/examples", + "permalink" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core/examples", + "native-core" + } + " - " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/native-core/examples", + "permalink" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/signals/examples", + "signals" + } + " - " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/60ee82942c4decf67b6ad263f639553d9b7e28a9/packages/signals/examples", + "permalink" + } + " - This is unreleased, but it's a very exciting project, so stay tuned!" + } + } + } + li { + "The " + Link { to: "https://github.com/DioxusLabs/example-projects", "example-projects" } + " repository. It might be deprecated/removed in the future though. See " + Link { to: "https://github.com/DioxusLabs/example-projects/issues/25", + "#25" + } + "." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookTailwindSection { + #[default] + Empty, + Tailwind, + Setup, + BonusSteps, + Development, + Web, + Desktop, +} +impl std::str::FromStr for CookbookTailwindSection { + type Err = CookbookTailwindSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "tailwind" => Ok(Self::Tailwind), + "setup" => Ok(Self::Setup), + "bonus-steps" => Ok(Self::BonusSteps), + "development" => Ok(Self::Development), + "web" => Ok(Self::Web), + "desktop" => Ok(Self::Desktop), + _ => Err(CookbookTailwindSectionParseError), + } + } +} +impl std::fmt::Display for CookbookTailwindSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Tailwind => f.write_str("tailwind"), + Self::Setup => f.write_str("setup"), + Self::BonusSteps => f.write_str("bonus-steps"), + Self::Development => f.write_str("development"), + Self::Web => f.write_str("web"), + Self::Desktop => f.write_str("desktop"), + } + } +} +#[derive(Debug)] +pub struct CookbookTailwindSectionParseError; +impl std::fmt::Display for CookbookTailwindSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookTailwindSectiontailwind, setup, bonus-steps, development, web, desktop", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookTailwindSectionParseError {} +#[component(no_case_check)] +pub fn CookbookTailwind(section: CookbookTailwindSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "tailwind", + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Tailwind, + }, + class: "header", + "Tailwind" + } + } + p { + "You can style your Dioxus application with whatever CSS framework you choose, or just write vanilla CSS." + } + p { + "One popular option for styling your Dioxus application is " + Link { to: "https://tailwindcss.com/", "Tailwind" } + ". Tailwind allows you to style your elements with CSS utility classes. This guide will show you how to setup tailwind CSS with your Dioxus application." + } + h2 { id: "setup", + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Setup, + }, + class: "header", + "Setup" + } + } + ol { + li { + p { "Install the Dioxus CLI:" } + CodeBlock { contents: "
\ncargo install --git https://github.com/DioxusLabs/cli
\n" } + } + li { + p { "Install npm: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm" } + } + li { + p { "Install the tailwind css cli: https://tailwindcss.com/docs/installation" } + } + li { + p { "Initialize the tailwind css project:" } + CodeBlock { contents: "
\nnpx tailwindcss init
\n" } + p { + "This should create a " + code { "tailwind.config.js" } + " file in the root of the project." + } + } + li { + p { + "Edit the " + code { "tailwind.config.js" } + " file to include rust files:" + } + CodeBlock { + contents: "
\nmodule.exports = {{\n    mode: "all",\n    content: [\n        // include all rust, html and css files in the src directory\n        "./src/**/*.{{rs,html,css}}",\n        // include all html files in the output (dist) directory\n        "./dist/**/*.html",\n    ],\n    theme: {{\n        extend: {{}},\n    }},\n    plugins: [],\n}}
\n", + } + } + li { + p { + "Create a " + code { "input.css" } + " file with the following content:" + } + CodeBlock { contents: "
\n@tailwind base;\n@tailwind components;\n@tailwind utilities;
\n" } + } + li { + p { + "Create a " + code { "Dioxus.toml" } + " file with the following content that links to the " + code { "tailwind.css" } + " file:" + } + CodeBlock { + contents: "
\n[application]\n\n# App (Project) Name\nname = "Tailwind CSS + Dioxus"\n\n# Dioxus App Default Platform\n# desktop, web, mobile, ssr\ndefault_platform = "web"\n\n# `build` & `serve` dist path\nout_dir = "dist"\n\n# resource (public) file folder\nasset_dir = "public"\n\n[web.app]\n\n# HTML title tag content\ntitle = "dioxus | ⛺"\n\n[web.watcher]\n\n# when watcher trigger, regenerate the `index.html`\nreload_html = true\n\n# which files or dirs will be watcher monitoring\nwatch_path = ["src", "public"]\n\n# uncomment line below if using Router\n# index_on_404 = true\n\n# include `assets` in web platform\n[web.resource]\n\n# CSS style file\nstyle = ["/tailwind.css"]\n\n# Javascript code file\nscript = []\n\n[web.resource.dev]\n\n# serve: [dev-server] only\n\n# CSS style file\nstyle = []\n\n# Javascript code file\nscript = []
\n", + } + } + } + h3 { id: "bonus-steps", + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::BonusSteps, + }, + class: "header", + "Bonus Steps" + } + } + ol { + li { + p { "Install the tailwind css vs code extension" } + } + li { + p { + "Go to the settings for the extension and find the experimental regex support section. Edit the setting.json file to look like this:" + } + CodeBlock { contents: "
\n"tailwindCSS.experimental.classRegex": ["class: \\"(.*)\\""],\n"tailwindCSS.includeLanguages": {{\n    "rust": "html"\n}},
\n" } + } + } + h2 { id: "development", + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Development, + }, + class: "header", + "Development" + } + } + ul { + li { + p { + "Run the following command in the root of the project to start the tailwind css compiler:" + } + CodeBlock { contents: "
\nnpx tailwindcss -i ./input.css -o ./public/tailwind.css --watch
\n" } + } + } + h3 { id: "web", + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Web, + }, + class: "header", + "Web" + } + } + ul { + li { + p { + "Run the following command in the root of the project to start the dioxus dev server:" + } + CodeBlock { contents: "
\ndx serve --hot-reload
\n" } + } + li { + p { + "Open the browser to " + Link { to: "http://localhost:8080", "http://localhost:8080" } + "." + } + } + } + h3 { id: "desktop", + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Desktop, + }, + class: "header", + "Desktop" + } + } + ul { + li { + p { + "Add a custom head pointing to the generated tailwind CSS file in your " + code { "main" } + ". It looks like:" + } + CodeBlock { contents: "
\ndioxus_desktop::launch_cfg(\n  App,\n  dioxus_desktop::Config::new()\n    .with_custom_head(r#"<link rel="stylesheet" href="public/tailwind.css">"#.to_string()))
\n" } + } + li { + p { "Launch the dioxus desktop app:" } + CodeBlock { contents: "
\ncargo run
\n" } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookCustomRendererSection { + #[default] + Empty, + CustomRenderer, + TheSpecifics, + Templates, + Mutations, + NodeStorage, + AnExample, + BuildingTemplates, + ApplyingMutations, + EventLoop, + CustomRawElements, + NativeCore, + TheRealdom, + Example, + Layout, + TextEditing, + Conclusion, +} +impl std::str::FromStr for CookbookCustomRendererSection { + type Err = CookbookCustomRendererSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "custom-renderer" => Ok(Self::CustomRenderer), + "the-specifics" => Ok(Self::TheSpecifics), + "templates" => Ok(Self::Templates), + "mutations" => Ok(Self::Mutations), + "node-storage" => Ok(Self::NodeStorage), + "an-example" => Ok(Self::AnExample), + "building-templates" => Ok(Self::BuildingTemplates), + "applying-mutations" => Ok(Self::ApplyingMutations), + "event-loop" => Ok(Self::EventLoop), + "custom-raw-elements" => Ok(Self::CustomRawElements), + "native-core" => Ok(Self::NativeCore), + "the-realdom" => Ok(Self::TheRealdom), + "example" => Ok(Self::Example), + "layout" => Ok(Self::Layout), + "text-editing" => Ok(Self::TextEditing), + "conclusion" => Ok(Self::Conclusion), + _ => Err(CookbookCustomRendererSectionParseError), + } + } +} +impl std::fmt::Display for CookbookCustomRendererSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::CustomRenderer => f.write_str("custom-renderer"), + Self::TheSpecifics => f.write_str("the-specifics"), + Self::Templates => f.write_str("templates"), + Self::Mutations => f.write_str("mutations"), + Self::NodeStorage => f.write_str("node-storage"), + Self::AnExample => f.write_str("an-example"), + Self::BuildingTemplates => f.write_str("building-templates"), + Self::ApplyingMutations => f.write_str("applying-mutations"), + Self::EventLoop => f.write_str("event-loop"), + Self::CustomRawElements => f.write_str("custom-raw-elements"), + Self::NativeCore => f.write_str("native-core"), + Self::TheRealdom => f.write_str("the-realdom"), + Self::Example => f.write_str("example"), + Self::Layout => f.write_str("layout"), + Self::TextEditing => f.write_str("text-editing"), + Self::Conclusion => f.write_str("conclusion"), + } + } +} +#[derive(Debug)] +pub struct CookbookCustomRendererSectionParseError; +impl std::fmt::Display for CookbookCustomRendererSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookCustomRendererSectioncustom-renderer, the-specifics, templates, mutations, node-storage, an-example, building-templates, applying-mutations, event-loop, custom-raw-elements, native-core, the-realdom, example, layout, text-editing, conclusion", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookCustomRendererSectionParseError {} +#[component(no_case_check)] +pub fn CookbookCustomRenderer(section: CookbookCustomRendererSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "custom-renderer", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::CustomRenderer, + }, + class: "header", + "Custom Renderer" + } + } + p { + "Dioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer." + } + p { + "Great news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require processing " + code { "Mutations" } + " and sending " + code { "UserEvents" } + "." + } + h2 { id: "the-specifics", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::TheSpecifics, + }, + class: "header", + "The specifics:" + } + } + p { "Implementing the renderer is fairly straightforward. The renderer needs to:" } + ol { + li { "Handle the stream of edits generated by updates to the virtual DOM" } + li { "Register listeners and pass events into the virtual DOM's event system" } + } + p { + "Essentially, your renderer needs to process edits and generate events to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen." + } + p { + "Internally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves." + } + p { + "For reference, check out the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/interpreter", + "javascript interpreter" + } + " or " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/dioxus-tui", + "tui renderer" + } + " as a starting point for your custom renderer." + } + h2 { id: "templates", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Templates, + }, + class: "header", + "Templates" + } + } + p { + "Dioxus is built around the concept of " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html", + "Templates" + } + ". Templates describe a UI tree known at compile time with dynamic parts filled at runtime. This is useful internally to make skip diffing static nodes, but it is also useful for the renderer to reuse parts of the UI tree. This can be useful for things like a list of items. Each item could contain some static parts and some dynamic parts. The renderer can use the template to create a static part of the UI once, clone it for each element in the list, and then fill in the dynamic parts." + } + h2 { id: "mutations", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Mutations, + }, + class: "header", + "Mutations" + } + } + p { + "The " + code { "Mutation" } + " type is a serialized enum that represents an operation that should be applied to update the UI. The variants roughly follow this set:" + } + CodeBlock { + contents: "
\nenum Mutation {{\n\tAppendChildren,\n\tAssignId,\n\tCreatePlaceholder,\n\tCreateTextNode,\n\tHydrateText,\n\tLoadTemplate,\n\tReplaceWith,\n\tReplacePlaceholder,\n\tInsertAfter,\n\tInsertBefore,\n\tSetAttribute,\n\tSetText,\n\tNewEventListener,\n\tRemoveEventListener,\n\tRemove,\n\tPushRoot,\n}}
\n", + } + p { + "The Dioxus diffing mechanism operates as a " + Link { to: "https://en.wikipedia.org/wiki/Stack_machine", "stack machine" } + " where the " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate", + "LoadTemplate" + } + ", " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.CreatePlaceholder", + "CreatePlaceholder" + } + ", and " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.CreateTextNode", + "CreateTextNode" + } + " mutations pushes a new \"real\" DOM node onto the stack and " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.AppendChildren", + "AppendChildren" + } + ", " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.InsertAfter", + "InsertAfter" + } + ", " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.InsertBefore", + "InsertBefore" + } + ", " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.ReplacePlaceholder", + "ReplacePlaceholder" + } + ", and " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.ReplaceWith", + "ReplaceWith" + } + " all remove nodes from the stack." + } + h2 { id: "node-storage", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::NodeStorage, + }, + class: "header", + "Node storage" + } + } + p { + "Dioxus saves and loads elements with IDs. Inside the VirtualDOM, this is just tracked as as a u64." + } + p { + "Whenever a " + code { "CreateElement" } + " edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when id is used in a mutation. Dioxus reclaims the IDs of elements when removed. To stay in sync with Dioxus you can use a sparse Vec (Vec" + " " + "<" + " " + "Option" + p { class: "inline-html-block", dangerous_inner_html: "" } + ">) with possibly unoccupied items. You can use the ids as indexes into the Vec for elements, and grow the Vec when an id does not exist." + } + h3 { id: "an-example", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::AnExample, + }, + class: "header", + "An Example" + } + } + p { + "For the sake of understanding, let's consider this example – a very simple UI declaration:" + } + CodeBlock { contents: "
\nrsx!( h1 {{"count: {{x}}"}} )
\n" } + h4 { id: "building-templates", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::BuildingTemplates, + }, + class: "header", + "Building Templates" + } + } + p { + "The above rsx will create a template that contains one static h1 tag and a placeholder for a dynamic text node. The template contains the static parts of the UI, and ids for the dynamic parts along with the paths to access them." + } + p { "The template will look something like this:" } + CodeBlock { + contents: "
\nTemplate {{\n\t// Some id that is unique for the entire project\n\tname: "main.rs:1:1:0",\n\t// The root nodes of the template\n\troots: &[\n\t\tTemplateNode::Element {{\n\t\t\ttag: "h1",\n\t\t\tnamespace: None,\n\t\t\tattrs: &[],\n\t\t\tchildren: &[\n\t\t\t\tTemplateNode::DynamicText {{\n\t\t\t\t\tid: 0\n\t\t\t\t}},\n\t\t\t],\n\t\t}}\n\t],\n\t// the path to each of the dynamic nodes\n\tnode_paths: &[\n\t\t// the path to dynamic node with a id of 0\n\t\t&[\n\t\t\t// on the first root node\n\t\t\t0,\n\t\t\t// the first child of the root node\n\t\t\t0,\n\t\t]\n\t],\n\t// the path to each of the dynamic attributes\n\tattr_paths: &'a [&'a [u8]],\n}}
\n", + } + blockquote { + p { + "For more detailed docs about the structure of templates see the " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html", + "Template api docs" + } + } + } + p { + "This template will be sent to the renderer in the " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/struct.Mutations.html#structfield.templates", + "list of templates" + } + " supplied with the mutations the first time it is used. Any time the renderer encounters a " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate", + "LoadTemplate" + } + " mutation after this, it should clone the template and store it in the given id." + } + p { + "For dynamic nodes and dynamic text nodes, a placeholder node should be created and inserted into the UI so that the node can be modified later." + } + p { "In HTML renderers, this template could look like this:" } + CodeBlock { contents: "
\n<h1>""</h1>
\n" } + h4 { id: "applying-mutations", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::ApplyingMutations, + }, + class: "header", + "Applying Mutations" + } + } + p { + "After the renderer has created all of the new templates, it can begin to process the mutations." + } + p { + "When the renderer starts, it should contain the Root node on the stack and store the Root node with an id of 0. The Root node is the top-level node of the UI. In HTML, this is the " + code { "
" } + " element." + } + CodeBlock { contents: "
\ninstructions: []\nstack: [\n\tRootNode,\n]\nnodes: [\n\tRootNode,\n]
\n" } + p { + "The first mutation is a " + code { "LoadTemplate" } + " mutation. This tells the renderer to load a root from the template with the given id. The renderer will then push the root node of the template onto the stack and store it with an id for later. In this case, the root node is an h1 element." + } + CodeBlock { + contents: "
\ninstructions: [\n\tLoadTemplate {{\n\t\t// the id of the template\n\t\tname: "main.rs:1:1:0",\n\t\t// the index of the root node in the template\n\t\tindex: 0,\n\t\t// the id to store\n\t\tid: ElementId(1),\n\t}}\n]\nstack: [\n\tRootNode,\n\t<h1>""</h1>,\n]\nnodes: [\n\tRootNode,\n\t<h1>""</h1>,\n]
\n", + } + p { + "Next, Dioxus will create the dynamic text node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the Mutation " + code { "HydrateText" } + ". When the renderer receives this instruction, it will navigate to the placeholder text node in the template and replace it with the new text." + } + CodeBlock { + contents: "
\ninstructions: [\n\tLoadTemplate {{\n\t\tname: "main.rs:1:1:0",\n\t\tindex: 0,\n\t\tid: ElementId(1),\n\t}},\n\tHydrateText {{\n\t\t// the id to store the text node\n\t\tid: ElementId(2),\n\t\t// the text to set\n\t\ttext: "count: 0",\n\t}}\n]\nstack: [\n\tRootNode,\n\t<h1>"count: 0"</h1>,\n]\nnodes: [\n\tRootNode,\n\t<h1>"count: 0"</h1>,\n\t"count: 0",\n]
\n", + } + p { + "Remember, the h1 node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the h1 node to the Root. It depends on the situation, but in this case, we use " + code { "AppendChildren" } + ". This pops the text node off the stack, leaving the Root element as the next element on the stack." + } + CodeBlock { + contents: "
\ninstructions: [\n\tLoadTemplate {{\n\t\tname: "main.rs:1:1:0",\n\t\tindex: 0,\n\t\tid: ElementId(1),\n\t}},\n\tHydrateText {{\n\t\tid: ElementId(2),\n\t\ttext: "count: 0",\n\t}},\n\tAppendChildren {{\n\t\t// the id of the parent node\n\t\tid: ElementId(0),\n\t\t// the number of nodes to pop off the stack and append\n\t\tm: 1\n\t}}\n]\nstack: [\n\tRootNode,\n]\nnodes: [\n\tRootNode,\n\t<h1>"count: 0"</h1>,\n\t"count: 0",\n]
\n", + } + p { "Over time, our stack looked like this:" } + CodeBlock { contents: "
\n[Root]\n[Root, <h1>""</h1>]\n[Root, <h1>"count: 0"</h1>]\n[Root]
\n" } + p { + "Conveniently, this approach completely separates the Virtual DOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits make Dioxus independent of platform specifics." + } + p { + "Dioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing." + } + p { + "This little demo serves to show exactly how a Renderer would need to process a mutation stream to build UIs." + } + h2 { id: "event-loop", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::EventLoop, + }, + class: "header", + "Event loop" + } + } + p { + "Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important for your custom renderer can handle those too." + } + p { + "The code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is:" + } + CodeBlock { + contents: "
\npub async fn run(&mut self) -> dioxus_core::error::Result<()> {{\n\t// Push the body element onto the WebsysDom's stack machine\n\tlet mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom());\n\twebsys_dom.stack.push(root_node);\n\n\t// Rebuild or hydrate the virtualdom\n\tlet mutations = self.internal_dom.rebuild();\n\twebsys_dom.apply_mutations(mutations);\n\n\t// Wait for updates from the real dom and progress the virtual dom\n\tloop {{\n\t\tlet user_input_future = websys_dom.wait_for_event();\n\t\tlet internal_event_future = self.internal_dom.wait_for_work();\n\n\t\tmatch select(user_input_future, internal_event_future).await {{\n\t\t\tEither::Left((_, _)) => {{\n\t\t\t\tlet mutations = self.internal_dom.work_with_deadline(|| false);\n\t\t\t\twebsys_dom.apply_mutations(mutations);\n\t\t\t}},\n\t\t\tEither::Right((event, _)) => websys_dom.handle_event(event),\n\t\t}}\n\n\t\t// render\n\t}}\n}}
\n", + } + p { + "It's important to decode what the real events are for your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus " + code { "UserEvent" } + " type. Right now, the virtual event system is modeled almost entirely around the HTML spec, but we are interested in slimming it down." + } + CodeBlock { + contents: "
\nfn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {{\n\tmatch event.type_().as_str() {{\n\t\t"keydown" => {{\n\t\t\tlet event: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();\n\t\t\tUserEvent::KeyboardEvent(UserEvent {{\n\t\t\t\tscope_id: None,\n\t\t\t\tpriority: EventPriority::Medium,\n\t\t\t\tname: "keydown",\n\t\t\t\t// This should be whatever element is focused\n\t\t\t\telement: Some(ElementId(0)),\n\t\t\t\tdata: Arc::new(KeyboardData{{\n\t\t\t\t\tchar_code: event.char_code(),\n\t\t\t\t\tkey: event.key(),\n\t\t\t\t\tkey_code: event.key_code(),\n\t\t\t\t\talt_key: event.alt_key(),\n\t\t\t\t\tctrl_key: event.ctrl_key(),\n\t\t\t\t\tmeta_key: event.meta_key(),\n\t\t\t\t\tshift_key: event.shift_key(),\n\t\t\t\t\tlocation: event.location(),\n\t\t\t\t\trepeat: event.repeat(),\n\t\t\t\t\twhich: event.which(),\n\t\t\t\t}})\n\t\t\t}})\n\t\t}}\n\t\t_ => todo!()\n\t}}\n}}
\n", + } + h2 { id: "custom-raw-elements", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::CustomRawElements, + }, + class: "header", + "Custom raw elements" + } + } + p { + "If you need to go as far as relying on custom elements/attributes 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. You can drop in your elements any time you want, with little hassle. However, you must be sure your renderer can handle the new namespace." + } + p { + "For more examples and information on how to create custom namespaces, see the " + Link { to: "https://github.com/DioxusLabs/dioxus/blob/master/packages/html/README.md#how-to-extend-it", + code { "dioxus_html" } + " crate" + } + "." + } + h1 { id: "native-core", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::NativeCore, + }, + class: "header", + "Native Core" + } + } + p { + "If you are creating a renderer in rust, the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core", + "native-core" + } + " crate provides some utilities to implement a renderer. It provides an abstraction over Mutations and Templates and contains helpers that can handle the layout and text editing for you." + } + h2 { id: "the-realdom", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::TheRealdom, + }, + class: "header", + "The RealDom" + } + } + p { + "The " + code { "RealDom" } + " is a higher-level abstraction over updating the Dom. It uses an entity component system to manage the state of nodes. This system allows you to modify insert and modify arbitrary components on nodes. On top of this, the RealDom provides a way to manage a tree of nodes, and the State trait provides a way to automatically add and update these components when the tree is modified. It also provides a way to apply " + code { "Mutations" } + " to the RealDom." + } + h3 { id: "example", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Example, + }, + class: "header", + "Example" + } + } + p { + "Let's build a toy renderer with borders, size, and text color." + " " + "Before we start let's take a look at an example element we can render:" + } + CodeBlock { contents: "
\ncx.render(rsx!{{\n\tdiv{{\n\t\tcolor: "red",\n\t\tp{{\n\t\t\tborder: "1px solid black",\n\t\t\t"hello world"\n\t\t}}\n\t}}\n}})
\n" } + p { + "In this tree, the color depends on the parent's color. The layout depends on the children's layout, the current text, and the text size. The border depends on only the current node." + } + p { "In the following diagram arrows represent dataflow:" } + p { + Link { to: "https://mermaid.live/edit#pako:eNqllV1vgjAUhv8K6W4wkQVa2QdLdrHsdlfukmSptEhjoaSWqTH-9xVwONAKst70g5739JzzlO5BJAgFAYi52EQJlsr6fAszS7d1sVhKnCdWJDJFt6peLVs5-9owohK7HFrVcFJ_pxnpmK8VVvRkTJikkWIiaxy1dhP23bUwW1WW5WbPrrqJ4ziR4EJ6dtVN2ls5y1ZztePUcrWZFCvqVEcPPDffvlyS1XoLIQnVgnVvVPR6FU9Zc-6dV453ojjOPbuetRJ57gIeXQR3cez7rjtteZyZQ2j5MqmjqwE0ZW0VKx9RKtgpFewp1aw3sXXFy6TWgiYlv8mfq1scD8ofbBCAfQg8_AMBOAyBxzEIwA4CxgQ99QbQkjnD2KT7_CfxGF8_9WXQEsq5sDZCcjICOXRCri4h6r3NA38Q6Jdi1EOx5w3DGDYYI6MUvJFjM3VoGHUeGoMd6mBnDmh2E3fo7O4Yhf0x4OkBmIKUyhQzol_GfbkcApXQlIYg0EOC5SoEYXbQ-3ChxHyXRSBQsqBTUOREx_7OsAY3BUGM-VqvUsKUkB_1U6vf05gtweEHTk4_HQ", + img { + src: "https://mermaid.ink/img/pako:eNqllV1vgjAUhv8K6W4wkQVa2QdLdrHsdlfukmSptEhjoaSWqTH-9xVwONAKst70g5739JzzlO5BJAgFAYi52EQJlsr6fAszS7d1sVhKnCdWJDJFt6peLVs5-9owohK7HFrVcFJ_pxnpmK8VVvRkTJikkWIiaxy1dhP23bUwW1WW5WbPrrqJ4ziR4EJ6dtVN2ls5y1ZztePUcrWZFCvqVEcPPDffvlyS1XoLIQnVgnVvVPR6FU9Zc-6dV453ojjOPbuetRJ57gIeXQR3cez7rjtteZyZQ2j5MqmjqwE0ZW0VKx9RKtgpFewp1aw3sXXFy6TWgiYlv8mfq1scD8ofbBCAfQg8_AMBOAyBxzEIwA4CxgQ99QbQkjnD2KT7_CfxGF8_9WXQEsq5sDZCcjICOXRCri4h6r3NA38Q6Jdi1EOx5w3DGDYYI6MUvJFjM3VoGHUeGoMd6mBnDmh2E3fo7O4Yhf0x4OkBmIKUyhQzol_GfbkcApXQlIYg0EOC5SoEYXbQ-3ChxHyXRSBQsqBTUOREx_7OsAY3BUGM-VqvUsKUkB_1U6vf05gtweEHTk4_HQ?type=png", + alt: "", + title: "", + } + } + } + p { + "To help in building a Dom, native-core provides the State trait and a RealDom struct. The State trait provides a way to describe how states in a node depend on other states in its relatives. By describing how to update a single node from its relations, native-core will derive a way to update the states of all nodes for you. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom." + } + p { + "Native Core cannot create all of the required methods for the State trait, but it can derive some of them. To implement the State trait, you must implement the following methods and let the " + code { "#[partial_derive_state]" } + " macro handle the rest:" + } + CodeBlock { + contents: "
\n// All states must derive Component (https://docs.rs/shipyard/latest/shipyard/derive.Component.html)\n// They also must implement Default or provide a custom implementation of create in the State trait\n#[derive(Default, Component)]\nstruct MyState;\n\n/// Derive some of the boilerplate for the State implementation\n#[partial_derive_state]\nimpl State for MyState {{\n    // The states of the parent nodes this state depends on\n    type ParentDependencies = ();\n\n    // The states of the child nodes this state depends on\n    type ChildDependencies = (Self,);\n\n    // The states of the current node this state depends on\n    type NodeDependencies = ();\n\n    // The parts of the current text, element, or placeholder node in the tree that this state depends on\n    const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new();\n\n    // How to update the state of the current node based on the state of the parent nodes, child nodes, and the current node\n    // Returns true if the node was updated and false if the node was not updated\n    fn update<'a>(\n        &mut self,\n        // The view of the current node limited to the parts this state depends on\n        _node_view: NodeView<()>,\n        // The state of the current node that this state depends on\n        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,\n        // The state of the parent nodes that this state depends on\n        _parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,\n        // The state of the child nodes that this state depends on\n        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,\n        // The context of the current node used to pass global state into the tree\n        _context: &SendAnyMap,\n    ) -> bool {{\n        todo!()\n    }}\n\n    // partial_derive_state will generate a default implementation of all the other methods\n}}
\n", + name: "custom_renderer.rs".to_string(), + } + p { "Lets take a look at how to implement the State trait for a simple renderer." } + CodeBlock { + contents: "
\nstruct FontSize(f64);\n\n// All states need to derive Component\n#[derive(Default, Debug, Copy, Clone, Component)]\nstruct Size(f64, f64);\n\n/// Derive some of the boilerplate for the State implementation\n#[partial_derive_state]\nimpl State for Size {{\n    type ParentDependencies = ();\n\n    // The size of the current node depends on the size of its children\n    type ChildDependencies = (Self,);\n\n    type NodeDependencies = ();\n\n    // Size only cares about the width, height, and text parts of the current node\n    const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()\n        // Get access to the width and height attributes\n        .with_attrs(AttributeMaskBuilder::Some(&["width", "height"]))\n        // Get access to the text of the node\n        .with_text();\n\n    fn update<'a>(\n        &mut self,\n        node_view: NodeView<()>,\n        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,\n        _parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,\n        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,\n        context: &SendAnyMap,\n    ) -> bool {{\n        let font_size = context.get::<FontSize>().unwrap().0;\n        let mut width;\n        let mut height;\n        if let Some(text) = node_view.text() {{\n            // if the node has text, use the text to size our object\n            width = text.len() as f64 * font_size;\n            height = font_size;\n        }} else {{\n            // otherwise, the size is the maximum size of the children\n            width = children\n                .iter()\n                .map(|(item,)| item.0)\n                .reduce(|accum, item| if accum >= item {{ accum }} else {{ item }})\n                .unwrap_or(0.0);\n\n            height = children\n                .iter()\n                .map(|(item,)| item.1)\n                .reduce(|accum, item| if accum >= item {{ accum }} else {{ item }})\n                .unwrap_or(0.0);\n        }}\n        // if the node contains a width or height attribute it overrides the other size\n        for a in node_view.attributes().into_iter().flatten() {{\n            match &*a.attribute.name {{\n                "width" => width = a.value.as_float().unwrap(),\n                "height" => height = a.value.as_float().unwrap(),\n                // because Size only depends on the width and height, no other attributes will be passed to the member\n                _ => panic!(),\n            }}\n        }}\n        // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed\n        let changed = (width != self.0) || (height != self.1);\n        *self = Self(width, height);\n        changed\n    }}\n}}\n\n#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]\nstruct TextColor {{\n    r: u8,\n    g: u8,\n    b: u8,\n}}\n\n#[partial_derive_state]\nimpl State for TextColor {{\n    // TextColor depends on the TextColor part of the parent\n    type ParentDependencies = (Self,);\n\n    type ChildDependencies = ();\n\n    type NodeDependencies = ();\n\n    // TextColor only cares about the color attribute of the current node\n    const NODE_MASK: NodeMaskBuilder<'static> =\n        // Get access to the color attribute\n        NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["color"]));\n\n    fn update<'a>(\n        &mut self,\n        node_view: NodeView<()>,\n        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,\n        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,\n        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,\n        _context: &SendAnyMap,\n    ) -> bool {{\n        // TextColor only depends on the color tag, so getting the first tag is equivalent to looking through all tags\n        let new = match node_view\n            .attributes()\n            .and_then(|mut attrs| attrs.next())\n            .and_then(|attr| attr.value.as_text())\n        {{\n            // if there is a color tag, translate it\n            Some("red") => TextColor {{ r: 255, g: 0, b: 0 }},\n            Some("green") => TextColor {{ r: 0, g: 255, b: 0 }},\n            Some("blue") => TextColor {{ r: 0, g: 0, b: 255 }},\n            Some(color) => panic!("unknown color {{}}", "red"),\n            // otherwise check if the node has a parent and inherit that color\n            None => match parent {{\n                Some((parent,)) => *parent,\n                None => Self::default(),\n            }},\n        }};\n        // check if the member has changed\n        let changed = new != *self;\n        *self = new;\n        changed\n    }}\n}}\n\n#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]\nstruct Border(bool);\n\n#[partial_derive_state]\nimpl State for Border {{\n    // TextColor depends on the TextColor part of the parent\n    type ParentDependencies = (Self,);\n\n    type ChildDependencies = ();\n\n    type NodeDependencies = ();\n\n    // Border does not depended on any other member in the current node\n    const NODE_MASK: NodeMaskBuilder<'static> =\n        // Get access to the border attribute\n        NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"]));\n\n    fn update<'a>(\n        &mut self,\n        node_view: NodeView<()>,\n        _node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,\n        _parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,\n        _children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,\n        _context: &SendAnyMap,\n    ) -> bool {{\n        // check if the node contains a border attribute\n        let new = Self(\n            node_view\n                .attributes()\n                .and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border"))\n                .is_some(),\n        );\n        // check if the member has changed\n        let changed = new != *self;\n        *self = new;\n        changed\n    }}\n}}
\n", + name: "custom_renderer.rs".to_string(), + } + p { + "Now that we have our state, we can put it to use in our RealDom. We can update the RealDom with apply_mutations to update the structure of the dom (adding, removing, and changing properties of nodes) and then update_state to update the States for each of the nodes that changed." + } + CodeBlock { + contents: "
\nfn main() -> Result<(), Box<dyn std::error::Error>> {{\n    fn app(cx: Scope) -> Element {{\n        let count = use_state(cx, || 0);\n\n        use_future(cx, (count,), |(count,)| async move {{\n            loop {{\n                tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n                count.set(*count + 1);\n            }}\n        }});\n\n        cx.render(rsx! {{\n            div{{\n                color: "red",\n                "{{count}}"\n            }}\n        }})\n    }}\n\n    // create the vdom, the real_dom, and the binding layer between them\n    let mut vdom = VirtualDom::new(app);\n    let mut rdom: RealDom = RealDom::new([\n        Border::to_type_erased(),\n        TextColor::to_type_erased(),\n        Size::to_type_erased(),\n    ]);\n    let mut dioxus_intigration_state = DioxusState::create(&mut rdom);\n\n    let mutations = vdom.rebuild();\n    // update the structure of the real_dom tree\n    dioxus_intigration_state.apply_mutations(&mut rdom, mutations);\n    let mut ctx = SendAnyMap::new();\n    // set the font size to 3.3\n    ctx.insert(FontSize(3.3));\n    // update the State for nodes in the real_dom tree\n    let _to_rerender = rdom.update_state(ctx);\n\n    // we need to run the vdom in a async runtime\n    tokio::runtime::Builder::new_current_thread()\n        .enable_all()\n        .build()?\n        .block_on(async {{\n            loop {{\n                // wait for the vdom to update\n                vdom.wait_for_work().await;\n\n                // get the mutations from the vdom\n                let mutations = vdom.render_immediate();\n\n                // update the structure of the real_dom tree\n                dioxus_intigration_state.apply_mutations(&mut rdom, mutations);\n\n                // update the state of the real_dom tree\n                let mut ctx = SendAnyMap::new();\n                // set the font size to 3.3\n                ctx.insert(FontSize(3.3));\n                let _to_rerender = rdom.update_state(ctx);\n\n                // render...\n                rdom.traverse_depth_first(|node| {{\n                    let indent = " ".repeat(node.height() as usize);\n                    let color = *node.get::<TextColor>().unwrap();\n                    let size = *node.get::<Size>().unwrap();\n                    let border = *node.get::<Border>().unwrap();\n                    let id = node.id();\n                    let node = node.node_type();\n                    let node_type = &*node;\n                    println!("{{indent}}{{id:?}} {{color:?}} {{size:?}} {{border:?}} {{node_type:?}}");\n                }});\n            }}\n        }})\n}}
\n", + name: "custom_renderer.rs".to_string(), + } + h2 { id: "layout", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Layout, + }, + class: "header", + "Layout" + } + } + p { + "For most platforms, the layout of the Elements will stay the same. The " + Link { to: "https://docs.rs/dioxus-native-core/latest/dioxus_native_core/layout_attributes/index.html", + "layout_attributes" + } + " module provides a way to apply HTML attributes a " + Link { to: "https://docs.rs/taffy/latest/taffy/index.html", "Taffy" } + " layout style." + } + h2 { id: "text-editing", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::TextEditing, + }, + class: "header", + "Text Editing" + } + } + p { + "To make it easier to implement text editing in rust renderers, " + code { "native-core" } + " also contains a renderer-agnostic cursor system. The cursor can handle text editing, selection, and movement with common keyboard shortcuts integrated." + } + CodeBlock { + contents: "
\nfn text_editing() {{\n    let mut cursor = Cursor::default();\n    let mut text = String::new();\n\n    // handle keyboard input with a max text length of 10\n    cursor.handle_input(\n        &Code::ArrowRight,\n        &Key::ArrowRight,\n        &Modifiers::empty(),\n        &mut text,\n        10,\n    );\n\n    // manually select text between characters 0-5 on the first line (this could be from dragging with a mouse)\n    cursor.start = Pos::new(0, 0);\n    cursor.end = Some(Pos::new(5, 0));\n\n    // delete the selected text and move the cursor to the start of the selection\n    cursor.delete_selection(&mut text);\n}}
\n", + name: "custom_renderer.rs".to_string(), + } + h2 { id: "conclusion", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Conclusion, + }, + class: "header", + "Conclusion" + } + } + p { + "That should be it! You should have nearly all the knowledge required on how to implement your renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderers, video game UI, and even augmented reality! If you're interested in contributing to any of these projects, don't be afraid to reach out or join the " + Link { to: "https://discord.gg/XgGxMSkvUM", "community" } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookOptimizingSection { + #[default] + Empty, + Optimizing, + BuildingInReleaseMode, + Upx, + BuildConfiguration, + Stable, + Unstable, + WasmOpt, + ImprovingDioxusCode, + BundlingAndMinifyingTheOutputJsAndHtml, +} +impl std::str::FromStr for CookbookOptimizingSection { + type Err = CookbookOptimizingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "optimizing" => Ok(Self::Optimizing), + "building-in-release-mode" => Ok(Self::BuildingInReleaseMode), + "upx" => Ok(Self::Upx), + "build-configuration" => Ok(Self::BuildConfiguration), + "stable" => Ok(Self::Stable), + "unstable" => Ok(Self::Unstable), + "wasm-opt" => Ok(Self::WasmOpt), + "improving-dioxus-code" => Ok(Self::ImprovingDioxusCode), + "bundling-and-minifying-the-output-js-and-html" => { + Ok(Self::BundlingAndMinifyingTheOutputJsAndHtml) + } + _ => Err(CookbookOptimizingSectionParseError), + } + } +} +impl std::fmt::Display for CookbookOptimizingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Optimizing => f.write_str("optimizing"), + Self::BuildingInReleaseMode => f.write_str("building-in-release-mode"), + Self::Upx => f.write_str("upx"), + Self::BuildConfiguration => f.write_str("build-configuration"), + Self::Stable => f.write_str("stable"), + Self::Unstable => f.write_str("unstable"), + Self::WasmOpt => f.write_str("wasm-opt"), + Self::ImprovingDioxusCode => f.write_str("improving-dioxus-code"), + Self::BundlingAndMinifyingTheOutputJsAndHtml => { + f.write_str("bundling-and-minifying-the-output-js-and-html") + } + } + } +} +#[derive(Debug)] +pub struct CookbookOptimizingSectionParseError; +impl std::fmt::Display for CookbookOptimizingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookOptimizingSectionoptimizing, building-in-release-mode, upx, build-configuration, stable, unstable, wasm-opt, improving-dioxus-code, bundling-and-minifying-the-output-js-and-html", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookOptimizingSectionParseError {} +#[component(no_case_check)] +pub fn CookbookOptimizing(section: CookbookOptimizingSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "optimizing", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Optimizing, + }, + class: "header", + "Optimizing" + } + } + p { + em { + "Note: This is written primarily for the web, but the main optimizations will work on other platforms too." + } + } + p { + "You might have noticed that Dioxus binaries are pretty big." + " " + "The WASM binary of a " + Link { to: "https://github.com/tigerros/dioxus-todo-app", "TodoMVC app" } + " weighs in at 2.36mb!" + " " + "Don't worry; we can get it down to a much more manageable 234kb." + " " + "This will get obviously lower over time." + " " + "For example, the new " + Link { to: "https://github.com/DioxusLabs/dioxus/pull/1402", "event system" } + " will reduce the binary size of a hello world app to less than 100kb (with unstable features)." + } + p { "We will also discuss ways to optimize your app for increased speed." } + p { + "However, certain optimizations will sacrifice speed for decreased binary size or the other way around." + " " + "That's what you need to figure out yourself. Does your app perform performance-intensive tasks, such as graphical processing or tons of DOM manipulations?" + " " + "You could go for increased speed. In most cases, though, decreased binary size is the better choice, especially because Dioxus WASM binaries are quite large." + } + p { + "To test binary sizes, we will use " + Link { to: "https://github.com/tigerros/dioxus-todo-app", "this" } + " repository as a sample app." + " " + "The " + code { "no-optimizations" } + " package will serve as the base, which weighs 2.36mb as of right now." + } + p { "Additional resources:" } + ul { + li { + Link { to: "https://rustwasm.github.io/docs/book/reference/code-size.html", + "WASM book - Shrinking " + code { ".wasm" } + " code size" + } + } + li { + Link { to: "https://github.com/johnthagen/min-sized-rust", "min-sized-rust" } + } + } + h2 { id: "building-in-release-mode", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::BuildingInReleaseMode, + }, + class: "header", + "Building in release mode" + } + } + p { + "This is the best way to optimize. In fact, the 2.36mb figure at the start of the guide is with release mode." + " " + "In debug mode, it's actually a whopping 32mb! It also increases the speed of your app." + } + p { + "Thankfully, no matter what tool you're using to build your app, it will probably have a " + code { "--release" } + " flag to do this." + } + p { + "Using the " + Link { to: "https://dioxuslabs.com/learn/0.4/CLI", "Dioxus CLI" } + " or " + Link { to: "https://trunkrs.dev/", "Trunk" } + ":" + } + ul { + li { + "Dioxus CLI: " + code { "dx build --release" } + } + li { + "Trunk: " + code { "trunk build --release" } + } + } + h2 { id: "upx", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Upx, + }, + class: "header", + "UPX" + } + } + p { + "If you're not targeting web, you can use the " + Link { to: "https://github.com/upx/upx", "UPX" } + " CLI tool to compress your executables." + } + p { "Setup:" } + ul { + li { + "Download a " + Link { to: "https://github.com/upx/upx/releases", "release" } + " and extract the directory inside to a sensible location." + } + li { "Add the executable located in the directory to your path variable." } + } + p { + "You can run " + code { "upx --help" } + " to get the CLI options, but you should also view " + code { "upx-doc.html" } + " for more detailed information." + " " + "It's included in the extracted directory." + } + p { + "An example command might be: " + code { "upx --best -o target/release/compressed.exe target/release/your-executable.exe" } + "." + } + h2 { id: "build-configuration", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::BuildConfiguration, + }, + class: "header", + "Build configuration" + } + } + p { + em { + "Note: Settings defined in " + code { ".cargo/config.toml" } + " will override settings in " + code { "Cargo.toml" } + "." + } + } + p { + "Other than the " + code { "--release" } + " flag, this is the easiest way to optimize your projects, and also the most effective way," + " " + "at least in terms of reducing binary size." + } + h3 { id: "stable", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Stable, + }, + class: "header", + "Stable" + } + } + p { + "This configuration is 100% stable and decreases the binary size from 2.36mb to 310kb." + " " + "Add this to your " + code { ".cargo/config.toml" } + ":" + } + CodeBlock { + contents: "
\n[profile.release]\nopt-level = "z"\ndebug = false\nlto = true\ncodegen-units = 1\npanic = "abort"\nstrip = true\nincremental = false
\n", + } + p { "Links to the documentation of each value:" } + ul { + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#opt-level", + code { "opt-level" } + } + } + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#debuginfo", + code { "debug" } + } + } + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#lto", + code { "lto" } + } + } + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#codegen-units", + code { "codegen-units" } + } + } + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#panic", + code { "panic" } + } + } + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#strip", + code { "strip" } + } + } + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#incremental", + code { "incremental" } + } + } + } + h3 { id: "unstable", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Unstable, + }, + class: "header", + "Unstable" + } + } + p { + "This configuration contains some unstable features, but it should work just fine." + " " + "It decreases the binary size from 310kb to 234kb." + " " + "Add this to your " + code { ".cargo/config.toml" } + ":" + } + CodeBlock { + contents: "
\n[unstable]\nbuild-std = ["std", "panic_abort", "core", "alloc"]\nbuild-std-features = ["panic_immediate_abort"]\n\n[build]\nrustflags = [\n    "-Clto",\n    "-Zvirtual-function-elimination",\n    "-Zlocation-detail=none"\n]\n\n# Same as in the Stable section\n[profile.release]\nopt-level = "z"\ndebug = false\nlto = true\ncodegen-units = 1\npanic = "abort"\nstrip = true\nincremental = false
\n", + } + p { + em { + "Note: The omitted space in each flag (e.g., " + code { "-Clto" } + ") is intentional. It is not a typo." + } + } + p { + "The values in " + code { "[profile.release]" } + " are documented in the " + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Stable, + }, + "Stable" + } + " section. Links to the documentation of each value:" + } + ul { + li { + Link { to: "https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags", + code { "[build.rustflags]" } + } + } + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#lto", + code { "-C lto" } + } + } + li { + Link { to: "https://doc.rust-lang.org/stable/unstable-book/compiler-flags/virtual-function-elimination.html", + code { "-Z virtual-function-elimination" } + } + } + li { + Link { to: "https://doc.rust-lang.org/stable/unstable-book/compiler-flags/location-detail.html", + code { "-Z location-detail" } + } + } + } + h2 { id: "wasm-opt", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::WasmOpt, + }, + class: "header", + "wasm-opt" + } + } + p { + em { + "Note: In the future, " + code { "wasm-opt" } + " will be supported natively through the " + Link { to: "https://crates.io/crates/dioxus-cli", "Dioxus CLI" } + "." + } + } + p { + code { "wasm-opt" } + " is a tool from the " + Link { to: "https://github.com/WebAssembly/binaryen", "binaryen" } + " library that optimizes your WASM files." + " " + "To use it, install a " + Link { to: "https://github.com/WebAssembly/binaryen/releases", "binaryen release" } + " and run this command from the package directory:" + } + CodeBlock { contents: "
\nwasm-opt dist/assets/dioxus/APP_NAME_bg.wasm -o dist/assets/dioxus/APP_NAME_bg.wasm -Oz
\n" } + p { + "The " + code { "-Oz" } + " flag specifies that " + code { "wasm-opt" } + " should optimize for size. For speed, use " + code { "-O4" } + "." + } + h2 { id: "improving-dioxus-code", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::ImprovingDioxusCode, + }, + class: "header", + "Improving Dioxus code" + } + } + p { "Let's talk about how you can improve your Dioxus code to be more performant." } + p { + "It's important to minimize the number of dynamic parts in your " + code { "rsx" } + ", like conditional rendering." + " " + "When Dioxus is rendering your component, it will skip parts that are the same as the last render." + " " + "That means that if you keep dynamic rendering to a minimum, your app will speed up, and quite a bit if it's not just hello world." + " " + "To see an example of this, check out " + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::Empty, + }, + "Dynamic Rendering" + } + "." + } + p { + "Also check out " + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::Empty, + }, + "Anti-patterns" + } + " for patterns that you should avoid." + " " + "Obviously, not all of them are just about performance, but some of them are." + } + h2 { id: "bundling-and-minifying-the-output-js-and-html", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::BundlingAndMinifyingTheOutputJsAndHtml, + }, + class: "header", + "Bundling and minifying the output JS and HTML" + } + } + p { + "This will be added in " + Link { to: "https://github.com/DioxusLabs/dioxus/pull/1369", "dioxus/#1369" } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CliIndexSection { + #[default] + Empty, + Introduction, + Features, +} +impl std::str::FromStr for CliIndexSection { + type Err = CliIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "introduction" => Ok(Self::Introduction), + "features" => Ok(Self::Features), + _ => Err(CliIndexSectionParseError), + } + } +} +impl std::fmt::Display for CliIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Introduction => f.write_str("introduction"), + Self::Features => f.write_str("features"), + } + } +} +#[derive(Debug)] +pub struct CliIndexSectionParseError; +impl std::fmt::Display for CliIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of CliIndexSectionintroduction, features")?; + Ok(()) + } +} +impl std::error::Error for CliIndexSectionParseError {} +#[component(no_case_check)] +pub fn CliIndex(section: CliIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "introduction", + Link { + to: BookRoute::CliIndex { + section: CliIndexSection::Introduction, + }, + class: "header", + "Introduction" + } + } + p { + "The ✨" + strong { "Dioxus CLI" } + "✨ is a tool to get Dioxus projects off the ground." + } + p { + "There's no documentation for commands here, but you can see all commands using " + code { "dx --help" } + " once you've installed the CLI! Furthermore, you can run " + code { "dx --help" } + " to get help with a specific command." + } + h2 { id: "features", + Link { + to: BookRoute::CliIndex { + section: CliIndexSection::Features, + }, + class: "header", + "Features" + } + } + ul { + li { "Build and pack a Dioxus project." } + li { + "Format " + code { "rsx" } + " code." + } + li { + "Hot Reload for " + code { "web" } + " platform." + } + li { "Create a Dioxus project from a template repository." } + li { "And more!" } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CliInstallationSection { + #[default] + Empty, + Installation, + InstallTheStableVersionRecommended, + InstallTheLatestDevelopmentBuildThroughGit, +} +impl std::str::FromStr for CliInstallationSection { + type Err = CliInstallationSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "installation" => Ok(Self::Installation), + "install-the-stable-version-recommended" => { + Ok(Self::InstallTheStableVersionRecommended) + } + "install-the-latest-development-build-through-git" => { + Ok(Self::InstallTheLatestDevelopmentBuildThroughGit) + } + _ => Err(CliInstallationSectionParseError), + } + } +} +impl std::fmt::Display for CliInstallationSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Installation => f.write_str("installation"), + Self::InstallTheStableVersionRecommended => { + f.write_str("install-the-stable-version-recommended") + } + Self::InstallTheLatestDevelopmentBuildThroughGit => { + f.write_str("install-the-latest-development-build-through-git") + } + } + } +} +#[derive(Debug)] +pub struct CliInstallationSectionParseError; +impl std::fmt::Display for CliInstallationSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CliInstallationSectioninstallation, install-the-stable-version-recommended, install-the-latest-development-build-through-git", + )?; + Ok(()) + } +} +impl std::error::Error for CliInstallationSectionParseError {} +#[component(no_case_check)] +pub fn CliInstallation(section: CliInstallationSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "installation", + Link { + to: BookRoute::CliInstallation { + section: CliInstallationSection::Installation, + }, + class: "header", + "Installation" + } + } + h2 { id: "install-the-stable-version-recommended", + Link { + to: BookRoute::CliInstallation { + section: CliInstallationSection::InstallTheStableVersionRecommended, + }, + class: "header", + "Install the stable version (recommended)" + } + } + CodeBlock { contents: "
\ncargo install dioxus-cli
\n" } + p { + "If you get an OpenSSL error on installation, ensure the dependencies listed " + Link { to: "https://docs.rs/openssl/latest/openssl/#automatic", "here" } + " are installed." + } + h2 { id: "install-the-latest-development-build-through-git", + Link { + to: BookRoute::CliInstallation { + section: CliInstallationSection::InstallTheLatestDevelopmentBuildThroughGit, + }, + class: "header", + "Install the latest development build through git" + } + } + p { + "To get the latest bug fixes and features, you can install the development version from git. However, this is not fully tested. That means you're probably going to have more bugs despite having the latest bug fixes." + } + CodeBlock { contents: "
\ncargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli
\n" } + p { + "This will download the CLI from the master branch, and install it in Cargo's global binary directory ( " + code { "~/.cargo/bin/" } + " by default)." + } + p { + "Run " + code { "dx --help" } + " for a list of all the available commands. Furthermore, you can run " + code { "dx --help" } + " to get help with a specific command." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CliCreatingSection { + #[default] + Empty, + CreateAProject, + InitializingAProject, +} +impl std::str::FromStr for CliCreatingSection { + type Err = CliCreatingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "create-a-project" => Ok(Self::CreateAProject), + "initializing-a-project" => Ok(Self::InitializingAProject), + _ => Err(CliCreatingSectionParseError), + } + } +} +impl std::fmt::Display for CliCreatingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::CreateAProject => f.write_str("create-a-project"), + Self::InitializingAProject => f.write_str("initializing-a-project"), + } + } +} +#[derive(Debug)] +pub struct CliCreatingSectionParseError; +impl std::fmt::Display for CliCreatingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CliCreatingSectioncreate-a-project, initializing-a-project", + )?; + Ok(()) + } +} +impl std::error::Error for CliCreatingSectionParseError {} +#[component(no_case_check)] +pub fn CliCreating(section: CliCreatingSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "create-a-project", + Link { + to: BookRoute::CliCreating { + section: CliCreatingSection::CreateAProject, + }, + class: "header", + "Create a Project" + } + } + p { "Once you have the Dioxus CLI installed, you can use it to create your own project!" } + h2 { id: "initializing-a-project", + Link { + to: BookRoute::CliCreating { + section: CliCreatingSection::InitializingAProject, + }, + class: "header", + "Initializing a project" + } + } + p { + "First, run the " + code { "dx create" } + " command to create a new project." + } + blockquote { + p { + "It clones this " + Link { to: "https://github.com/DioxusLabs/dioxus-template", "template" } + ", which is used for web apps." + } + p { + "You can create your project from a different template by passing the " + code { "template" } + " argument:" + } + CodeBlock { contents: "
\ndx create --template gh:dioxuslabs/dioxus-template
\n" } + } + p { + "Next, navigate into your new project using " + code { "cd project-name" } + ", or simply opening it in an IDE." + } + blockquote { + p { + "Make sure the WASM target is installed before running the projects." + " " + "You can install the WASM target for rust using rustup:" + } + CodeBlock { contents: "
\nrustup target add wasm32-unknown-unknown
\n" } + } + p { + "Finally, serve your project with " + code { "dx serve" } + "! The CLI will tell you the address it is serving on, along with additional info such as code warnings." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CliConfigureSection { + #[default] + Empty, + ConfigureProject, + Structure, + Application, + Webapp, + Webwatcher, + Webresource, + Webresourcedev, + Webproxy, + ConfigExample, + DesktopAndTui, +} +impl std::str::FromStr for CliConfigureSection { + type Err = CliConfigureSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "configure-project" => Ok(Self::ConfigureProject), + "structure" => Ok(Self::Structure), + "application-" => Ok(Self::Application), + "webapp-" => Ok(Self::Webapp), + "webwatcher-" => Ok(Self::Webwatcher), + "webresource-" => Ok(Self::Webresource), + "webresourcedev-" => Ok(Self::Webresourcedev), + "webproxy" => Ok(Self::Webproxy), + "config-example" => Ok(Self::ConfigExample), + "desktop-and-tui" => Ok(Self::DesktopAndTui), + _ => Err(CliConfigureSectionParseError), + } + } +} +impl std::fmt::Display for CliConfigureSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ConfigureProject => f.write_str("configure-project"), + Self::Structure => f.write_str("structure"), + Self::Application => f.write_str("application-"), + Self::Webapp => f.write_str("webapp-"), + Self::Webwatcher => f.write_str("webwatcher-"), + Self::Webresource => f.write_str("webresource-"), + Self::Webresourcedev => f.write_str("webresourcedev-"), + Self::Webproxy => f.write_str("webproxy"), + Self::ConfigExample => f.write_str("config-example"), + Self::DesktopAndTui => f.write_str("desktop-and-tui"), + } + } +} +#[derive(Debug)] +pub struct CliConfigureSectionParseError; +impl std::fmt::Display for CliConfigureSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CliConfigureSectionconfigure-project, structure, application-, webapp-, webwatcher-, webresource-, webresourcedev-, webproxy, config-example, desktop-and-tui", + )?; + Ok(()) + } +} +impl std::error::Error for CliConfigureSectionParseError {} +#[component(no_case_check)] +pub fn CliConfigure(section: CliConfigureSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "configure-project", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::ConfigureProject, + }, + class: "header", + "Configure Project" + } + } + p { + "This chapter will teach you how to configure the CLI with the " + code { "Dioxus.toml" } + " file. There's an " + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::ConfigExample, + }, + "example" + } + " which has comments to describe individual keys. You can copy that or view this documentation for a more complete learning experience." + } + p { + "\"🔒\" indicates a mandatory item. Some headers are mandatory, but none of the keys inside them are. In that case, you only need to include the header, but no keys. It might look weird, but it's normal." + } + h2 { id: "structure", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Structure, + }, + class: "header", + "Structure" + } + } + p { "Each header has its TOML form directly under it." } + h3 { id: "application-", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Application, + }, + class: "header", + "Application 🔒" + } + } + CodeBlock { contents: "
\n[application]
\n" } + p { "Application-wide configuration. Applies to both web and desktop." } + ul { + li { + strong { "name" } + " 🔒 - Project name & title." + CodeBlock { contents: "
\nname = "my_project"
\n" } + } + li { + strong { "default_platform" } + " 🔒 - The platform this project targets" + CodeBlock { contents: "
\n# Currently supported platforms: web, desktop\ndefault_platform = "web"
\n" } + } + li { + strong { "out_dir" } + " - The directory to place the build artifacts from " + code { "dx build" } + " or " + code { "dx serve" } + " into. This is also where the " + code { "assets" } + " directory will be copied into." + CodeBlock { contents: "
\nout_dir = "dist"
\n" } + } + li { + strong { "asset_dir" } + " - The directory with your static assets. The CLI will automatically copy these assets into the " + strong { "out_dir" } + " after a build/serve." + CodeBlock { contents: "
\nasset_dir = "public"
\n" } + } + li { + strong { "sub_package" } + " - The sub package in the workspace to build by default." + CodeBlock { contents: "
\nsub_package = "my-crate"
\n" } + } + } + h3 { id: "webapp-", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Webapp, + }, + class: "header", + "Web.App 🔒" + } + } + CodeBlock { contents: "
\n[web.app]
\n" } + p { "Web-specific configuration." } + ul { + li { + strong { "title" } + " - The title of the web page." + CodeBlock { contents: "
\n# HTML title tag content\ntitle = "project_name"
\n" } + } + li { + strong { "base_path" } + " - The base path to build the application for serving at. This can be useful when serving your application in a subdirectory under a domain. For example, when building a site to be served on GitHub Pages." + CodeBlock { contents: "
\n# The application will be served at domain.com/my_application/, so we need to modify the base_path to the path where the application will be served\nbase_path = "my_application"
\n" } + } + } + h3 { id: "webwatcher-", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Webwatcher, + }, + class: "header", + "Web.Watcher 🔒" + } + } + CodeBlock { contents: "
\n[web.watcher]
\n" } + p { "Development server configuration." } + ul { + li { + p { + strong { "reload_html" } + " - If this is true, the cli will rebuild the index.html file every time the application is rebuilt" + } + CodeBlock { contents: "
\nreload_html = true
\n" } + } + li { + p { + strong { "watch_path" } + " - The files & directories to monitor for changes" + } + CodeBlock { contents: "
\nwatch_path = ["src", "public"]
\n" } + } + li { + p { + strong { "index_on_404" } + " - If enabled, Dioxus will serve the root page when a route is not found." + em { "This is needed when serving an application that uses the router" } + ". However, when serving your app using something else than Dioxus (e.g. GitHub Pages), you will have to check how to configure it on that platform. In GitHub Pages, you can make a copy of " + code { "index.html" } + " named " + code { "404.html" } + " in the same directory." + } + CodeBlock { contents: "
\nindex_on_404 = true
\n" } + } + } + h3 { id: "webresource-", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Webresource, + }, + class: "header", + "Web.Resource 🔒" + } + } + CodeBlock { contents: "
\n[web.resource]
\n" } + p { "Static resource configuration." } + ul { + li { + p { + strong { "style" } + " - CSS files to include in your application." + } + CodeBlock { contents: "
\nstyle = [\n   # Include from public_dir.\n   "./assets/style.css",\n   # Or some asset from online cdn.\n   "https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css"\n]
\n" } + } + li { + p { + strong { "script" } + " - JavaScript files to include in your application." + } + CodeBlock { contents: "
\nscript = [\n    # Include from asset_dir.\n    "./public/index.js",\n    # Or from an online CDN.\n    "https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js"\n]
\n" } + } + } + h3 { id: "webresourcedev-", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Webresourcedev, + }, + class: "header", + "Web.Resource.Dev 🔒" + } + } + CodeBlock { contents: "
\n[web.resource.dev]
\n" } + p { + "This is the same as " + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Webresource, + }, + code { "[web.resource]" } + } + ", but it only works in development servers. For example, if you want to include a file in a " + code { "dx serve" } + " server, but not a " + code { "dx serve --release" } + " server, put it here." + } + h3 { id: "webproxy", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Webproxy, + }, + class: "header", + "Web.Proxy" + } + } + CodeBlock { contents: "
\n[[web.proxy]]
\n" } + p { + "Configuration related to any proxies your application requires during development. Proxies will forward requests to a new service." + } + ul { + li { + strong { "backend" } + " - The URL to the server to proxy. The CLI will forward any requests under the backend relative route to the backend instead of returning 404" + CodeBlock { contents: "
\nbackend = "http://localhost:8000/api/"
\n" } + "This will cause any requests made to the dev server with prefix /api/ to be redirected to the backend server at http://localhost:8000. The path and query parameters will be passed on as-is (path rewriting is currently not supported)." + } + } + h2 { id: "config-example", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::ConfigExample, + }, + class: "header", + "Config example" + } + } + p { "This includes all fields, mandatory or not." } + CodeBlock { + contents: "
\n[application]\n\n# App name\nname = "project_name"\n\n# The Dioxus platform to default to\ndefault_platform = "web"\n\n# `build` & `serve` output path\nout_dir = "dist"\n\n# The static resource path\nasset_dir = "public"\n\n[web.app]\n\n# HTML title tag content\ntitle = "project_name"\n\n[web.watcher]\n\n# When watcher is triggered, regenerate the `index.html`\nreload_html = true\n\n# Which files or dirs will be monitored\nwatch_path = ["src", "public"]\n\n# Include style or script assets\n[web.resource]\n\n# CSS style file\nstyle = []\n\n# Javascript code file\nscript = []\n\n[web.resource.dev]\n\n# Same as [web.resource], but for development servers\n\n# CSS style file\nstyle = []\n\n# JavaScript files\nscript = []\n\n[[web.proxy]]\nbackend = "http://localhost:8000/api/"
\n", + } + h2 { id: "desktop-and-tui", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::DesktopAndTui, + }, + class: "header", + "Desktop and TUI" + } + } + p { + "For the desktop and TUI (terminal user interface) renderers," + " " + "Dioxus bundles your app using " + code { "dx bundle" } + " and the " + Link { to: "https://docs.rs/crate/tauri-bundler/latest", "tauri-bundler" } + "." + } + p { + "You can check out the " + Link { to: "https://docs.rs/tauri-bundler/latest/tauri_bundler/bundle/index.html", + "tauri-bundler docs.rs" + } + "." + " " + "This covers all the different settings. Keep in mind that " + code { "FooSettings" } + " becomes just " + code { "foo" } + " in the TOML." + } + p { + "Tauri uses a JSON file for the configuration, but Dioxus uses TOML. So this tauri-bundler example:" + } + CodeBlock { + contents: "
\n{{\n  "package": {{\n    "productName": "Your Awesome App",\n    "version": "0.1.0"\n  }},\n  "tauri": {{\n    "bundle": {{\n      "active": true,\n      "identifier": "com.my.app",\n      "shortDescription": "",\n      "longDescription": "",\n      "copyright": "Copyright (c) You 2021. All rights reserved.",\n      "icon": [\n        "icons/32x32.png",\n        "icons/128x128.png",\n        "icons/128x128@2x.png",\n        "icons/icon.icns",\n        "icons/icon.ico"\n      ],\n      "resources": ["./assets/**/*.png"],\n      "deb": {{\n        "depends": ["debian-dependency1", "debian-dependency2"]\n      }},\n      "macOS": {{\n        "frameworks": [],\n        "minimumSystemVersion": "10.11",\n        "license": "./LICENSE"\n      }},\n      "externalBin": ["./sidecar-app"]\n    }}\n  }}\n}}
\n", + } + p { "needs to be translated to TOML." } + p { + "However, Dioxus has Dioxus-specific mandatory TOML fields that we need to include as well." + " " + "We can see what fields are mandatory from the documentation above." + } + p { + "Additionally, we also need to remove " + code { "tauri" } + " from the TOML headers." + } + p { + "This is our final " + code { "Dioxus.toml" } + ":" + } + CodeBlock { + contents: "
\n# From Dioxus\n[application]\nname = "Your Awesome App"\ndefault_platform = "desktop"\n\n# You might only be running on desktop, but the following "web" values are still required.\n[web.app]\ntitle = "Awesome"\n\n[web.watcher]\n\n[web.resource.dev]\n\n# From the tauri-bundler.\n[package]\nproductName = "Your Awesome App"\nversion = "0.1.0"\n\n[bundle]\nactive = true\nidentifier = "com.my.app"\nshortDescription = ""\nlongDescription = ""\ncopyright = "Copyright (c) You 2021. All rights reserved."\nicon = [\n  "icons/32x32.png",\n  "icons/128x128.png",\n  "icons/128x128@2x.png",\n  "icons/icon.icns",\n  "icons/icon.ico"\n]\nresources = [ "./assets/**/*.png" ]\nexternalBin = [ "./sidecar-app" ]\n\n[bundle.deb]\ndepends = [ "debian-dependency1", "debian-dependency2" ]\n\n[bundle.macOS]\nframeworks = [ ]\nminimumSystemVersion = "10.11"\nlicense = "./LICENSE"
\n", + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CliTranslateSection { + #[default] + Empty, + TranslatingExistingHtml, + Usage, +} +impl std::str::FromStr for CliTranslateSection { + type Err = CliTranslateSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "translating-existing-html" => Ok(Self::TranslatingExistingHtml), + "usage" => Ok(Self::Usage), + _ => Err(CliTranslateSectionParseError), + } + } +} +impl std::fmt::Display for CliTranslateSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::TranslatingExistingHtml => f.write_str("translating-existing-html"), + Self::Usage => f.write_str("usage"), + } + } +} +#[derive(Debug)] +pub struct CliTranslateSectionParseError; +impl std::fmt::Display for CliTranslateSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CliTranslateSectiontranslating-existing-html, usage", + )?; + Ok(()) + } +} +impl std::error::Error for CliTranslateSectionParseError {} +#[component(no_case_check)] +pub fn CliTranslate(section: CliTranslateSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "translating-existing-html", + Link { + to: BookRoute::CliTranslate { + section: CliTranslateSection::TranslatingExistingHtml, + }, + class: "header", + "Translating existing HTML" + } + } + p { + "Dioxus uses a custom format called RSX to represent the HTML because it is more concise and looks more like Rust code. However, it can be a pain to convert existing HTML to RSX. That's why Dioxus comes with a tool called " + code { "dx translate" } + " that can automatically convert HTML to RSX!" + } + p { + "Dx translate can make converting large chunks of HTML to RSX much easier! Lets try translating some of the HTML from the Dioxus homepage:" + } + CodeBlock { + contents: "
\ndx translate --raw  "<div class=\\"relative w-full mx-4 sm:mx-auto text-gray-600\\"><div class=\\"text-[3em] md:text-[5em] font-semibold dark:text-white text-ghdarkmetal font-sans py-12 flex flex-col\\"><span>Fullstack, crossplatform,</span><span>lightning fast, fully typed.</span></div><h3 class=\\"text-[2em] dark:text-white font-extralight text-ghdarkmetal pt-4 max-w-screen-md mx-auto\\">Dioxus is a Rust library for building apps that run on desktop, web, mobile, and more.</h3><div class=\\"pt-12 text-white text-[1.2em] font-sans font-bold flex flex-row justify-center space-x-4\\"><a href=\\"/learn/0.4/getting_started\\" dioxus-prevent-default=\\"onclick\\" class=\\"bg-red-600 py-2 px-8 hover:-translate-y-2 transition-transform duration-300\\" data-dioxus-id=\\"216\\">Quickstart</a><a href=\\"/learn/0.4/reference\\" dioxus-prevent-default=\\"onclick\\" class=\\"bg-blue-500 py-2 px-8 hover:-translate-y-2 transition-transform duration-300\\" data-dioxus-id=\\"214\\">Read the docs</a></div><div class=\\"max-w-screen-2xl mx-auto pt-36\\"><h1 class=\\"text-md\\">Trusted by top companies</h1><div class=\\"pt-4 flex flex-row flex-wrap justify-center\\"><div class=\\"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\\"><img src=\\"static/futurewei_bw.png\\"></div><div class=\\"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\\"><img src=\\"static/airbuslogo.svg\\"></div><div class=\\"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\\"><img src=\\"static/ESA_logo.svg\\"></div><div class=\\"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\\"><img src=\\"static/yclogo.svg\\"></div><div class=\\"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\\"><img src=\\"static/satellite.webp\\"></div></div></div></div>"
\n", + } + p { "We get the following RSX you can easily copy and paste into your code:" } + CodeBlock { + contents: "
\ndiv {{ class: "relative w-full mx-4 sm:mx-auto text-gray-600",\n   div {{ class: "text-[3em] md:text-[5em] font-semibold dark:text-white text-ghdarkmetal font-sans py-12 flex flex-col",\n      span {{ "Fullstack, crossplatform," }}\n      span {{ "lightning fast, fully typed." }}\n   }}\n   h3 {{ class: "text-[2em] dark:text-white font-extralight text-ghdarkmetal pt-4 max-w-screen-md mx-auto",\n      "Dioxus is a Rust library for building apps that run on desktop, web, mobile, and more."\n   }}\n   div {{ class: "pt-12 text-white text-[1.2em] font-sans font-bold flex flex-row justify-center space-x-4",\n      a {{\n         href: "/learn/0.4/getting_started",\n         data_dioxus_id: "216",\n         dioxus_prevent_default: "onclick",\n         class: "bg-red-600 py-2 px-8 hover:-translate-y-2 transition-transform duration-300",\n         "Quickstart"\n      }}\n      a {{\n         dioxus_prevent_default: "onclick",\n         href: "/learn/0.4/reference",\n         data_dioxus_id: "214",\n         class: "bg-blue-500 py-2 px-8 hover:-translate-y-2 transition-transform duration-300",\n         "Read the docs"\n      }}\n   }}\n   div {{ class: "max-w-screen-2xl mx-auto pt-36",\n      h1 {{ class: "text-md", "Trusted by top companies" }}\n      div {{ class: "pt-4 flex flex-row flex-wrap justify-center",\n         div {{ class: "h-12 w-40 bg-black p-2 m-4 flex justify-center items-center",\n            img {{ src: "static/futurewei_bw.png" }}\n         }}\n         div {{ class: "h-12 w-40 bg-black p-2 m-4 flex justify-center items-center",\n            img {{ src: "static/airbuslogo.svg" }}\n         }}\n         div {{ class: "h-12 w-40 bg-black p-2 m-4 flex justify-center items-center",\n            img {{ src: "static/ESA_logo.svg" }}\n         }}\n         div {{ class: "h-12 w-40 bg-black p-2 m-4 flex justify-center items-center",\n            img {{ src: "static/yclogo.svg" }}\n         }}\n         div {{ class: "h-12 w-40 bg-black p-2 m-4 flex justify-center items-center",\n            img {{ src: "static/satellite.webp" }}\n         }}\n      }}\n   }}\n}}
\n", + } + h2 { id: "usage", + Link { + to: BookRoute::CliTranslate { + section: CliTranslateSection::Usage, + }, + class: "header", + "Usage" + } + } + p { + "The " + code { "dx translate" } + " command has several flags you can use to control your html input and rsx output." + } + p { + "You can use the " + code { "--file" } + " flag to translate an HTML file to RSX:" + } + CodeBlock { contents: "
\ndx translate --file index.html
\n" } + p { + "Or you can use the " + code { "--raw" } + " flag to translate a string of HTML to RSX:" + } + CodeBlock { contents: "
\ndx translate --raw "<div>Hello world</div>"
\n" } + p { "Both of those commands will output the following RSX:" } + CodeBlock { contents: "
\ndiv {{ "Hello world" }}
\n" } + p { + "The " + code { "dx translate" } + " command will output the RSX to stdout. You can use the " + code { "--output" } + " flag to write the RSX to a file instead." + } + CodeBlock { contents: "
\ndx translate --raw "<div>Hello world</div>" --output index.rs
\n" } + p { + "You can automatically create a component with the " + code { "--component" } + " flag." + } + CodeBlock { contents: "
\ndx translate --raw "<div>Hello world</div>" --component
\n" } + p { "This will output the following component:" } + CodeBlock { contents: "
\nfn component(cx: Scope) -> Element {{\n   cx.render(rsx! {{\n      div {{ "Hello world" }}\n   }})\n}}
\n" } + p { + "To learn more about the different flags " + code { "dx translate" } + " supports, run " + code { "dx translate --help" } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ContributingIndexSection { + #[default] + Empty, + Contributing, + ImprovingDocs, + WorkingOnTheEcosystem, + BugsFeatures, + BeforeYouContribute, + HowToTestDioxusWithLocalCrate, +} +impl std::str::FromStr for ContributingIndexSection { + type Err = ContributingIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "contributing" => Ok(Self::Contributing), + "improving-docs" => Ok(Self::ImprovingDocs), + "working-on-the-ecosystem" => Ok(Self::WorkingOnTheEcosystem), + "bugs--features" => Ok(Self::BugsFeatures), + "before-you-contribute" => Ok(Self::BeforeYouContribute), + "how-to-test-dioxus-with-local-crate" => Ok(Self::HowToTestDioxusWithLocalCrate), + _ => Err(ContributingIndexSectionParseError), + } + } +} +impl std::fmt::Display for ContributingIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Contributing => f.write_str("contributing"), + Self::ImprovingDocs => f.write_str("improving-docs"), + Self::WorkingOnTheEcosystem => f.write_str("working-on-the-ecosystem"), + Self::BugsFeatures => f.write_str("bugs--features"), + Self::BeforeYouContribute => f.write_str("before-you-contribute"), + Self::HowToTestDioxusWithLocalCrate => { + f.write_str("how-to-test-dioxus-with-local-crate") + } + } + } +} +#[derive(Debug)] +pub struct ContributingIndexSectionParseError; +impl std::fmt::Display for ContributingIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ContributingIndexSectioncontributing, improving-docs, working-on-the-ecosystem, bugs--features, before-you-contribute, how-to-test-dioxus-with-local-crate", + )?; + Ok(()) + } +} +impl std::error::Error for ContributingIndexSectionParseError {} +#[component(no_case_check)] +pub fn ContributingIndex(section: ContributingIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "contributing", + Link { + to: BookRoute::ContributingIndex { + section: ContributingIndexSection::Contributing, + }, + class: "header", + "Contributing" + } + } + p { + "Development happens in the " + Link { to: "https://github.com/DioxusLabs/dioxus", "Dioxus GitHub repository" } + ". If you've found a bug or have an idea for a feature, please submit an issue (but first check if someone hasn't " + Link { to: "https://github.com/DioxusLabs/dioxus/issues", "done it already" } + ")." + } + p { + Link { to: "https://github.com/DioxusLabs/dioxus/discussions", "GitHub discussions" } + " can be used as a place to ask for help or talk about features. You can also join " + Link { to: "https://discord.gg/XgGxMSkvUM", "our Discord channel" } + " where some development discussion happens." + } + h2 { id: "improving-docs", + Link { + to: BookRoute::ContributingIndex { + section: ContributingIndexSection::ImprovingDocs, + }, + class: "header", + "Improving Docs" + } + } + p { + "If you'd like to improve the docs, PRs are welcome! Both Rust docs (" + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages", "source" } + ") and this guide (" + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/docs/guide", + "source" + } + ") can be found in the GitHub repo." + } + h2 { id: "working-on-the-ecosystem", + Link { + to: BookRoute::ContributingIndex { + section: ContributingIndexSection::WorkingOnTheEcosystem, + }, + class: "header", + "Working on the Ecosystem" + } + } + p { + "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 many people would benefit from, it will be appreciated. You can " + Link { to: "https://www.npmjs.com/search?q=keywords:react-component", "browse npm.js" } + " for inspiration. Once you are done, add your library to the " + Link { to: "https://github.com/DioxusLabs/awesome-dioxus", "awesome dioxus" } + " list or share it in the " + code { "#I-made-a-thing" } + " channel on " + Link { to: "https://discord.gg/XgGxMSkvUM", "Discord" } + "." + } + h2 { id: "bugs--features", + Link { + to: BookRoute::ContributingIndex { + section: ContributingIndexSection::BugsFeatures, + }, + class: "header", + "Bugs & Features" + } + } + p { + "If you've fixed " + Link { to: "https://github.com/DioxusLabs/dioxus/issues", "an open issue" } + ", feel free to submit a PR! You can also take a look at " + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Empty, + }, + "the roadmap" + } + " and work on something in there. Consider " + Link { to: "https://discord.gg/XgGxMSkvUM", "reaching out" } + " to the team first to make sure everyone's on the same page, and you don't do useless work!" + } + p { + "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." + } + h2 { id: "before-you-contribute", + Link { + to: BookRoute::ContributingIndex { + section: ContributingIndexSection::BeforeYouContribute, + }, + class: "header", + "Before you contribute" + } + } + p { + "You might be surprised that a lot of checks fail when making your first PR." + " " + "That's why you should first run these commands before contributing, and it will save you " + em { "lots" } + " of time, because the" + " " + "GitHub CI is much slower at executing all of these than your PC." + } + ul { + li { + "Format code with " + Link { to: "https://github.com/rust-lang/rustfmt", "rustfmt" } + ":" + } + } + CodeBlock { contents: "
\ncargo fmt --all
\n" } + ul { + li { + "Check all code " + Link { to: "https://doc.rust-lang.org/cargo/commands/cargo-check.html", + "cargo check" + } + ":" + } + } + CodeBlock { contents: "
\ncargo check --workspace --examples --tests
\n" } + ul { + li { + "Check if " + Link { to: "https://doc.rust-lang.org/clippy/", "Clippy" } + " generates any warnings. Please fix these!" + } + } + CodeBlock { contents: "
\ncargo clippy --workspace --examples --tests -- -D warnings
\n" } + ul { + li { + "Test all code with " + Link { to: "https://doc.rust-lang.org/cargo/commands/cargo-test.html", + "cargo-test" + } + ":" + } + } + CodeBlock { contents: "
\ncargo test --all --tests
\n" } + ul { + li { + "More tests, this time with " + Link { to: "https://sagiegurari.github.io/cargo-make/", "cargo-make" } + ". Here are all steps, including installation:" + } + } + CodeBlock { contents: "
\ncargo install --force cargo-make\ncargo make tests
\n" } + ul { + li { + "Test unsafe crates with " + Link { to: "https://github.com/rust-lang/miri", "MIRI" } + ". Currently, this is used for the two MIRI tests in " + code { "dioxus-core" } + " and " + code { "dioxus-native-core" } + ":" + } + } + CodeBlock { contents: "
\ncargo miri test --package dioxus-core --test miri_stress\ncargo miri test --package dioxus-native-core --test miri_native
\n" } + ul { + li { + "Test with Playwright. This tests the UI itself, right in a browser. Here are all steps, including installation:" + strong { + "Disclaimer: This might inexplicably fail on your machine without it being your fault." + } + " Make that PR anyway!" + } + } + CodeBlock { contents: "
\ncd playwright-tests\nnpm ci\nnpm install -D @playwright/test\nnpx playwright install --with-deps\nnpx playwright test
\n" } + h2 { id: "how-to-test-dioxus-with-local-crate", + Link { + to: BookRoute::ContributingIndex { + section: ContributingIndexSection::HowToTestDioxusWithLocalCrate, + }, + class: "header", + "How to test dioxus with local crate" + } + } + p { + "If you are developing a feature, you should test it in your local setup before raising a PR. This process makes sure you are aware of your code functionality before being reviewed by peers." + } + ul { + li { "Fork the following github repo (DioxusLabs/dioxus):" } + } + p { + code { "https://github.com/DioxusLabs/dioxus" } + } + ul { + li { + "Create a new or use an existing rust crate (ignore this step if you will use an existing rust crate):" + " " + "This is where we will be testing the features of the forked" + } + } + CodeBlock { contents: "
\ncargo new --bin demo
\n" } + ul { + li { "Add the dioxus dependencies for your rust crate (new/existing) in cargo.toml:" } + } + CodeBlock { contents: "
\ndioxus = {{ path = "<path to forked dioxus project>/dioxus/packages/dioxus/" }}\n\ndioxus-web = {{ path = "<path to forked dioxus project>/dioxus/packages/web/" }}
\n" } + p { + "This above example is for dioxus-web. To know about the dependencies for different renderer visit " + Link { to: "https://dioxuslabs.com/learn/0.4/getting_started", "here" } + "." + } + ul { + li { "Run and test your feature" } + } + CodeBlock { contents: "
\ndx serve
\n" } + p { + "If this is your first time with dioxus, please read " + Link { to: "https://dioxuslabs.com/learn/0.4/guide", "the guide" } + " to get familiar with dioxus." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ContributingProjectStructureSection { + #[default] + Empty, + ProjectStructure, + Renderers, + StateManagementhooks, + CoreUtilities, + NativeRendererUtilities, + WebRendererTooling, + DeveloperTooling, +} +impl std::str::FromStr for ContributingProjectStructureSection { + type Err = ContributingProjectStructureSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "project-structure" => Ok(Self::ProjectStructure), + "renderers" => Ok(Self::Renderers), + "state-managementhooks" => Ok(Self::StateManagementhooks), + "core-utilities" => Ok(Self::CoreUtilities), + "native-renderer-utilities" => Ok(Self::NativeRendererUtilities), + "web-renderer-tooling" => Ok(Self::WebRendererTooling), + "developer-tooling" => Ok(Self::DeveloperTooling), + _ => Err(ContributingProjectStructureSectionParseError), + } + } +} +impl std::fmt::Display for ContributingProjectStructureSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ProjectStructure => f.write_str("project-structure"), + Self::Renderers => f.write_str("renderers"), + Self::StateManagementhooks => f.write_str("state-managementhooks"), + Self::CoreUtilities => f.write_str("core-utilities"), + Self::NativeRendererUtilities => f.write_str("native-renderer-utilities"), + Self::WebRendererTooling => f.write_str("web-renderer-tooling"), + Self::DeveloperTooling => f.write_str("developer-tooling"), + } + } +} +#[derive(Debug)] +pub struct ContributingProjectStructureSectionParseError; +impl std::fmt::Display for ContributingProjectStructureSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ContributingProjectStructureSectionproject-structure, renderers, state-managementhooks, core-utilities, native-renderer-utilities, web-renderer-tooling, developer-tooling", + )?; + Ok(()) + } +} +impl std::error::Error for ContributingProjectStructureSectionParseError {} +#[component(no_case_check)] +pub fn ContributingProjectStructure( + section: ContributingProjectStructureSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "project-structure", + Link { + to: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::ProjectStructure, + }, + class: "header", + "Project Structure" + } + } + p { + "There are many packages in the Dioxus organization. This document will help you understand the purpose of each package and how they fit together." + } + h2 { id: "renderers", + Link { + to: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::Renderers, + }, + class: "header", + "Renderers" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/desktop", + "Desktop" + } + ": A Render that Runs Dioxus applications natively, but renders them with the system webview" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/mobile", + "Mobile" + } + ": A Render that Runs Dioxus applications natively, but renders them with the system webview. This is currently a copy of the desktop render" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/web", + "Web" + } + ": Renders Dioxus applications in the browser by compiling to WASM and manipulating the DOM" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/liveview", + "Liveview" + } + ": A Render that Runs on the server, and renders using a websocket proxy in the browser" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/rink", + "Rink" + } + ": A Renderer that renders a HTML-like tree into a terminal" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/dioxus-tui", + "TUI" + } + ": A Renderer that uses Rink to render a Dioxus application in a terminal" + } + li { + Link { to: "https://github.com/DioxusLabs/blitz/tree/master/blitz-core", + "Blitz-Core" + } + ": An experimental native renderer that renders a HTML-like tree using WGPU." + } + li { + Link { to: "https://github.com/DioxusLabs/blitz", "Blitz" } + ": An experimental native renderer that uses Blitz-Core to render a Dioxus application using WGPU." + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/ssr", + "SSR" + } + ": A Render that Runs Dioxus applications on the server, and renders them to HTML" + } + } + h2 { id: "state-managementhooks", + Link { + to: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::StateManagementhooks, + }, + class: "header", + "State Management/Hooks" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/hooks", + "Hooks" + } + ": A collection of common hooks for Dioxus applications" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/signals", + "Signals" + } + ": A experimental state management library for Dioxus applications. This currently contains a " + code { "Copy" } + " version of UseRef" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus-std", "Dioxus STD" } + ": A collection of platform agnostic hooks to interact with system interfaces (The clipboard, camera, etc.)." + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/fermi", + "Fermi" + } + ": A global state management library for Dioxus applications." + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/router", + "Router" + } + ": A client-side router for Dioxus applications" + } + } + h2 { id: "core-utilities", + Link { + to: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::CoreUtilities, + }, + class: "header", + "Core utilities" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/core", + "core" + } + ": The core virtual dom implementation every Dioxus application uses" + ul { + li { + "You can read more about the architecture of the core " + Link { to: "https://dioxuslabs.com/blog/templates-diffing/", + "in this blog post" + } + " and the " + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Empty, + }, + "custom renderer section of the guide" + } + } + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/rsx", + "RSX" + } + ": The core parsing for RSX used for hot reloading, autoformatting, and the macro" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/core-macro", + "core-macro" + } + ": The rsx! macro used to write Dioxus applications. (This is a wrapper over the RSX crate)" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus-html-macro", "HTML macro" } + ": A html-like alternative to the RSX macro" + } + } + h2 { id: "native-renderer-utilities", + Link { + to: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::NativeRendererUtilities, + }, + class: "header", + "Native Renderer Utilities" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core", + "native-core" + } + ": Incrementally computed tree of states (mostly styles)" + ul { + li { + "You can read more about how native-core can help you build native renderers in the " + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::NativeCore, + }, + "custom renderer section of the guide" + } + } + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/native-core-macro", + "native-core-macro" + } + ": A helper macro for native core" + } + li { + Link { to: "https://github.com/DioxusLabs/taffy", "Taffy" } + ": Layout engine powering Blitz-Core, Rink, and Bevy UI" + } + } + h2 { id: "web-renderer-tooling", + Link { + to: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::WebRendererTooling, + }, + class: "header", + "Web renderer tooling" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/html", + "HTML" + } + ": defines html specific elements, events, and attributes" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/interpreter", + "Interpreter" + } + ": defines browser bindings used by the web and desktop renderers" + } + } + h2 { id: "developer-tooling", + Link { + to: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::DeveloperTooling, + }, + class: "header", + "Developer tooling" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/hot-reload", + "hot-reload" + } + ": Macro that uses the RSX crate to hot reload static parts of any rsx! macro. This macro works with any non-web renderer with an " + Link { to: "https://crates.io/crates/dioxus-hot-reload", "integration" } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/autofmt", + "autofmt" + } + ": Formats RSX code" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/rsx-rosetta", + "rsx-rosetta" + } + ": Handles conversion between HTML and RSX" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/cli", + "CLI" + } + ": A Command Line Interface and VSCode extension to assist with Dioxus usage" + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ContributingWalkthroughReadmeSection { + #[default] + Empty, + WalkthroughOfTheHelloWorldExampleInternals, + TheSourceFile, + TheRsxMacro, + LaunchingTheApp, + TheVirtualDom, + TheInitialRender, + WaitingForEvents, + DiffingScopes, + Conclusion, +} +impl std::str::FromStr for ContributingWalkthroughReadmeSection { + type Err = ContributingWalkthroughReadmeSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "walkthrough-of-the-hello-world-example-internals" => { + Ok(Self::WalkthroughOfTheHelloWorldExampleInternals) + } + "the-source-file" => Ok(Self::TheSourceFile), + "the-rsx-macro" => Ok(Self::TheRsxMacro), + "launching-the-app" => Ok(Self::LaunchingTheApp), + "the-virtual-dom" => Ok(Self::TheVirtualDom), + "the-initial-render" => Ok(Self::TheInitialRender), + "waiting-for-events" => Ok(Self::WaitingForEvents), + "diffing-scopes" => Ok(Self::DiffingScopes), + "conclusion" => Ok(Self::Conclusion), + _ => Err(ContributingWalkthroughReadmeSectionParseError), + } + } +} +impl std::fmt::Display for ContributingWalkthroughReadmeSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::WalkthroughOfTheHelloWorldExampleInternals => { + f.write_str("walkthrough-of-the-hello-world-example-internals") + } + Self::TheSourceFile => f.write_str("the-source-file"), + Self::TheRsxMacro => f.write_str("the-rsx-macro"), + Self::LaunchingTheApp => f.write_str("launching-the-app"), + Self::TheVirtualDom => f.write_str("the-virtual-dom"), + Self::TheInitialRender => f.write_str("the-initial-render"), + Self::WaitingForEvents => f.write_str("waiting-for-events"), + Self::DiffingScopes => f.write_str("diffing-scopes"), + Self::Conclusion => f.write_str("conclusion"), + } + } +} +#[derive(Debug)] +pub struct ContributingWalkthroughReadmeSectionParseError; +impl std::fmt::Display for ContributingWalkthroughReadmeSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ContributingWalkthroughReadmeSectionwalkthrough-of-the-hello-world-example-internals, the-source-file, the-rsx-macro, launching-the-app, the-virtual-dom, the-initial-render, waiting-for-events, diffing-scopes, conclusion", + )?; + Ok(()) + } +} +impl std::error::Error for ContributingWalkthroughReadmeSectionParseError {} +#[component(no_case_check)] +pub fn ContributingWalkthroughReadme( + section: ContributingWalkthroughReadmeSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "walkthrough-of-the-hello-world-example-internals", + Link { + to: BookRoute::ContributingWalkthroughReadme { + section: ContributingWalkthroughReadmeSection::WalkthroughOfTheHelloWorldExampleInternals, + }, + class: "header", + "Walkthrough of the Hello World Example Internals" + } + } + p { + "This walkthrough will take you through the internals of the Hello World example program. It will explain how major parts of Dioxus internals interact with each other to take the readme example from a source file to a running application. This guide should serve as a high-level overview of the internals of Dioxus. It is not meant to be a comprehensive guide." + } + p { "The core crate roughly works like this:" } + p { + img { + src: "https://mermaid.ink/img/pako:eNqNk01v2zAMhv8KocsuTQ876lCgWAb0sGDD0mMAg7PoWogsBvpwWhT976MlJ3OKbKtOEvmIfEWRr6plQ0qrmDDR2uJTwGE1ft55kBXIGwqNHQYyVvywWt3BA3rjKGj4gs5BX0-V_1n4QtUthW_Mh6WzWgryg537OpJPsQJ_zsX9PrmG0fBwWxM2NIH1nmdRFuxTn4C7K4mn9djTpYAjWsnTcQBaSJiWxIcULEVILCIiu5Egyf3RhpTRwfr75tOC73LKggGmQkUcBLcDVUJyFoF_qcEkoxEVzZHDvjIXpnOhtm1PJp8rvcGw37Z8oPu4FlkvhVvbrivGypyP_3dWXRo2WdrAsp-fN391Qd5n1BBnSU0-GDy9sHyGo678xcOyOU7fMHcMHINNtcgIPfP-Wr2WAu6NeeRzGTS0z7fxgEd_7T3_Zi8b5kp1T1IxvvgWfjlu9x-SexHqo1VTN2qgMKA1MoavU6CdkkaSBlJatoY6zC7t1M6_CYo58VZUKZ1CphtVo8yDq3SHLopVJiZx2NTRLhP-9htxEk8q?type=png", + alt: "", + title: "", + } + } + h2 { id: "the-source-file", + Link { + to: BookRoute::ContributingWalkthroughReadme { + section: ContributingWalkthroughReadmeSection::TheSourceFile, + }, + class: "header", + "The Source File" + } + } + p { + "We start will a hello world program. This program renders a desktop app with the text \"Hello World\" in a webview." + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\npub fn App(cx: Scope) -> Element {{\n    let mut count = use_state(cx, || 0);\n\n    cx.render(rsx! {{\n        h1 {{ "High-Five counter: {{count}}" }}\n        button {{ onclick: move |_| count += 1, "Up high!" }}\n        button {{ onclick: move |_| count -= 1, "Down low!" }}\n    }})\n}}
\n", + name: "readme.rs".to_string(), + } + p { + Link { to: "https://mermaid.live/edit#pako:eNqNkT1vwyAQhv8KvSlR48HphtQtqjK0S6tuSBGBS0CxwcJHk8rxfy_YVqxKVdR3ug_u4YXrQHmNwOFQ-bMyMhB7fReOJbVxfwyyMSy0l7GSpW1ARda727ksUy5MuSyKgvBC5ULA1h5N8WK_kCkfHWHgrBuiXsBynrvdsY9E3u1iM_eyvFOVVadMnELOap-o1911JLPHZ1b-YqLTc3LjTt7WifTZMJPsPdx1ov3Z_ellfcdL8R8vmTy5eUqsTUpZ-vzZzjAEK6gx1NLqtJwuNwSQwRoF8BRqGU4ChOvTORnJf3w7BZxCxBXERkvCjZXpQTXwg6zaVEVtyYe3cdvD0vsf4bucgw", + img { + src: "https://mermaid.ink/img/pako:eNqNkT1vwyAQhv8KvSlR48HphtQtqjK0S6tuSBGBS0CxwcJHk8rxfy_YVqxKVdR3ug_u4YXrQHmNwOFQ-bMyMhB7fReOJbVxfwyyMSy0l7GSpW1ARda727ksUy5MuSyKgvBC5ULA1h5N8WK_kCkfHWHgrBuiXsBynrvdsY9E3u1iM_eyvFOVVadMnELOap-o1911JLPHZ1b-YqLTc3LjTt7WifTZMJPsPdx1ov3Z_ellfcdL8R8vmTy5eUqsTUpZ-vzZzjAEK6gx1NLqtJwuNwSQwRoF8BRqGU4ChOvTORnJf3w7BZxCxBXERkvCjZXpQTXwg6zaVEVtyYe3cdvD0vsf4bucgw?type=png", + alt: "", + title: "", + } + } + } + h2 { id: "the-rsx-macro", + Link { + to: BookRoute::ContributingWalkthroughReadme { + section: ContributingWalkthroughReadmeSection::TheRsxMacro, + }, + class: "header", + "The rsx! Macro" + } + } + p { + "Before the Rust compiler runs the program, it will expand all " + Link { to: "https://doc.rust-lang.org/reference/procedural-macros.html", + "macros" + } + ". Here is what the hello world example looks like expanded:" + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\nfn main() {{\n    dioxus_desktop::launch(app);\n}}\n\nfn app(cx: Scope) -> Element {{\n    let mut count = use_state(cx, || 0);\n\n    cx.render(\n        // rsx expands to LazyNodes::new\n        ::dioxus::core::LazyNodes::new(\n            move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode {{\n                // The template is every static part of the rsx\n                static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {{\n                    // This is the source location of the rsx that generated this template. This is used to make hot rsx reloading work. Hot rsx reloading just replaces the template with a new one generated from the rsx by the CLI.\n                    name: "examples\\\\readme.rs:14:15:250",\n                    // The root nodes are the top level nodes of the rsx\n                    roots: &[\n                        // The h1 node\n                        ::dioxus::core::TemplateNode::Element {{\n                            // Find the built in h1 tag in the dioxus_elements crate exported by the dioxus html crate\n                            tag: dioxus_elements::h1::TAG_NAME,\n                            namespace: dioxus_elements::h1::NAME_SPACE,\n                            attrs: &[],\n                            // The children of the h1 node\n                            children: &[\n                                // The dynamic count text node\n                                // Any nodes that are dynamic have a dynamic placeholder with a unique index\n                                ::dioxus::core::TemplateNode::DynamicText {{\n                                    // This index is used to find what element in `dynamic_nodes` to use instead of the placeholder\n                                    id: 0usize,\n                                }},\n                            ],\n                        }},\n                        // The up high button node\n                        ::dioxus::core::TemplateNode::Element {{\n                            tag: dioxus_elements::button::TAG_NAME,\n                            namespace: dioxus_elements::button::NAME_SPACE,\n                            attrs: &[\n                                // The dynamic onclick listener attribute\n                                // Any attributes that are dynamic have a dynamic placeholder with a unique index.\n                                ::dioxus::core::TemplateAttribute::Dynamic {{\n                                    // Similar to dynamic nodes, dynamic attributes have a unique index used to find the attribute in `dynamic_attrs` to use instead of the placeholder\n                                    id: 0usize,\n                                }},\n                            ],\n                            children: &[::dioxus::core::TemplateNode::Text {{ text: "Up high!" }}],\n                        }},\n                        // The down low button node\n                        ::dioxus::core::TemplateNode::Element {{\n                            tag: dioxus_elements::button::TAG_NAME,\n                            namespace: dioxus_elements::button::NAME_SPACE,\n                            attrs: &[\n                                // The dynamic onclick listener attribute\n                                ::dioxus::core::TemplateAttribute::Dynamic {{ id: 1usize }},\n                            ],\n                            children: &[::dioxus::core::TemplateNode::Text {{ text: "Down low!" }}],\n                        }},\n                    ],\n                    // Node paths is a list of paths to every dynamic node in the rsx\n                    node_paths: &[\n                        // The first node path is the path to the dynamic node with an id of 0 (the count text node)\n                        &[\n                            // Go to the index 0 root node\n                            0u8, //\n                            // Go to the first child of the root node\n                            0u8,\n                        ],\n                    ],\n                    // Attr paths is a list of paths to every dynamic attribute in the rsx\n                    attr_paths: &[\n                        // The first attr path is the path to the dynamic attribute with an id of 0 (the up high button onclick listener)\n                        &[\n                            // Go to the index 1 root node\n                            1u8,\n                        ],\n                        // The second attr path is the path to the dynamic attribute with an id of 1 (the down low button onclick listener)\n                        &[\n                            // Go to the index 2 root node\n                            2u8,\n                        ],\n                    ],\n                }};\n                // The VNode is a reference to the template with the dynamic parts of the rsx\n                ::dioxus::core::VNode {{\n                    parent: None,\n                    key: None,\n                    // The static template this node will use. The template is stored in a Cell so it can be replaced with a new template when hot rsx reloading is enabled\n                    template: std::cell::Cell::new(TEMPLATE),\n                    root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(\n                        3,\n                        __cx.bump(),\n                    )\n                    .into(),\n                    dynamic_nodes: __cx.bump().alloc([\n                        // The dynamic count text node (dynamic node id 0)\n                        __cx.text_node(format_args!("High-Five counter: {{0}}", count)),\n                    ]),\n                    dynamic_attrs: __cx.bump().alloc([\n                        // The dynamic up high button onclick listener (dynamic attribute id 0)\n                        dioxus_elements::events::onclick(__cx, move |_| count += 1),\n                        // The dynamic down low button onclick listener (dynamic attribute id 1)\n                        dioxus_elements::events::onclick(__cx, move |_| count -= 1),\n                    ]),\n                }}\n            }},\n        ),\n    )\n}}
\n", + name: "readme_expanded.rs".to_string(), + } + p { + "The rsx macro separates the static parts of the rsx (the template) and the dynamic parts (the " + Link { to: "https://docs.rs/dioxus-core/0.3.2/dioxus_core/prelude/struct.VNode.html#structfield.dynamic_nodes", + "dynamic_nodes" + } + " and " + Link { to: "https://docs.rs/dioxus-core/0.3.2/dioxus_core/prelude/struct.VNode.html#structfield.dynamic_attrs", + "dynamic_attributes" + } + ")." + } + p { + "The static template only contains the parts of the rsx that cannot change at runtime with holes for the dynamic parts:" + } + p { + Link { to: "https://mermaid.live/edit#pako:eNqdksFuwjAMhl8l8wkkKtFx65njdtm0E0GVSQKJoEmVOgKEeHecUrXStO0wn5Lf9u8vcm6ggjZQwf4UzspiJPH2Ib3g6NLuELG1oiMkp0TsLs9EDu2iUeSCH8tz2HJmy3lRFPrqsXGq9mxeLzcbCU6LZSUGXWRdwnY7tY7Tdoko-Dq1U64fODgiUfzJMeuOe7_ZGq-ny2jNhGQu9DqT8NUK6w72RcL8dxgdzv4PnHLAKf-Fk80HoBUDrfkqeBkTUd8EC2hMbNBpXtYtJySQNQ0PqPioMR4lSH_nOkwUPq9eQUUxmQWkViOZtUN-UwPVHk8dq0Y7CvH9uf3-E9wfrmuk1A", + img { + src: "https://mermaid.ink/img/pako:eNqdksFuwjAMhl8l8wkkKtFx65njdtm0E0GVSQKJoEmVOgKEeHecUrXStO0wn5Lf9u8vcm6ggjZQwf4UzspiJPH2Ib3g6NLuELG1oiMkp0TsLs9EDu2iUeSCH8tz2HJmy3lRFPrqsXGq9mxeLzcbCU6LZSUGXWRdwnY7tY7Tdoko-Dq1U64fODgiUfzJMeuOe7_ZGq-ny2jNhGQu9DqT8NUK6w72RcL8dxgdzv4PnHLAKf-Fk80HoBUDrfkqeBkTUd8EC2hMbNBpXtYtJySQNQ0PqPioMR4lSH_nOkwUPq9eQUUxmQWkViOZtUN-UwPVHk8dq0Y7CvH9uf3-E9wfrmuk1A?type=png", + alt: "", + title: "", + } + } + } + p { + "The dynamic_nodes and dynamic_attributes are the parts of the rsx that can change at runtime:" + } + p { + Link { to: "https://mermaid.live/edit#pako:eNp1UcFOwzAM_RXLVzZpvUbighDiABfgtkxTlnirtSaZUgc0df130hZEEcwny35-79nu0EZHqHDfxA9bmyTw9KIDlGjz7pDMqQZ3DsazhVCQ7dQbwnEiKxwDvN3NqhN4O4C3q_VaIztYKXjkQ7184HcCG3MQSgq6Mes1bjbTPAV3RdqIJN5l-V__2_Fcf5iY68dgG7ZHBT4WD5ftZfIBN7dQ_Tj4w1B9MVTXGZa_GMYdcIGekjfsymW7oaFRavKkUZXUmXTUqENfcCZLfD0Hi0pSpgXmkzNC92zKATyqvWnaUiXHEtPz9KrxY_0nzYOPmA", + img { + src: "https://mermaid.ink/img/pako:eNp1UcFOwzAM_RXLVzZpvUbighDiABfgtkxTlnirtSaZUgc0df130hZEEcwny35-79nu0EZHqHDfxA9bmyTw9KIDlGjz7pDMqQZ3DsazhVCQ7dQbwnEiKxwDvN3NqhN4O4C3q_VaIztYKXjkQ7184HcCG3MQSgq6Mes1bjbTPAV3RdqIJN5l-V__2_Fcf5iY68dgG7ZHBT4WD5ftZfIBN7dQ_Tj4w1B9MVTXGZa_GMYdcIGekjfsymW7oaFRavKkUZXUmXTUqENfcCZLfD0Hi0pSpgXmkzNC92zKATyqvWnaUiXHEtPz9KrxY_0nzYOPmA?type=png", + alt: "", + title: "", + } + } + } + h2 { id: "launching-the-app", + Link { + to: BookRoute::ContributingWalkthroughReadme { + section: ContributingWalkthroughReadmeSection::LaunchingTheApp, + }, + class: "header", + "Launching the App" + } + } + p { + "The app is launched by calling the " + code { "launch" } + " function with the root component. Internally, this function will create a new web view using " + Link { to: "https://docs.rs/wry/latest/wry/", "wry" } + " and create a virtual dom with the root component (" + code { "fn app()" } + " in the readme example). This guide will not explain the renderer in-depth, but you can read more about it in the " + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Empty, + }, + "custom renderer" + } + " section." + } + h2 { id: "the-virtual-dom", + Link { + to: BookRoute::ContributingWalkthroughReadme { + section: ContributingWalkthroughReadmeSection::TheVirtualDom, + }, + class: "header", + "The Virtual DOM" + } + } + p { + "Before we dive into the initial render in the virtual DOM, we need to discuss what the virtual DOM is. The virtual DOM is a representation of the DOM that is used to diff the current DOM from the new DOM. This diff is then used to create a list of mutations that need to be applied to the DOM to bring it into sync with the virtual DOM." + } + p { "The Virtual DOM roughly looks like this:" } + CodeBlock { + contents: "
\npub struct VirtualDom {{\n    // All the templates that have been created or set during hot reloading\n    pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,\n\n    // A slab of all the scopes that have been created\n    pub(crate) scopes: ScopeSlab,\n\n    // All scopes that have been marked as dirty\n    pub(crate) dirty_scopes: BTreeSet<DirtyScope>,\n\n    // Every element is actually a dual reference - one to the template and the other to the dynamic node in that template\n    pub(crate) elements: Slab<ElementRef>,\n\n    // This receiver is used to receive messages from hooks about what scopes need to be marked as dirty\n    pub(crate) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,\n\n    // The changes queued up to be sent to the renderer\n    pub(crate) mutations: Mutations<'static>,\n}}
\n", + } + blockquote { + p { + "What is a " + Link { to: "https://docs.rs/slab/latest/slab/", "slab" } + "?" + } + p { + "A slab acts like a hashmap with integer keys if you don't care about the value of the keys. It is internally backed by a dense vector which makes it more efficient than a hashmap. When you insert a value into a slab, it returns an integer key that you can use to retrieve the value later." + } + } + blockquote { + p { "How does Dioxus use slabs?" } + p { + "Dioxus uses \"synchronized slabs\" to communicate between the renderer and the VDOM. When a node is created in the Virtual DOM, an (elementId, mutation) pair is passed to the renderer to identify that node, which the renderer will then render in actual DOM. These ids are also used by the Virtual Dom to reference that node in future mutations, like setting an attribute on a node or removing a node. When the renderer sends an event to the Virtual Dom, it sends the ElementId of the node that the event was triggered on. The Virtual DOM uses this id to find that node in the slab and then run the necessary event handlers." + } + } + p { + "The virtual DOM is a tree of scopes. A new " + code { "Scope" } + " is created for every component when it is first rendered and recycled when the component is unmounted." + } + p { "Scopes serve three main purposes:" } + ol { + li { "They store the state of hooks used by the component" } + li { + "They store the state for the context API (for example: using" + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_shared_state_provider.html", + "use_shared_state_provider" + } + ")." + } + li { + "They store the current and previous versions of the " + code { "VNode" } + " that was rendered, so they can be" + " " + "diffed to generate the set of mutations needed to re-render it." + } + } + h3 { id: "the-initial-render", + Link { + to: BookRoute::ContributingWalkthroughReadme { + section: ContributingWalkthroughReadmeSection::TheInitialRender, + }, + class: "header", + "The Initial Render" + } + } + p { "The root scope is created and rebuilt:" } + ol { + li { "The root component is run" } + li { + "The root component returns a " + code { "VNode" } + } + li { + "Mutations for this " + code { "VNode" } + " are created and added to the mutation list (this may involve creating new child components)" + } + li { + "The " + code { "VNode" } + " is stored in the root's " + code { "Scope" } + "." + } + } + p { + "After the root's " + code { "Scope" } + " is built, all generated mutations are sent to the renderer, which applies them to the DOM." + } + p { + "After the initial render, the root " + code { "Scope" } + " looks like this:" + } + p { + Link { to: "https://mermaid.live/edit#pako:eNqtVE1P4zAQ_SuzPrWikRpWXCLtBRDisItWsOxhCaqM7RKricdyJrQV8N93QtvQNCkfEnOynydv3nxkHoVCbUQipjnOVSYDwc_L1AFbWd3dB-kzuEQkuFLoDUwDFkCZAek9nGDh0RlHK__atA1GkUUHf45f0YbppAqB_aOzIAvz-t7-chN_Y-1bw1WSJKsglIu2w9tktWXxIIuHURT5XCqTYa5NmDguw2R8c5MKq2GcgF46WTB_jafi9rZL0yi5q4jQTSrf9altO4okCn1Ratwyz55Qxuku2ITlTMgs6HCQimsPmb3PvqVi-L5gjXP3QcnxWnL8JZLrwGvR31n0KV-Bx6-r-oVkT_-3G1S-NQLbk9i8rj7udP2cixed2QcDCitHJiQw7ub3EVlNecrPjudG2-6soFO5VbMECmR9T5OnlUY4-AFxfw9aTFst3McU9TK1Otm6NEn_DubBYlX2_dglLXOz48FgwJmJ5lZTlhz6xWgNaFnyDgpymcARHO0W2a9J_l5w2wYXvHuGPcqaQ-rESBQmFNJq3nCPNZoK3l4sUSR81DLMUpG6Z_aTFeHV0imRUKjMSFReSzKnVnKGhUimMi8ZNdoShl-rlfmyOUfCS_cPcePz_B_Wl4pc", + img { + src: "https://mermaid.ink/img/pako:eNqtVE1P4zAQ_SuzPrWikRpWXCLtBRDisItWsOxhCaqM7RKricdyJrQV8N93QtvQNCkfEnOynydv3nxkHoVCbUQipjnOVSYDwc_L1AFbWd3dB-kzuEQkuFLoDUwDFkCZAek9nGDh0RlHK__atA1GkUUHf45f0YbppAqB_aOzIAvz-t7-chN_Y-1bw1WSJKsglIu2w9tktWXxIIuHURT5XCqTYa5NmDguw2R8c5MKq2GcgF46WTB_jafi9rZL0yi5q4jQTSrf9altO4okCn1Ratwyz55Qxuku2ITlTMgs6HCQimsPmb3PvqVi-L5gjXP3QcnxWnL8JZLrwGvR31n0KV-Bx6-r-oVkT_-3G1S-NQLbk9i8rj7udP2cixed2QcDCitHJiQw7ub3EVlNecrPjudG2-6soFO5VbMECmR9T5OnlUY4-AFxfw9aTFst3McU9TK1Otm6NEn_DubBYlX2_dglLXOz48FgwJmJ5lZTlhz6xWgNaFnyDgpymcARHO0W2a9J_l5w2wYXvHuGPcqaQ-rESBQmFNJq3nCPNZoK3l4sUSR81DLMUpG6Z_aTFeHV0imRUKjMSFReSzKnVnKGhUimMi8ZNdoShl-rlfmyOUfCS_cPcePz_B_Wl4pc?type=png", + alt: "", + title: "", + } + } + } + h3 { id: "waiting-for-events", + Link { + to: BookRoute::ContributingWalkthroughReadme { + section: ContributingWalkthroughReadmeSection::WaitingForEvents, + }, + class: "header", + "Waiting for Events" + } + } + p { + "The Virtual DOM will only ever re-render a " + code { "Scope" } + " if it is marked as dirty. Each hook is responsible for marking the " + code { "Scope" } + " as dirty if the state has changed. Hooks can mark a scope as dirty by sending a message to the Virtual Dom's channel. You can see the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/hooks", + "implementations" + } + " for the hooks dioxus includes by default on how this is done. Calling " + code { "needs_update()" } + " on a hook will also cause it to mark its scope as dirty." + } + p { "There are generally two ways a scope is marked as dirty:" } + ol { + li { + "The renderer triggers an event: An event listener on this event may be called, which may mark a" + " " + "component as dirty, if processing the event resulted in any generated any mutations." + } + li { + "The renderer calls" + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.VirtualDom.html#method.wait_for_work", + code { "wait_for_work" } + } + ":" + " " + "This polls dioxus internal future queue. One of these futures may mark a component as dirty." + } + } + p { + "Once at least one " + code { "Scope" } + " is marked as dirty, the renderer can call " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.VirtualDom.html#method.render_with_deadline", + code { "render_with_deadline" } + } + " to diff the dirty scopes." + } + h3 { id: "diffing-scopes", + Link { + to: BookRoute::ContributingWalkthroughReadme { + section: ContributingWalkthroughReadmeSection::DiffingScopes, + }, + class: "header", + "Diffing Scopes" + } + } + p { + "When a user clicks the \"up high\" button, the root " + code { "Scope" } + " will be marked as dirty by the " + code { "use_state" } + " hook. The desktop renderer will then call " + code { "render_with_deadline" } + ", which will diff the root " + code { "Scope" } + "." + } + p { + "To start the diffing process, the component function is run. After the root component is run it, the root " + code { "Scope" } + " will look like this:" + } + p { + Link { to: "https://mermaid.live/edit#pako:eNrFVlFP2zAQ_iuen0BrpCaIl0i8AEJ72KQJtpcRFBnbJVYTn-U4tBXw33dpG5M2CetoBfdkny_ffb67fPIT5SAkjekkhxnPmHXk-3WiCVpZ3T9YZjJyDeDIDQcjycRCQVwmCTOGXEBhQEvtVvG1CWUldwo0-XX-6vVIF5W1GB9cWVbI1_PNL5v8jW3uPFbpmFOc2HK-GfA2WG1ZeJSFx0EQmJxxmUEupE01liEd394mVAkyjolYaFYgfu1P6N1dF8Yzua-cA51WphtTWzsLc872Zan9CnEGUkktuk6fFm_i5NxFRwn9bUimHrIvCT3-N2EBM70j5XBNOTwI5TrxmvQJkr7ELcHx67Jeggz0v92g8q0RaE-iP1193On6NyxecKUeJeFQaSdtTMLu_Xah5ctT_u94Nty2ZwU0zxWfxqQA5PecPq84kq9nfRw7SK0WDiEFZ4O37d34S_-08lFBVfb92KVb5HIrAp0WpjKYKeGyODLz0dohWIkaZNkiJqfkdLvIH6oRaTSoEmm0n06k0a5K0ZdpL61Io0Yt0nfpxc7UQ0_9cJrhyZ8syX-6brS706Mc489Vjja7fbWj3cxDqIdfJJqOaCFtwZTAV8hT7U0ovjBQRmiMS8HsNKGJfsE4Vjm4WWhOY2crOaKVEczJS8WwgAWNJywv0SuFcmB_rJ41y9fNiBqm_wA0MS9_AUuAiy0", + img { + src: "https://mermaid.ink/img/pako:eNrFVlFP2zAQ_iuen0BrpCaIl0i8AEJ72KQJtpcRFBnbJVYTn-U4tBXw33dpG5M2CetoBfdkny_ffb67fPIT5SAkjekkhxnPmHXk-3WiCVpZ3T9YZjJyDeDIDQcjycRCQVwmCTOGXEBhQEvtVvG1CWUldwo0-XX-6vVIF5W1GB9cWVbI1_PNL5v8jW3uPFbpmFOc2HK-GfA2WG1ZeJSFx0EQmJxxmUEupE01liEd394mVAkyjolYaFYgfu1P6N1dF8Yzua-cA51WphtTWzsLc872Zan9CnEGUkktuk6fFm_i5NxFRwn9bUimHrIvCT3-N2EBM70j5XBNOTwI5TrxmvQJkr7ELcHx67Jeggz0v92g8q0RaE-iP1193On6NyxecKUeJeFQaSdtTMLu_Xah5ctT_u94Nty2ZwU0zxWfxqQA5PecPq84kq9nfRw7SK0WDiEFZ4O37d34S_-08lFBVfb92KVb5HIrAp0WpjKYKeGyODLz0dohWIkaZNkiJqfkdLvIH6oRaTSoEmm0n06k0a5K0ZdpL61Io0Yt0nfpxc7UQ0_9cJrhyZ8syX-6brS706Mc489Vjja7fbWj3cxDqIdfJJqOaCFtwZTAV8hT7U0ovjBQRmiMS8HsNKGJfsE4Vjm4WWhOY2crOaKVEczJS8WwgAWNJywv0SuFcmB_rJ41y9fNiBqm_wA0MS9_AUuAiy0?type=png", + alt: "", + title: "", + } + } + } + p { + "Next, the Virtual DOM will compare the new VNode with the previous VNode and only update the parts of the tree that have changed. Because of this approach, when a component is re-rendered only the parts of the tree that have changed will be updated in the DOM by the renderer." + } + p { + "The diffing algorithm goes through the list of dynamic attributes and nodes and compares them to the previous VNode. If the attribute or node has changed, a mutation that describes the change is added to the mutation list." + } + p { + "Here is what the diffing algorithm looks like for the root " + code { "Scope" } + " (red lines indicate that a mutation was generated, and green lines indicate that no mutation was generated)" + } + p { + Link { to: "https://mermaid.live/edit#pako:eNrFlFFPwjAQx7_KpT7Kko2Elya8qCE-aGLAJ5khpe1Yw9Zbug4k4He3OJjbGPig0T5t17tf_nf777aEo5CEkijBNY-ZsfAwDjW4kxfzhWFZDGNECxOOmYTIYAo2lsCyDG4xzVBLbcv8_RHKSG4V6orSIN0Wxrh8b2RYKr_uTyubd1W92GiWKg7aac6bOU3G803HbVk82xfP_Ok0JEqAT-FeLWJvpFYSOBbaSkMhCMnra5MgtfhWFrPWqHlhL2urT6atbU-oa0PNE8WXFFJ0-nazXakRroddGk9IwYEUnCd5w7Pddr5UTT8ZuVJY5F0fM7ebRLYyXNDgUnprJWxM-9lb7xAQLHe-M2xDYQCD9pD_2hez_kVn-P_rjLq6n3qjYv2iO5qz9DyvPdyv1ETp5eTTJ_7BGvQq8v1TVtl5jXUcRRcrqFh-dI4VtFlBN6t_ynLNkh5JpUmZEm5rbvfhkLiN6H4BQt2jYGYZklC_uzxWWJxsNCfUmkL2SJEJZuWdYs4cKaERS3IXlUJZNI_lGv7cxj2SMf2CeMx5_wBcbK19", + img { + src: "https://mermaid.ink/img/pako:eNrFlFFPwjAQx7_KpT7Kko2Elya8qCE-aGLAJ5khpe1Yw9Zbug4k4He3OJjbGPig0T5t17tf_nf777aEo5CEkijBNY-ZsfAwDjW4kxfzhWFZDGNECxOOmYTIYAo2lsCyDG4xzVBLbcv8_RHKSG4V6orSIN0Wxrh8b2RYKr_uTyubd1W92GiWKg7aac6bOU3G803HbVk82xfP_Ok0JEqAT-FeLWJvpFYSOBbaSkMhCMnra5MgtfhWFrPWqHlhL2urT6atbU-oa0PNE8WXFFJ0-nazXakRroddGk9IwYEUnCd5w7Pddr5UTT8ZuVJY5F0fM7ebRLYyXNDgUnprJWxM-9lb7xAQLHe-M2xDYQCD9pD_2hez_kVn-P_rjLq6n3qjYv2iO5qz9DyvPdyv1ETp5eTTJ_7BGvQq8v1TVtl5jXUcRRcrqFh-dI4VtFlBN6t_ynLNkh5JpUmZEm5rbvfhkLiN6H4BQt2jYGYZklC_uzxWWJxsNCfUmkL2SJEJZuWdYs4cKaERS3IXlUJZNI_lGv7cxj2SMf2CeMx5_wBcbK19?type=png", + alt: "", + title: "", + } + } + } + h2 { id: "conclusion", + Link { + to: BookRoute::ContributingWalkthroughReadme { + section: ContributingWalkthroughReadmeSection::Conclusion, + }, + class: "header", + "Conclusion" + } + } + p { + "This is only a brief overview of how the Virtual Dom works. There are several aspects not yet covered in this guide including:" + } + ul { + li { "How the Virtual DOM handles async-components" } + li { "Keyed diffing" } + li { + "Using " + Link { to: "https://github.com/fitzgen/bumpalo", "bump allocation" } + " to efficiently allocate VNodes." + } + } + p { + "If you need more information about the Virtual Dom, you can read the code of the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/core", + "core" + } + " crate or reach out to us on " + Link { to: "https://discord.gg/XgGxMSkvUM", "Discord" } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ContributingGuidingPrinciplesSection { + #[default] + Empty, + OverallGoals, + CrossPlatform, + Performance, + TypeSafety, + DeveloperExperience, +} +impl std::str::FromStr for ContributingGuidingPrinciplesSection { + type Err = ContributingGuidingPrinciplesSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "overall-goals" => Ok(Self::OverallGoals), + "cross-platform" => Ok(Self::CrossPlatform), + "performance" => Ok(Self::Performance), + "type-safety" => Ok(Self::TypeSafety), + "developer-experience" => Ok(Self::DeveloperExperience), + _ => Err(ContributingGuidingPrinciplesSectionParseError), + } + } +} +impl std::fmt::Display for ContributingGuidingPrinciplesSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::OverallGoals => f.write_str("overall-goals"), + Self::CrossPlatform => f.write_str("cross-platform"), + Self::Performance => f.write_str("performance"), + Self::TypeSafety => f.write_str("type-safety"), + Self::DeveloperExperience => f.write_str("developer-experience"), + } + } +} +#[derive(Debug)] +pub struct ContributingGuidingPrinciplesSectionParseError; +impl std::fmt::Display for ContributingGuidingPrinciplesSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ContributingGuidingPrinciplesSectionoverall-goals, cross-platform, performance, type-safety, developer-experience", + )?; + Ok(()) + } +} +impl std::error::Error for ContributingGuidingPrinciplesSectionParseError {} +#[component(no_case_check)] +pub fn ContributingGuidingPrinciples( + section: ContributingGuidingPrinciplesSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "overall-goals", + Link { + to: BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::OverallGoals, + }, + class: "header", + "Overall Goals" + } + } + p { + "This document outlines some of the overall goals for Dioxus. These goals are not set in stone, but they represent general guidelines for the project." + } + p { + "The goal of Dioxus is to make it easy to build " + strong { "cross-platform applications that scale" } + "." + } + h2 { id: "cross-platform", + Link { + to: BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::CrossPlatform, + }, + class: "header", + "Cross-Platform" + } + } + p { + "Dioxus is designed to be cross-platform by default. This means that it should be easy to build applications that run on the web, desktop, and mobile. However, Dioxus should also be flexible enough to allow users to opt into platform-specific features when needed. The " + code { "use_eval" } + " is one example of this. By default, Dioxus does not assume that the platform supports JavaScript, but it does provide a hook that allows users to opt into JavaScript when needed." + } + h2 { id: "performance", + Link { + to: BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::Performance, + }, + class: "header", + "Performance" + } + } + p { + "As Dioxus applications grow, they should remain relatively performant without the need for manual optimizations. There will be cases where manual optimizations are needed, but Dioxus should try to make these cases as rare as possible." + } + p { + "One of the benefits of the core architecture of Dioxus is that it delivers reasonable performance even when components are rerendered often. It is based on a Virtual Dom which performs diffing which should prevent unnecessary re-renders even when large parts of the component tree are rerun. On top of this, Dioxus groups static parts of the RSX tree together to skip diffing them entirely." + } + h2 { id: "type-safety", + Link { + to: BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::TypeSafety, + }, + class: "header", + "Type Safety" + } + } + p { + "As teams grow, the Type safety of Rust is a huge advantage. Dioxus should leverage this advantage to make it easy to build applications with large teams." + } + p { + "To take full advantage of Rust's type system, Dioxus should try to avoid exposing public " + code { "Any" } + " types and string-ly typed APIs where possible." + } + h2 { id: "developer-experience", + Link { + to: BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::DeveloperExperience, + }, + class: "header", + "Developer Experience" + } + } + p { "Dioxus should be easy to learn and ergonomic to use." } + ul { + li { + p { + "The API of Dioxus attempts to remain close to React's API where possible. This makes it easier for people to learn Dioxus if they already know React" + } + } + li { + p { + "We can avoid the tradeoff between simplicity and flexibility by providing multiple layers of API: One for the very common use case, one for low-level control" + } + ul { + li { + "Hooks: the hooks crate has the most common use cases, but " + code { "cx.hook" } + " provides a way to access the underlying persistent reference if needed." + } + li { + "The builder pattern in platform Configs: The builder pattern is used to default to the most common use case, but users can change the defaults if needed." + } + } + } + li { + p { "Documentation:" } + ul { + li { "All public APIs should have rust documentation" } + li { + "Examples should be provided for all public features. These examples both serve as documentation and testing. They are checked by CI to ensure that they continue to compile" + } + li { "The most common workflows should be documented in the guide" } + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ContributingRoadmapSection { + #[default] + Empty, + RoadmapFeatureSet, + Features, + Roadmap, + Core, + Ssr, + Desktop, + Mobile, + BundlingCli, + EssentialHooks, + WorkInProgress, + BuildTool, + ServerComponentSupport, + NativeRendering, +} +impl std::str::FromStr for ContributingRoadmapSection { + type Err = ContributingRoadmapSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "roadmap--feature-set" => Ok(Self::RoadmapFeatureSet), + "features" => Ok(Self::Features), + "roadmap" => Ok(Self::Roadmap), + "core" => Ok(Self::Core), + "ssr" => Ok(Self::Ssr), + "desktop" => Ok(Self::Desktop), + "mobile" => Ok(Self::Mobile), + "bundling-cli" => Ok(Self::BundlingCli), + "essential-hooks" => Ok(Self::EssentialHooks), + "work-in-progress" => Ok(Self::WorkInProgress), + "build-tool" => Ok(Self::BuildTool), + "server-component-support" => Ok(Self::ServerComponentSupport), + "native-rendering" => Ok(Self::NativeRendering), + _ => Err(ContributingRoadmapSectionParseError), + } + } +} +impl std::fmt::Display for ContributingRoadmapSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::RoadmapFeatureSet => f.write_str("roadmap--feature-set"), + Self::Features => f.write_str("features"), + Self::Roadmap => f.write_str("roadmap"), + Self::Core => f.write_str("core"), + Self::Ssr => f.write_str("ssr"), + Self::Desktop => f.write_str("desktop"), + Self::Mobile => f.write_str("mobile"), + Self::BundlingCli => f.write_str("bundling-cli"), + Self::EssentialHooks => f.write_str("essential-hooks"), + Self::WorkInProgress => f.write_str("work-in-progress"), + Self::BuildTool => f.write_str("build-tool"), + Self::ServerComponentSupport => f.write_str("server-component-support"), + Self::NativeRendering => f.write_str("native-rendering"), + } + } +} +#[derive(Debug)] +pub struct ContributingRoadmapSectionParseError; +impl std::fmt::Display for ContributingRoadmapSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ContributingRoadmapSectionroadmap--feature-set, features, roadmap, core, ssr, desktop, mobile, bundling-cli, essential-hooks, work-in-progress, build-tool, server-component-support, native-rendering", + )?; + Ok(()) + } +} +impl std::error::Error for ContributingRoadmapSectionParseError {} +#[component(no_case_check)] +pub fn ContributingRoadmap(section: ContributingRoadmapSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "roadmap--feature-set", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::RoadmapFeatureSet, + }, + class: "header", + "Roadmap & Feature-set" + } + } + p { + "This feature set and roadmap can help you decide if what Dioxus can do today works for you." + } + p { + "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 " + Link { to: "https://discord.gg/XgGxMSkvUM", "joining the discord" } + "." + } + p { "Generally, here's the status of each platform:" } + ul { + li { + p { + strong { "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." + } + } + li { + p { + strong { "SSR" } + ": Dioxus is a great choice for pre-rendering, hydration, and rendering HTML on a web endpoint. Be warned – the VirtualDom is not (currently) " + code { "Send + Sync" } + "." + } + } + li { + p { + strong { "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." + } + } + li { + p { + strong { "Mobile" } + ": Mobile support is very young. You'll be figuring things out as you go and there are not many support crates for peripherals." + } + } + li { + p { + strong { "LiveView" } + ": LiveView support is very young. You'll be figuring things out as you go. Thankfully, none of it is too hard and any work can be upstreamed into Dioxus." + } + } + } + h2 { id: "features", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Features, + }, + class: "header", + "Features" + } + } + hr {} + table { + thead { + th { "Feature" } + th { "Status" } + th { "Description" } + } + tr { + th { "Conditional Rendering" } + th { "x" } + th { "if/then to hide/show component" } + } + tr { + th { "Map, Iterator" } + th { "x" } + th { "map/filter/reduce to produce rsx!" } + } + tr { + th { "Keyed Components" } + th { "x" } + th { "advanced diffing with keys" } + } + tr { + th { "Web" } + th { "x" } + th { "renderer for web browser" } + } + tr { + th { "Desktop (webview)" } + th { "x" } + th { "renderer for desktop" } + } + tr { + th { "Shared State (Context)" } + th { "x" } + th { "share state through the tree" } + } + tr { + th { "Hooks" } + th { "x" } + th { "memory cells in components" } + } + tr { + th { "SSR" } + th { "x" } + th { "render directly to string" } + } + tr { + th { "Component Children" } + th { "x" } + th { "cx.children() as a list of nodes" } + } + tr { + th { "Headless components" } + th { "x" } + th { "components that don't return real elements" } + } + tr { + th { "Fragments" } + th { "x" } + th { "multiple elements without a real root" } + } + tr { + th { "Manual Props" } + th { "x" } + th { "Manually pass in props with spread syntax" } + } + tr { + th { "Controlled Inputs" } + th { "x" } + th { "stateful wrappers around inputs" } + } + tr { + th { "CSS/Inline Styles" } + th { "x" } + th { "syntax for inline styles/attribute groups" } + } + tr { + th { "Custom elements" } + th { "x" } + th { "Define new element primitives" } + } + tr { + th { "Suspense" } + th { "x" } + th { "schedule future render from future/promise" } + } + tr { + th { "Integrated error handling" } + th { "x" } + th { "Gracefully handle errors with ? syntax" } + } + tr { + th { "NodeRef" } + th { "x" } + th { "gain direct access to nodes" } + } + tr { + th { "Re-hydration" } + th { "x" } + th { "Pre-render to HTML to speed up first contentful paint" } + } + tr { + th { "Jank-Free Rendering" } + th { "x" } + th { "Large diffs are segmented across frames for silky-smooth transitions" } + } + tr { + th { "Effects" } + th { "x" } + th { "Run effects after a component has been committed to render" } + } + tr { + th { "Portals" } + th { "*" } + th { "Render nodes outside of the traditional tree structure" } + } + tr { + th { "Cooperative Scheduling" } + th { "*" } + th { "Prioritize important events over non-important events" } + } + tr { + th { "Server Components" } + th { "*" } + th { "Hybrid components for SPA and Server" } + } + tr { + th { "Bundle Splitting" } + th { "i" } + th { "Efficiently and asynchronously load the app" } + } + tr { + th { "Lazy Components" } + th { "i" } + th { "Dynamically load the new components as the page is loaded" } + } + tr { + th { "1st class global state" } + th { "x" } + th { "redux/recoil/mobx on top of context" } + } + tr { + th { "Runs natively" } + th { "x" } + th { "runs as a portable binary w/o a runtime (Node)" } + } + tr { + th { "Subtree Memoization" } + th { "x" } + th { "skip diffing static element subtrees" } + } + tr { + th { "High-efficiency templates" } + th { "x" } + th { "rsx! calls are translated to templates on the DOM's side" } + } + tr { + th { "Compile-time correct" } + th { "x" } + th { "Throw errors on invalid template layouts" } + } + tr { + th { "Heuristic Engine" } + th { "x" } + th { "track component memory usage to minimize future allocations" } + } + tr { + th { "Fine-grained reactivity" } + th { "i" } + th { "Skip diffing for fine-grain updates" } + } + } + ul { + li { "x = implemented and working" } + li { "* = actively being worked on" } + li { "i = not yet implemented or being worked on" } + } + h2 { id: "roadmap", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Roadmap, + }, + class: "header", + "Roadmap" + } + } + p { "These Features are planned for the future of Dioxus:" } + h3 { id: "core", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Core, + }, + class: "header", + "Core" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Release of Dioxus Core" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Upgrade documentation to include more theory and be more comprehensive" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Support for HTML-side templates for lightning-fast dom manipulation" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Support for multiple renderers for same virtualdom (subtrees)" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Support for ThreadSafe (Send + Sync)" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Support for Portals" + } + } + h3 { id: "ssr", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Ssr, + }, + class: "header", + "SSR" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "SSR Support + Hydration" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Integrated suspense support for SSR" + } + } + h3 { id: "desktop", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Desktop, + }, + class: "header", + "Desktop" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Declarative window management" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Templates for building/bundling" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Access to Canvas/WebGL context natively" + } + } + h3 { id: "mobile", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Mobile, + }, + class: "header", + "Mobile" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Mobile standard library" + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "GPS" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Camera" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "filesystem" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Biometrics" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "WiFi" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Bluetooth" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Notifications" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Clipboard" + } + } + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Animations" + } + } + h3 { id: "bundling-cli", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::BundlingCli, + }, + class: "header", + "Bundling (CLI)" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Translation from HTML into RSX" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Dev server" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Live reload" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Translation from JSX into RSX" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Hot module replacement" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Code splitting" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Asset macros" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Css pipeline" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Image pipeline" + } + } + h3 { id: "essential-hooks", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::EssentialHooks, + }, + class: "header", + "Essential hooks" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Router" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Global state management" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Resize observer" + } + } + h2 { id: "work-in-progress", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::WorkInProgress, + }, + class: "header", + "Work in Progress" + } + } + h3 { id: "build-tool", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::BuildTool, + }, + class: "header", + "Build Tool" + } + } + p { + "We are currently working on our own build tool called " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/master/packages/cli", + "Dioxus CLI" + } + " which will support:" + } + ul { + li { "an interactive TUI" } + li { "on-the-fly reconfiguration" } + li { "hot CSS reloading" } + li { "two-way data binding between browser and source code" } + li { + "an interpreter for " + code { "rsx!" } + } + li { "ability to publish to github/netlify/vercel" } + li { "bundling for iOS/Desktop/etc" } + } + h3 { id: "server-component-support", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::ServerComponentSupport, + }, + class: "header", + "Server Component Support" + } + } + p { + "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." + } + h3 { id: "native-rendering", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::NativeRendering, + }, + class: "header", + "Native rendering" + } + } + p { + "We are currently working on a native renderer for Dioxus using WGPU called " + Link { to: "https://github.com/DioxusLabs/blitz/", "Blitz" } + ". This will allow you to build apps that are rendered natively for iOS, Android, and Desktop." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum MigrationIndexSection { + #[default] + Empty, + _03MigrationGuide, +} +impl std::str::FromStr for MigrationIndexSection { + type Err = MigrationIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "03-migration-guide" => Ok(Self::_03MigrationGuide), + _ => Err(MigrationIndexSectionParseError), + } + } +} +impl std::fmt::Display for MigrationIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::_03MigrationGuide => f.write_str("03-migration-guide"), + } + } +} +#[derive(Debug)] +pub struct MigrationIndexSectionParseError; +impl std::fmt::Display for MigrationIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of MigrationIndexSection03-migration-guide", + )?; + Ok(()) + } +} +impl std::error::Error for MigrationIndexSectionParseError {} +#[component(no_case_check)] +pub fn MigrationIndex(section: MigrationIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "03-migration-guide", + Link { + to: BookRoute::MigrationIndex { + section: MigrationIndexSection::_03MigrationGuide, + }, + class: "header", + "0.3 Migration Guide" + } + } + p { + "This guide will outline the API changes between the " + code { "0.3" } + " and " + code { "0.4" } + " releases. The two major breaking changes in this release are how hot reloading works on desktop platforms and how the router works:" + } + ul { + li { + Link { + to: BookRoute::MigrationHotReload { + section: MigrationHotReloadSection::Empty, + }, + "Hot reload" + } + } + li { + Link { + to: BookRoute::MigrationRouter { + section: MigrationRouterSection::Empty, + }, + "Router" + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum MigrationRouterSection { + #[default] + Empty, + Router, + DefiningYourRouter, + LinkingToRoutes, + ExternalLinks, + UseRouter, + UseRoute, + UseNavigator, + NewFeatures, +} +impl std::str::FromStr for MigrationRouterSection { + type Err = MigrationRouterSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "router" => Ok(Self::Router), + "defining-your-router" => Ok(Self::DefiningYourRouter), + "linking-to-routes" => Ok(Self::LinkingToRoutes), + "external-links" => Ok(Self::ExternalLinks), + "use-router" => Ok(Self::UseRouter), + "use-route" => Ok(Self::UseRoute), + "use-navigator" => Ok(Self::UseNavigator), + "new-features" => Ok(Self::NewFeatures), + _ => Err(MigrationRouterSectionParseError), + } + } +} +impl std::fmt::Display for MigrationRouterSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Router => f.write_str("router"), + Self::DefiningYourRouter => f.write_str("defining-your-router"), + Self::LinkingToRoutes => f.write_str("linking-to-routes"), + Self::ExternalLinks => f.write_str("external-links"), + Self::UseRouter => f.write_str("use-router"), + Self::UseRoute => f.write_str("use-route"), + Self::UseNavigator => f.write_str("use-navigator"), + Self::NewFeatures => f.write_str("new-features"), + } + } +} +#[derive(Debug)] +pub struct MigrationRouterSectionParseError; +impl std::fmt::Display for MigrationRouterSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of MigrationRouterSectionrouter, defining-your-router, linking-to-routes, external-links, use-router, use-route, use-navigator, new-features", + )?; + Ok(()) + } +} +impl std::error::Error for MigrationRouterSectionParseError {} +#[component(no_case_check)] +pub fn MigrationRouter(section: MigrationRouterSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "router", + Link { + to: BookRoute::MigrationRouter { + section: MigrationRouterSection::Router, + }, + class: "header", + "Router" + } + } + p { + "The router has been entirely rewritten in the " + code { "0.4" } + " release to provide type safe routes. This guide serves to help you upgrade your project to the new router. For more information on the router, see the " + Link { + to: BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }, + "router guide" + } + "." + } + h2 { id: "defining-your-router", + Link { + to: BookRoute::MigrationRouter { + section: MigrationRouterSection::DefiningYourRouter, + }, + class: "header", + "Defining Your Router" + } + } + p { "Previously, you defined your route with components:" } + CodeBlock { contents: "
\nrsx! {{\n    Router::<Route> {{\n        Route {{ to: "/home", Home {{}} }}\n        Route {{ to: "/blog", Blog {{}} }}\n        // BlogPost has a dynamic id\n        Route {{ to: "/blog/:id", BlogPost {{}} }}\n    }}\n}}
\n" } + p { "Now you must define your routes as an enum of possible routes:" } + CodeBlock { + contents: "
\nuse dioxus_router::prelude::*;\nuse dioxus::prelude::*;\n\n#[derive(Routable, PartialEq, Debug, Clone)]\nenum Route {{\n    #[route("/home")]\n    // This route will render the Home component with the HomeProps props. (make sure you have the props imported)\n    // You can modify the props by passing extra arguments to the macro. For example, if you want the Home variant to render a component called Homepage, you could use:\n    // #[route("/home", Homepage)]\n    Home {{}},\n    #[route("/blog")]\n    Blog {{}},\n    // BlogPost has a dynamic id\n    #[route("/blog/:id")]\n    BlogPost {{\n        id: usize\n    }}\n}}\n\n#[component]\nfn Home(cx: Scope) -> Element {{\n    todo!()\n}}\n\n#[component]\nfn Blog(cx: Scope) -> Element {{\n    todo!()\n}}\n\n#[component]\nfn BlogPost(cx: Scope, id: usize) -> Element {{\n    // Note that you have access to id here in a type safe way without calling any extra functions!\n    todo!()\n}}
\n", + } + h2 { id: "linking-to-routes", + Link { + to: BookRoute::MigrationRouter { + section: MigrationRouterSection::LinkingToRoutes, + }, + class: "header", + "Linking to routes" + } + } + p { + "Now that routes are enums, you should use the enum as the route in Links. If you try to link to a route that does not exist, you will get a compiler error." + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\nfn Component(cx: Scope) -> Element {{\n    render! {{\n        Link {{\n            to: Route::BlogPost {{ id: 123 }},\n            "blog post"\n        }}\n    }}\n}}
\n", + } + h2 { id: "external-links", + Link { + to: BookRoute::MigrationRouter { + section: MigrationRouterSection::ExternalLinks, + }, + class: "header", + "External Links" + } + } + p { "To link to external routes, you can use a string:" } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\nfn Component(cx: Scope) -> Element {{\n    render! {{\n        Link {{\n            to: "https://google.com",\n            "google"\n        }}\n    }}\n}}
\n", + } + h2 { id: "use-router", + Link { + to: BookRoute::MigrationRouter { + section: MigrationRouterSection::UseRouter, + }, + class: "header", + "use_router" + } + } + p { + "The " + code { "use_router" } + " hook has been split into two separate hooks: the " + code { "use_route" } + " hook and the " + code { "use_navigator" } + " hook." + } + h3 { id: "use-route", + Link { + to: BookRoute::MigrationRouter { + section: MigrationRouterSection::UseRoute, + }, + class: "header", + "use_route" + } + } + p { "The new use_route hook lets you read the current route:" } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n#[derive(Clone, Routable)]\nenum Route {{\n    #[route("/")]\n    Index {{}},\n}}\n\nfn App(cx: Scope) -> Element {{\n    render! {{\n        h1 {{ "App" }}\n        Router::<Route> {{}}\n    }}\n}}\n\n#[component]\nfn Index(cx: Scope) -> Element {{\n    // Read from (and subscribe to the current route)\n    let path = use_route(&cx).unwrap();\n    render! {{\n        h2 {{ "Current Path" }}\n        p {{ "{{path}}" }}\n    }}\n}}
\n", + } + h3 { id: "use-navigator", + Link { + to: BookRoute::MigrationRouter { + section: MigrationRouterSection::UseNavigator, + }, + class: "header", + "use_navigator" + } + } + p { + code { "use_navigator" } + " lets you change the route programmatically:" + } + CodeBlock { + contents: "
\n#[component]\nfn Home(cx: Scope) -> Element {{\n    let nav = use_navigator(cx);\n\n    // push\n    nav.push(Route::PageNotFound {{ route: vec![] }});\n\n    // replace\n    nav.replace(Route::Home {{}});\n\n    // go back\n    nav.go_back();\n\n    // go forward\n    nav.go_forward();\n\n    render! {{\n        h1 {{ "Welcome to the Dioxus Blog!" }}\n    }}\n}}
\n", + name: "navigator.rs".to_string(), + } + p { + "You can read more about programmatic navigation in the " + Link { + to: BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::Empty, + }, + "Router Book" + } + "." + } + h3 { id: "new-features", + Link { + to: BookRoute::MigrationRouter { + section: MigrationRouterSection::NewFeatures, + }, + class: "header", + "New features" + } + } + p { "In addition to these changes, there have been many new features added to the router:" } + ul { + li { + Link { + to: BookRoute::RouterReferenceStaticGeneration { + section: RouterReferenceStaticGenerationSection::Empty, + }, + "static generation support" + } + } + li { + Link { + to: BookRoute::RouterReferenceLayouts { + section: RouterReferenceLayoutsSection::Empty, + }, + "Layouts" + } + } + li { + Link { + to: BookRoute::RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection::Empty, + }, + "Nesting" + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum MigrationHotReloadSection { + #[default] + Empty, + HotReloading, +} +impl std::str::FromStr for MigrationHotReloadSection { + type Err = MigrationHotReloadSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "hot-reloading" => Ok(Self::HotReloading), + _ => Err(MigrationHotReloadSectionParseError), + } + } +} +impl std::fmt::Display for MigrationHotReloadSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::HotReloading => f.write_str("hot-reloading"), + } + } +} +#[derive(Debug)] +pub struct MigrationHotReloadSectionParseError; +impl std::fmt::Display for MigrationHotReloadSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of MigrationHotReloadSectionhot-reloading", + )?; + Ok(()) + } +} +impl std::error::Error for MigrationHotReloadSectionParseError {} +#[component(no_case_check)] +pub fn MigrationHotReload(section: MigrationHotReloadSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "hot-reloading", + Link { + to: BookRoute::MigrationHotReload { + section: MigrationHotReloadSection::HotReloading, + }, + class: "header", + "Hot reloading" + } + } + p { + "Desktop hot reloading has changed in the " + code { "0.4" } + " release to use the " + Link { + to: BookRoute::CliIndex { + section: CliIndexSection::Empty, + }, + "Dioxus CLI" + } + " for all platforms." + } + p { + "Previously, you may have included the " + code { "hot_reload_init!" } + " macro in your main function. This is no longer needed." + } + p { "old:" } + CodeBlock { contents: "
\nfn main() {{\n    hot_reload_init!();\n    // ...\n}}
\n" } + p { "new:" } + CodeBlock { contents: "
\nfn main() {{\n    // ...\n}}
\n" } + p { + "Now you can run your project with the dioxus CLI by passing the " + code { "--platform" } + " flag:" + } + CodeBlock { contents: "
\ndx serve --platform desktop --hot-reload
\n" } + } +} + +use super::*; diff --git a/packages/docs-router/src/docs/router_05.rs b/packages/docs-router/src/docs/router_05.rs new file mode 100644 index 000000000..b3a3f04ce --- /dev/null +++ b/packages/docs-router/src/docs/router_05.rs @@ -0,0 +1,19967 @@ +use dioxus::prelude::*; +#[derive( + Clone, + Copy, + dioxus_router::prelude::Routable, + PartialEq, + Eq, + Hash, + Debug, + serde::Serialize, + serde::Deserialize, +)] +pub enum BookRoute { + #[route("/#:section")] + Index { section: IndexSection }, + #[route("/getting_started/#:section")] + GettingStartedIndex { section: GettingStartedIndexSection }, + #[route("/guide/#:section")] + GuideIndex { section: GuideIndexSection }, + #[route("/guide/your_first_component#:section")] + GuideYourFirstComponent { + section: GuideYourFirstComponentSection, + }, + #[route("/guide/state#:section")] + GuideState { section: GuideStateSection }, + #[route("/guide/data_fetching#:section")] + GuideDataFetching { section: GuideDataFetchingSection }, + #[route("/guide/full_code#:section")] + GuideFullCode { section: GuideFullCodeSection }, + #[route("/reference/#:section")] + ReferenceIndex { section: ReferenceIndexSection }, + #[route("/reference/rsx#:section")] + ReferenceRsx { section: ReferenceRsxSection }, + #[route("/reference/components#:section")] + ReferenceComponents { section: ReferenceComponentsSection }, + #[route("/reference/component_props#:section")] + ReferenceComponentProps { + section: ReferenceComponentPropsSection, + }, + #[route("/reference/event_handlers#:section")] + ReferenceEventHandlers { + section: ReferenceEventHandlersSection, + }, + #[route("/reference/hooks#:section")] + ReferenceHooks { section: ReferenceHooksSection }, + #[route("/reference/user_input#:section")] + ReferenceUserInput { section: ReferenceUserInputSection }, + #[route("/reference/context#:section")] + ReferenceContext { section: ReferenceContextSection }, + #[route("/reference/dynamic_rendering#:section")] + ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection, + }, + #[route("/reference/router#:section")] + ReferenceRouter { section: ReferenceRouterSection }, + #[route("/reference/use_resource#:section")] + ReferenceUseResource { + section: ReferenceUseResourceSection, + }, + #[route("/reference/use_coroutine#:section")] + ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection, + }, + #[route("/reference/spawn#:section")] + ReferenceSpawn { section: ReferenceSpawnSection }, + #[route("/reference/assets#:section")] + ReferenceAssets { section: ReferenceAssetsSection }, + #[route("/reference/choosing_a_web_renderer#:section")] + ReferenceChoosingAWebRenderer { + section: ReferenceChoosingAWebRendererSection, + }, + #[route("/reference/desktop/#:section")] + ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection, + }, + #[route("/reference/mobile/#:section")] + ReferenceMobileIndex { + section: ReferenceMobileIndexSection, + }, + #[route("/reference/mobile/apis#:section")] + ReferenceMobileApis { section: ReferenceMobileApisSection }, + #[route("/reference/web/#:section")] + ReferenceWebIndex { section: ReferenceWebIndexSection }, + #[route("/reference/ssr#:section")] + ReferenceSsr { section: ReferenceSsrSection }, + #[route("/reference/liveview#:section")] + ReferenceLiveview { section: ReferenceLiveviewSection }, + #[route("/reference/fullstack/#:section")] + ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection, + }, + #[route("/reference/fullstack/server_functions#:section")] + ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection, + }, + #[route("/reference/fullstack/extractors#:section")] + ReferenceFullstackExtractors { + section: ReferenceFullstackExtractorsSection, + }, + #[route("/reference/fullstack/middleware#:section")] + ReferenceFullstackMiddleware { + section: ReferenceFullstackMiddlewareSection, + }, + #[route("/reference/fullstack/authentication#:section")] + ReferenceFullstackAuthentication { + section: ReferenceFullstackAuthenticationSection, + }, + #[route("/reference/fullstack/routing#:section")] + ReferenceFullstackRouting { + section: ReferenceFullstackRoutingSection, + }, + #[route("/router/#:section")] + RouterIndex { section: RouterIndexSection }, + #[route("/router/example/#:section")] + RouterExampleIndex { section: RouterExampleIndexSection }, + #[route("/router/example/first-route#:section")] + RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection, + }, + #[route("/router/example/building-a-nest#:section")] + RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection, + }, + #[route("/router/example/navigation-targets#:section")] + RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection, + }, + #[route("/router/example/redirection-perfection#:section")] + RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection, + }, + #[route("/router/example/full-code#:section")] + RouterExampleFullCode { + section: RouterExampleFullCodeSection, + }, + #[route("/router/reference/#:section")] + RouterReferenceIndex { + section: RouterReferenceIndexSection, + }, + #[route("/router/reference/routes/#:section")] + RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection, + }, + #[route("/router/reference/routes/nested#:section")] + RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection, + }, + #[route("/router/reference/layouts#:section")] + RouterReferenceLayouts { + section: RouterReferenceLayoutsSection, + }, + #[route("/router/reference/navigation/#:section")] + RouterReferenceNavigationIndex { + section: RouterReferenceNavigationIndexSection, + }, + #[route("/router/reference/navigation/programmatic#:section")] + RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection, + }, + #[route("/router/reference/history-providers#:section")] + RouterReferenceHistoryProviders { + section: RouterReferenceHistoryProvidersSection, + }, + #[route("/router/reference/history-buttons#:section")] + RouterReferenceHistoryButtons { + section: RouterReferenceHistoryButtonsSection, + }, + #[route("/router/reference/routing-update-callback#:section")] + RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection, + }, + #[route("/cookbook/#:section")] + CookbookIndex { section: CookbookIndexSection }, + #[route("/cookbook/publishing#:section")] + CookbookPublishing { section: CookbookPublishingSection }, + #[route("/cookbook/antipatterns#:section")] + CookbookAntipatterns { + section: CookbookAntipatternsSection, + }, + #[route("/cookbook/error_handling#:section")] + CookbookErrorHandling { + section: CookbookErrorHandlingSection, + }, + #[route("/cookbook/integrations/#:section")] + CookbookIntegrationsIndex { + section: CookbookIntegrationsIndexSection, + }, + #[route("/cookbook/integrations/logging#:section")] + CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection, + }, + #[route("/cookbook/integrations/internationalization#:section")] + CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection, + }, + #[route("/cookbook/state/#:section")] + CookbookStateIndex { section: CookbookStateIndexSection }, + #[route("/cookbook/state/external/#:section")] + CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection, + }, + #[route("/cookbook/state/custom_hooks/#:section")] + CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection, + }, + #[route("/cookbook/testing#:section")] + CookbookTesting { section: CookbookTestingSection }, + #[route("/cookbook/tailwind#:section")] + CookbookTailwind { section: CookbookTailwindSection }, + #[route("/cookbook/custom_renderer#:section")] + CookbookCustomRenderer { + section: CookbookCustomRendererSection, + }, + #[route("/cookbook/optimizing#:section")] + CookbookOptimizing { section: CookbookOptimizingSection }, + #[route("/CLI/#:section")] + CliIndex { section: CliIndexSection }, + #[route("/CLI/creating#:section")] + CliCreating { section: CliCreatingSection }, + #[route("/CLI/configure#:section")] + CliConfigure { section: CliConfigureSection }, + #[route("/CLI/translate#:section")] + CliTranslate { section: CliTranslateSection }, + #[route("/contributing/#:section")] + ContributingIndex { section: ContributingIndexSection }, + #[route("/contributing/project_structure#:section")] + ContributingProjectStructure { + section: ContributingProjectStructureSection, + }, + #[route("/contributing/guiding_principles#:section")] + ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection, + }, + #[route("/contributing/roadmap#:section")] + ContributingRoadmap { section: ContributingRoadmapSection }, + #[route("/migration/#:section")] + MigrationIndex { section: MigrationIndexSection }, + #[route("/migration/hooks#:section")] + MigrationHooks { section: MigrationHooksSection }, + #[route("/migration/state#:section")] + MigrationState { section: MigrationStateSection }, + #[route("/migration/fermi#:section")] + MigrationFermi { section: MigrationFermiSection }, + #[route("/migration/props#:section")] + MigrationProps { section: MigrationPropsSection }, +} +impl BookRoute { + /// Get the markdown for a page by its ID + pub const fn page_markdown(id: use_mdbook::mdbook_shared::PageId) -> &'static str { + match id.0 { + 16usize => { + "# Router\n\nIn 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. For a desktop app, these scenes might be different views in your app.\n\nTo unify these platforms, Dioxus provides a first-party solution for scene management called Dioxus Router.\n\n## What is it?\n\nFor an app like the Dioxus landing page (https://dioxuslabs.com), we want to have several different scenes:\n\n* Homepage\n* Blog\n\nEach of these scenes is independent – we don't want to render both the homepage and blog at the same time.\n\nThe Dioxus router makes it easy to create these scenes. To make sure we're using the router, add the `router` feature to your `dioxus` dependency:\n\n````shell\ncargo add dioxus@0.5.0 --features router\n````\n\n## Using the router\n\nUnlike other routers in the Rust ecosystem, our router is built declaratively at compile time. This makes it possible to compose our app layout simply by defining an enum.\n\n````rust@router_reference.rs\n// All of our routes will be a variant of this Route enum\n#[derive(Routable, PartialEq, Clone)]\nenum Route {\n // if the current location is \"/home\", render the Home component\n #[route(\"/home\")]\n Home {},\n // if the current location is \"/blog\", render the Blog component\n #[route(\"/blog\")]\n Blog {},\n}\n\nfn Home() -> Element {\n todo!()\n}\n\nfn Blog() -> Element {\n todo!()\n}\n````\n\nWhenever we visit this app, we will get either the Home component or the Blog component rendered depending on which route we enter at. If neither of these routes match the current location, then nothing will render.\n\nWe can fix this one of two ways:\n\n* A fallback 404 page\n\n````rust@router_reference.rs\n// All of our routes will be a variant of this Route enum\n#[derive(Routable, PartialEq, Clone)]\nenum Route {\n #[route(\"/home\")]\n Home {},\n #[route(\"/blog\")]\n Blog {},\n // if the current location doesn't match any of the above routes, render the NotFound component\n #[route(\"/:..segments\")]\n NotFound { segments: Vec },\n}\n\nfn Home() -> Element {\n todo!()\n}\n\nfn Blog() -> Element {\n todo!()\n}\n\n#[component]\nfn NotFound(segments: Vec) -> Element {\n todo!()\n}\n````\n\n* Redirect 404 to home\n\n````rust@router_reference.rs\n// All of our routes will be a variant of this Route enum\n#[derive(Routable, PartialEq, Clone)]\nenum Route {\n #[route(\"/home\")]\n // if the current location doesn't match any of the other routes, redirect to \"/home\"\n #[redirect(\"/:..segments\", |segments: Vec| Route::Home {})]\n Home {},\n #[route(\"/blog\")]\n Blog {},\n}\n````\n\n## Links\n\nFor our app to navigate these routes, we can provide clickable elements called Links. These simply wrap `
` elements that, when clicked, navigate the app to the given location. Because our route is an enum of valid routes, if you try to link to a page that doesn't exist, you will get a compiler error.\n\n````rust@router_reference.rs\nrsx! {\n Link { to: Route::Home {}, \"Go home!\" }\n}\n````\n\n## More reading\n\nThis page is just a very brief overview of the router. For more information, check out the [router book](../router/index.md) or some of the [router examples](https://github.com/DioxusLabs/dioxus/blob/master/examples/router.rs)." + } + 34usize => { + "# Introduction\n\n > \n > If you are not familiar with Dioxus itself, check out the [Dioxus guide](../guide/index.md) first.\n\nWhether you are building a website, desktop app, or mobile app, splitting your app's views into \"pages\" can be an effective method for organization and maintainability.\n\nFor this purpose, Dioxus provides a router. Use the `cargo add` command to add the dependency:\n\n````sh\ncargo add dioxus@0.5.0 --features router\n````\n\nThen, add this to your `Dioxus.toml` (learn more about configuration [here](../CLI/configure)):\n\n````toml\n[web.watcher]\nindex_on_404 = true\n````\n\n > \n > This configuration only works when using `dx serve`. If you host your app in a different way (which you most likely do in production), you need to find out how to add a fallback 404 page to your app, and make it a copy of the generated `dist/index.html`.\n\nThis will instruct `dx serve` to redirect any unknown route to the index, to then be resolved by the router.\nThe router works on the client. If we connect through the index route (e.g., `localhost:8080`, then click a link to go to `localhost:8080/contact`), the app renders the new route without reloading.\nHowever, when we go to a route *before* going to the index (go straight to `localhost:8080/contact`), we are trying to access a static route from the server, but the only static route on our server is the index (because the Dioxus frontend is a Single Page Application) and it will fail unless we redirect all missing routes to the index.\n\nThis book is intended to get you up to speed with Dioxus Router. It is split\ninto two sections:\n\n1. The [reference](reference/index.md) section explains individual features in\n depth. You can read it from start to finish, or you can read individual chapters\n in whatever order you want.\n1. If you prefer a learning-by-doing approach, you can check out the\n *[example project](example/index.md)*. It guides you through\n creating a dioxus app, setting up the router, and using some of its\n functionality.\n\n > \n > Please note that this is not the only documentation for the Dioxus Router. You\n > can also check out the [API Docs](https://docs.rs/dioxus-router/)." + } + 53usize => { + "# Error handling\n\nA selling point of Rust for web development is the reliability of always knowing where errors can occur and being forced to handle them\n\nHowever, we haven't talked about error handling at all in this guide! In this chapter, we'll cover some strategies in handling errors to ensure your app never crashes.\n\n## The simplest – returning None\n\nAstute 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:\n\n````rust@error_handling.rs\nfn App() -> Element {\n None\n}\n````\n\nThis lets us add in some syntactic sugar for operations we think *shouldn't* fail, but we're still not confident enough to \"unwrap\" on.\n\n > \n > The nature of `Option` might change in the future as the `try` trait gets upgraded.\n\n````rust@error_handling.rs\nfn App() -> Element {\n // immediately return \"None\"\n let name = use_hook(|| Some(\"hi\"))?;\n\n todo!()\n}\n````\n\n## Early return on result\n\nBecause Rust can't accept both Options and Results with the existing try infrastructure, you'll need to manually handle Results. This can be done by converting them into Options or by explicitly handling them. If you choose to convert your Result into an Option and bubble it with a `?`, keep in mind that if you do hit an error you will lose error information and nothing will be rendered for that component.\n\n````rust@error_handling.rs\nfn App() -> Element {\n // Convert Result to Option\n let name: i32 = use_hook(|| \"1.234\").parse().ok()?;\n\n // Early return\n let count = use_hook(|| \"1.234\");\n let val: i32 = match count.parse() {\n Ok(val) => val,\n Err(err) => return rsx! {\"Parsing failed\"},\n };\n\n todo!()\n}\n````\n\nNotice that while hooks in Dioxus do not like being called in conditionals or loops, they *are* okay with early returns. Returning an error state early is a completely valid way of handling errors.\n\n## Match results\n\nThe next \"best\" way of handling errors in Dioxus is to match on the error locally. This is the most robust way of handling errors, but it doesn't scale to architectures beyond a single component.\n\nTo do this, we simply have an error state built into our component:\n\n````rust@error_handling.rs\nlet mut error = use_signal(|| None);\n````\n\nWhenever we perform an action that generates an error, we'll set that error state. We can then match on the error in a number of ways (early return, return Element, etc).\n\n````rust@error_handling.rs\nfn Commandline() -> Element {\n let mut error = use_signal(|| None);\n\n match error() {\n Some(error) => rsx! { h1 { \"An error occurred\" } },\n None => rsx! { input { oninput: move |_| error.set(Some(\"bad thing happened!\")) } },\n }\n}\n````\n\n## Passing error states through components\n\nIf you're dealing with a handful of components with minimal nesting, you can just pass the error handle into child components.\n\n````rust@error_handling.rs\nfn Commandline() -> Element {\n let error = use_signal(|| None);\n\n if let Some(error) = error() {\n return rsx! {\"An error occurred\"};\n }\n\n rsx! {\n Child { error }\n Child { error }\n Child { error }\n Child { error }\n }\n}\n\n#[component]\nfn Child(error: Signal>) -> Element {\n rsx! { input { oninput: move |_| error.set(Some(\"bad thing happened!\")) } }\n}\n````\n\nMuch like before, our child components can manually set the error during their own actions. The advantage to this pattern is that we can easily isolate error states to a few components at a time, making our app more predictable and robust.\n\n## Throwing errors\n\nDioxus provides a much easier way to handle errors: throwing them. Throwing errors combines the best parts of an error state and early return: you can easily throw and error with `?`, but you keep information about the error so that you can handle it in a parent component.\n\nYou can call `throw` on any `Result` type that implements `Debug` to turn it into an error state and then use `?` to return early if you do hit an error. You can capture the error state with an `ErrorBoundary` component that will render the a different component if an error is thrown in any of its children.\n\n````rust@error_handling.rs\nfn Parent() -> Element {\n rsx! {\n ErrorBoundary {\n handle_error: |error| {\n rsx! {\n \"Oops, we encountered an error. Please report {error} to the developer of this application\"\n }\n },\n ThrowsError {}\n }\n }\n}\n\nfn ThrowsError() -> Element {\n let name: i32 = use_hook(|| \"1.234\").parse().throw()?;\n\n todo!()\n}\n````\n\nYou can even nest `ErrorBoundary` components to capture errors at different levels of your app.\n\n````rust@error_handling.rs\nfn App() -> Element {\n rsx! {\n ErrorBoundary {\n handle_error: |error| {\n rsx! {\n \"Hmm, something went wrong. Please report {error} to the developer of this application\"\n }\n },\n Parent {}\n }\n }\n}\n\nfn Parent() -> Element {\n rsx! {\n ErrorBoundary {\n handle_error: |error| {\n rsx! {\n \"The child component encountered an error: {error}\"\n }\n },\n ThrowsError {}\n }\n }\n}\n\nfn ThrowsError() -> Element {\n let name: i32 = use_hook(|| \"1.234\").parse().throw()?;\n\n todo!()\n}\n````\n\nThis pattern is particularly helpful whenever your code generates a non-recoverable error. You can gracefully capture these \"global\" error states without panicking or handling state for each error yourself." + } + 1usize => { + "# Getting Started\n\nThis section will help you set up your Dioxus project!\n\n## Prerequisites\n\n### An Editor\n\nDioxus 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.\n\n### Rust\n\nHead over to [https://rust-lang.org](http://rust-lang.org) and install the Rust compiler.\n\nWe strongly recommend going through the [official Rust book](https://doc.rust-lang.org/book/ch01-00-getting-started.html) *completely*. However, we hope that a Dioxus app can serve as a great first Rust project. With Dioxus, you'll learn about:\n\n* Error handling\n* Structs, Functions, Enums\n* Closures\n* Macros\n\nWe've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need deep knowledge of async, lifetimes, or smart pointers until you start building complex Dioxus apps.\n\n### Platform-specific dependencies\n\nMost platforms don't require any additional dependencies, but if you are targeting desktop, you can install the following dependencies:\n\n````inject-dioxus\nDesktopDependencies {}\n````\n\n### Dioxus CLI\n\nNext, lets install the Dioxus CLI:\n\n````\ncargo install dioxus-cli\n````\n\nIf you get an OpenSSL error on installation, ensure the dependencies listed [here](https://docs.rs/openssl/latest/openssl/#automatic) are installed.\n\n## Create a new project\n\nYou can create a new Dioxus project by running the following command and following the prompts:\n\n````sh\ndx new\n````\n\nFirst you will need to select a platform. Each platform has its own reference with more information on how to set up a project for that platform. Here are the platforms we recommend starting with:\n\n* Web\n * [Client Side](../reference/web/index.md): runs in the browser through WebAssembly\n * [Fullstack](../reference/fullstack/index.md): renders to HTML text on the server and hydrates it on the client\n\n > \n > If you are not sure which web platform you want to use, check out the [choosing a web renderer](../reference/choosing_a_web_renderer.md) chapter.\n\n* WebView\n * [Desktop](../reference/desktop/index.md): runs in a web view on desktop\n * [Mobile](../reference/mobile/index.md): runs in a web view on mobile. Mobile is currently not supported by the dioxus CLI. The [mobile reference](../reference/mobile/index.md) has more information about setting up a mobile project\n\nNext, you can choose a styling library. For this project, we will use vanilla CSS.\n\nFinally, you can choose to start the project with the router enabled. The router is covered in the [router guide](../router/index.md).\n\n## Running the project\n\nOnce you have created your project, you can start it with the following command:\n\n````sh\ncd my_project\ndx serve\n````\n\nFor projects using the liveview template, run `dx serve --desktop`.\n\nFor Web targets the application will be served at [http://localhost:8080](http://localhost:8080)\n\n## Conclusion\n\nThat's it! You now have a working Dioxus project. You can continue learning about dioxus by [making a hackernews clone in the guide](../guide/index.md), or learning about specific topics/platforms in the [reference](../reference/index.md). If you have any questions, feel free to ask in the [discord](https://discord.gg/XgGxMSkvUM) or [open a discussion](https://github.com/DioxusLabs/dioxus/discussions)." + } + 20usize => { + "# Assets\n\n > \n > ⚠\u{fe0f} Support: Manganis is currently in alpha. API changes are planned and bugs are more likely\n\nAssets are files that are included in the final build of the application. They can be images, fonts, stylesheets, or any other file that is not a source file. Dioxus includes first class support for assets, and provides a simple way to include them in your application and automatically optimize them for production.\n\nAssets in dioxus are also compatible with libraries! If you are building a library, you can include assets in your library and they will be automatically included in the final build of any application that uses your library.\n\nFirst, you need to add the `manganis` crate to your `Cargo.toml` file:\n\n````sh\ncargo add manganis\n````\n\n## Including images\n\nTo include an asset in your application, you can simply wrap the path to the asset in a `mg!` call. For example, to include an image in your application, you can use the following code:\n\n````rust@assets.rs\nuse dioxus::prelude::*;\n\nfn App() -> Element {\n // You can link to assets that are relative to the package root or even link to an asset from a url\n // These assets will automatically be picked up by the dioxus cli, optimized, and bundled with your final applications\n const ASSET: Asset = asset!(\"/assets/static/ferrous_wave.png\");\n\n rsx! {\n img { src: \"{ASSET}\" }\n }\n}\n````\n\nYou can also optimize, resize, and preload images using the `mg!` macro. Choosing an optimized file type (like WebP) and a reasonable quality setting can significantly reduce the size of your images which helps your application load faster. For example, you can use the following code to include an optimized image in your application:\n\n````rust@assets.rs\npub const ENUM_ROUTER_IMG: Asset = asset!(\"/assets/static/enum_router.png\");\n\nfn EnumRouter() -> Element {\n rsx! {\n img { src: \"{ENUM_ROUTER_IMG}\" }\n }\n}\n````\n\n## Including arbitrary files\n\nIn dioxus desktop, you may want to include a file with data for your application. You can use the `file` function to include arbitrary files in your application. For example, you can use the following code to include a file in your application:\n\n````rust@assets.rs\n// You can also collect arbitrary files. Relative paths are resolved relative to the package root\nconst PATH_TO_BUNDLED_CARGO_TOML: &str = manganis::mg!(file(\"./Cargo.toml\"));\n````\n\nThese files will be automatically included in the final build of your application, and you can use them in your application as you would any other file.\n\n## Including stylesheets\n\nYou can include stylesheets in your application using the `mg!` macro. For example, you can use the following code to include a stylesheet in your application:\n\n````rust@assets.rs\n// You can also bundle stylesheets with your application\n// Any files that end with .css will be minified and bundled with your application even if you don't explicitly include them in your \nconst _: &str = manganis::mg!(file(\"./tailwind.css\"));\n````\n\n > \n > The [tailwind guide](../cookbook/tailwind.md) has more information on how to use tailwind with dioxus.\n\n## Conclusion\n\nDioxus provides first class support for assets, and makes it easy to include them in your application. You can include images, arbitrary files, and stylesheets in your application, and dioxus will automatically optimize them for production. This makes it easy to include assets in your application and ensure that they are optimized for production.\n\nYou can read more about assets and all the options available to optimize your assets in the [manganis documentation](https://docs.rs/manganis/0.2.2/manganis/)." + } + 25usize => { + "# Web\n\nTo run on the Web, your app must be compiled to WebAssembly and depend on the `dioxus` and `dioxus-web` crates.\n\nA build of Dioxus for the web will be roughly equivalent to the size of a React build (70kb vs 65kb) but it will load significantly faster because [WebAssembly can be compiled as it is streamed](https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/).\n\nExamples:\n\n* [TodoMVC](https://github.com/DioxusLabs/dioxus/blob/main/examples/todomvc.rs)\n* [Tailwind App](https://github.com/DioxusLabs/dioxus/tree/v0.5/examples/tailwind)\n\n[![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/dioxus/blob/main/examples/todomvc.rs)\n\n > \n > Note: Because of the limitations of Wasm, [not every crate will work](https://rustwasm.github.io/docs/book/reference/which-crates-work-with-wasm.html) with your web apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc).\n\n## Support\n\nThe Web is the best-supported target platform for Dioxus.\n\n* Because your app will be compiled to WASM you have access to browser APIs through [wasm-bindgen](https://rustwasm.github.io/docs/wasm-bindgen/introduction.html).\n* Dioxus provides hydration to resume apps that are rendered on the server. See the [fullstack](../fullstack/index.md) reference for more information.\n\n## Running Javascript\n\nDioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose.\n\nFor these cases, Dioxus web exposes the use_eval hook that allows you to run raw Javascript in the webview:\n\n````rust@eval.rs\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app);\n}\n\nfn app() -> Element {\n // You can create as many eval instances as you want\n let mut eval = eval(\n r#\"\n // You can send messages from JavaScript to Rust with the dioxus.send function\n dioxus.send(\"Hi from JS!\");\n // You can receive messages from Rust to JavaScript with the dioxus.recv function\n let msg = await dioxus.recv();\n console.log(msg);\n \"#,\n );\n\n // You can send messages to JavaScript with the send method\n eval.send(\"Hi from Rust!\".into()).unwrap();\n\n let future = use_resource(move || {\n to_owned![eval];\n async move {\n // You can receive any message from JavaScript with the recv method\n eval.recv().await.unwrap()\n }\n });\n\n match future.read_unchecked().as_ref() {\n Some(v) => rsx! {\n p { \"{v}\" }\n },\n _ => rsx! {\n p { \"hello\" }\n },\n }\n}\n\n````\n\nIf you are targeting web, but don't plan on targeting any other Dioxus renderer you can also use the generated wrappers in the [web-sys](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html) and [gloo](https://gloo-rs.web.app/) crates.\n\n## Customizing Index Template\n\nDioxus supports providing custom index.html templates. The index.html must include a `div` with the id `main` to be used. Hot Reload is still supported. An example\nis provided in the [PWA-Example](https://github.com/DioxusLabs/dioxus/blob/main/examples/PWA-example/index.html)." + } + 33usize => { + "# Routing\n\nYou can easily integrate your fullstack application with a client side router using Dioxus Router. This allows you to create different scenes in your app and navigate between them. You can read more about the router in the [router reference](../router.md)\n\n````rust@server_router.rs\n#![allow(non_snake_case)]\n\nuse axum::Router;\nuse dioxus::prelude::*;\n\nuse dioxus_router::prelude::*;\nuse serde::{Deserialize, Serialize};\n\nfn main() {\n launch(|| rsx! { Router:: {} });\n}\n\n#[derive(Clone, Routable, Debug, PartialEq, Serialize, Deserialize)]\nenum Route {\n #[route(\"/\")]\n Home {},\n #[route(\"/blog/:id\")]\n Blog { id: i32 },\n}\n\n#[component]\nfn Blog(id: i32) -> Element {\n rsx! {\n Link { to: Route::Home {}, \"Go to counter\" }\n table {\n tbody {\n for _ in 0..id {\n tr {\n for _ in 0..id {\n td { \"hello world!\" }\n }\n }\n }\n }\n }\n }\n}\n\n#[component]\nfn Home() -> Element {\n let mut count = use_signal(|| 0);\n let mut text = use_signal(|| \"...\".to_string());\n\n rsx! {\n Link { to: Route::Blog { id: count() }, \"Go to blog\" }\n div {\n h1 { \"High-Five counter: {count}\" }\n button { onclick: move |_| count += 1, \"Up high!\" }\n button { onclick: move |_| count -= 1, \"Down low!\" }\n button {\n onclick: move |_| {\n async move {\n if let Ok(data) = get_server_data().await {\n println!(\"Client received: {}\", data);\n text.set(data.clone());\n post_server_data(data).await.unwrap();\n }\n }\n },\n \"Run server function!\"\n }\n \"Server said: {text}\"\n }\n }\n}\n\n#[server(PostServerData)]\nasync fn post_server_data(data: String) -> Result<(), ServerFnError> {\n println!(\"Server received: {}\", data);\n\n Ok(())\n}\n\n#[server(GetServerData)]\nasync fn get_server_data() -> Result {\n Ok(\"Hello from the server!\".to_string())\n}\n\n````\n\n````inject-dioxus\nSandBoxFrame {\n\turl: \"https://codesandbox.io/p/sandbox/dioxus-fullstack-router-s75v5q?file=%2Fsrc%2Fmain.rs%3A7%2C1\"\n}\n````" + } + 19usize => { + "# Spawning Futures\n\nThe `use_resource` 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 `spawn`:\n\n````rust@spawn.rs\nlet mut response = use_signal(|| String::from(\"...\"));\n\nlet log_in = move |_| {\n spawn(async move {\n let resp = reqwest::Client::new()\n .get(\"https://dioxuslabs.com\")\n .send()\n .await;\n\n match resp {\n Ok(_data) => {\n log::info!(\"dioxuslabs.com responded!\");\n response.set(\"dioxuslabs.com responded!\".into());\n }\n Err(err) => {\n log::info!(\"Request failed with error: {err:?}\")\n }\n }\n });\n};\n\nrsx! {\n button { onclick: log_in, \"Response: {response}\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n spawn::App {}\n}\n````\n\n > \n > Note: `spawn` will always spawn a *new* future. You most likely don't want to call it on every render.\n\nCalling `spawn` will give you a `JoinHandle` which lets you cancel or pause the future.\n\n## Spawning Tokio Tasks\n\nSometimes, 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:\n\n````rust@spawn.rs\nspawn(async {\n let _ = tokio::spawn(async {}).await;\n\n let _ = tokio::task::spawn_local(async {\n // some !Send work\n })\n .await;\n});\n````" + } + 40usize => { + "# Full Code\n\n````rust@full_example.rs\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n// ANCHOR: router\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(NavBar)]\n #[route(\"/\")]\n Home {},\n #[nest(\"/blog\")]\n #[layout(Blog)]\n #[route(\"/\")]\n BlogList {},\n #[route(\"/post/:name\")]\n BlogPost { name: String },\n #[end_layout]\n #[end_nest]\n #[end_layout]\n #[nest(\"/myblog\")]\n #[redirect(\"/\", || Route::BlogList {})]\n #[redirect(\"/:name\", |name: String| Route::BlogPost { name })]\n #[end_nest]\n #[route(\"/:..route\")]\n PageNotFound {\n route: Vec,\n },\n}\n// ANCHOR_END: router\n\npub fn App() -> Element {\n rsx! { Router:: {} }\n}\n\n#[component]\nfn NavBar() -> Element {\n rsx! {\n nav {\n ul {\n li {\n Link { to: Route::Home {}, \"Home\" }\n }\n li {\n Link { to: Route::BlogList {}, \"Blog\" }\n }\n }\n }\n Outlet:: {}\n }\n}\n\n#[component]\nfn Home() -> Element {\n rsx! { h1 { \"Welcome to the Dioxus Blog!\" } }\n}\n\n#[component]\nfn Blog() -> Element {\n rsx! {\n h1 { \"Blog\" }\n Outlet:: {}\n }\n}\n\n#[component]\nfn BlogList() -> Element {\n rsx! {\n h2 { \"Choose a post\" }\n ul {\n li {\n Link {\n to: Route::BlogPost {\n name: \"Blog post 1\".into(),\n },\n \"Read the first blog post\"\n }\n }\n li {\n Link {\n to: Route::BlogPost {\n name: \"Blog post 2\".into(),\n },\n \"Read the second blog post\"\n }\n }\n }\n }\n}\n\n#[component]\nfn BlogPost(name: String) -> Element {\n rsx! { h2 { \"Blog Post: {name}\" } }\n}\n\n#[component]\nfn PageNotFound(route: Vec) -> Element {\n rsx! {\n h1 { \"Page not found\" }\n p { \"We are terribly sorry, but the page you requested doesn't exist.\" }\n pre { color: \"red\", \"log:\\nattemped to navigate to: {route:?}\" }\n }\n}\n\n````" + } + 68usize => { + "# Contributing\n\nDevelopment 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 (but first check if someone hasn't [done it already](https://github.com/DioxusLabs/dioxus/issues)).\n\n[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.\n\n## Improving Docs\n\nIf you'd like to improve the docs, PRs are welcome! The Rust docs ([source](https://github.com/DioxusLabs/dioxus/tree/main/packages)) and this guide ([source](https://github.com/DioxusLabs/docsite/tree/main/docs-src/0.5/en)) can be found in their respective GitHub repos.\n\n## Working on the Ecosystem\n\nPart 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 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. Once you are done, add your library to the [awesome dioxus](https://github.com/DioxusLabs/awesome-dioxus) list or share it in the `#I-made-a-thing` channel on [Discord](https://discord.gg/XgGxMSkvUM).\n\n## Bugs & Features\n\nIf you've fixed [an open issue](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!\n\nAll pull requests (including those made by a team member) must be approved by at least one other team member.\nLarger, more nuanced decisions about design, architecture, breaking changes, trade-offs, etc. are made by team consensus.\n\n## Before you contribute\n\nYou might be surprised that a lot of checks fail when making your first PR.\nThat's why you should first run these commands before contributing, and it will save you *lots* of time, because the\nGitHub CI is much slower at executing all of these than your PC.\n\n* Format code with [rustfmt](https://github.com/rust-lang/rustfmt):\n\n````sh\ncargo fmt -- src/**/**.rs\n````\n\n* You might need to install some packages on Linux (Ubuntu/deb) before the following commands will complete successfully (there is also a Nix flake in the repo root):\n\n````sh\nsudo apt install libgdk3.0-cil libatk1.0-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libsoup-3.0-dev libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev\n````\n\n* Check all code [cargo check](https://doc.rust-lang.org/cargo/commands/cargo-check.html):\n\n````sh\ncargo check --workspace --examples --tests\n````\n\n* Check if [Clippy](https://doc.rust-lang.org/clippy/) generates any warnings. Please fix these!\n\n````sh\ncargo clippy --workspace --examples --tests -- -D warnings\n````\n\n* Test all code with [cargo-test](https://doc.rust-lang.org/cargo/commands/cargo-test.html):\n\n````sh\ncargo test --all --tests\n````\n\n* More tests, this time with [cargo-make](https://sagiegurari.github.io/cargo-make/). Here are all steps, including installation:\n\n````sh\ncargo install --force cargo-make\ncargo make tests\n````\n\n* Test unsafe crates with [MIRI](https://github.com/rust-lang/miri). Currently, this is used for the two MIRI tests in `dioxus-core` and `dioxus-native-core`:\n\n````sh\ncargo miri test --package dioxus-core --test miri_stress\ncargo miri test --package dioxus-native-core --test miri_native\n````\n\n* Test with Playwright. This tests the UI itself, right in a browser. Here are all steps, including installation:\n **Disclaimer: This might inexplicably fail on your machine without it being your fault.** Make that PR anyway!\n\n````sh\ncd playwright-tests\nnpm ci\nnpm install -D @playwright/test\nnpx playwright install --with-deps\nnpx playwright test\n````\n\n## How to test dioxus with local crate\n\nIf you are developing a feature, you should test it in your local setup before raising a PR. This process makes sure you are aware of your code functionality before being reviewed by peers.\n\n* Fork the following github repo (DioxusLabs/dioxus):\n\n`https://github.com/DioxusLabs/dioxus`\n\n* Create a new or use an existing rust crate (ignore this step if you will use an existing rust crate):\n This is where we will be testing the features of the forked\n\n````sh\ncargo new --bin demo\n````\n\n* Add the dioxus dependency to your rust crate (new/existing) in Cargo.toml:\n\n````toml\ndioxus = { path = \"/dioxus/packages/dioxus\", features = [\"web\", \"router\"] }\n````\n\nThis above example is for dioxus-web, with dioxus-router. To know about the dependencies for different renderer visit [here](https://dioxuslabs.com/learn/0.5/getting_started).\n\n* Run and test your feature\n\n````sh\ndx serve\n````\n\nIf this is your first time with dioxus, please read [the guide](https://dioxuslabs.com/learn/0.5/guide) to get familiar with dioxus." + } + 76usize => { + "# Props Migration\n\nIn dioxus 0.4, props are passed into the component through the scope. In dioxus 0.5, props are passed into the component through the props struct directly.\n\n## Owned Props\n\nThe props were borrowed with the lifetime from the scope. The props are cloned every render, and passed into the component as an owned value.\n\nDioxus 0.4:\n\n````rust\n#[component]\nfn Comp(cx: Scope, name: String) -> Element {\n // You pass in an owned prop, but inside the component, it is borrowed (name is the type &String inside the function)\n let owned_name: String = name.clone();\n\n cx.render(rsx! {\n \"Hello {owned_name}\"\n })\n}\n````\n\nDioxus 0.5:\n\n````rust@migration_props.rs\n// In dioxus 0.5, props are always owned. You pass in owned props and you get owned props in the body of the component\n#[component]\nfn Comp(name: String) -> Element {\n // Name is owned here already (name is the type String inside the function)\n let owned_name: String = name;\n\n rsx! {\"Hello {owned_name}\"}\n}\n````\n\nBecause props are cloned every render, making props Copy is recommended. You can easily make a field Copy by accepting `ReadOnlySignal` instead of `T` in the props struct:\n\n````rust@migration_props.rs\n// In dioxus 0.5, props are always owned. You pass in owned props and you get owned props in the body of the component\n#[component]\nfn CopyPropsComp(name: ReadOnlySignal) -> Element {\n rsx! {\n button {\n // You can easily copy the value of a signal into a closure\n onclick: move |_| {\n println!(\"Hello {name}\");\n async move {\n println!(\"Hello {name}\");\n }\n },\n \"Click me\"\n }\n }\n}\n\nfn CopyPropsCompParent() -> Element {\n rsx! { CopyPropsComp { name: \"World\" } }\n}\n````\n\n## Borrowed Props\n\nBorrowed props are removed in dioxus 0.5. Mapped signals can act similarly to borrowed props if your props are borrowed from state.\n\nDioxus 0.4:\n\n````rust\nfn Parent(cx: Scope) -> Element {\n let state = use_state(cx, || (1, \"World\".to_string()));\n rsx! {\n BorrowedComp {\n name: &state.get().1\n }\n }\n}\n\n#[component]\nfn BorrowedComp<'a>(cx: Scope<'a>, name: &'a str) -> Element<'a> {\n rsx! {\n \"Hello {name}\"\n }\n}\n````\n\nDioxus 0.5:\n\n````rust@migration_props.rs\nfn Parent() -> Element {\n let state = use_signal(|| (1, \"World\".to_string()));\n\n rsx! { BorrowedComp { name: state.map(|s| &s.1) } }\n}\n\n#[component]\nfn BorrowedComp(name: MappedSignal) -> Element {\n rsx! {\"Hello {name}\"}\n}\n````\n\n## Manual Props\n\nManual prop structs in dioxus 0.5 need to derive `Clone` in addition to `Props` and `PartialEq`:\n\nDioxus 0.4:\n\n````rust\n#[derive(Props, PartialEq)]\nstruct ManualProps {\n name: String,\n}\n\n// Functions accept the props directly instead of the scope\nfn ManualPropsComponent(cx: Scope) -> Element {\n render! {\n \"Hello {cx.props.name}\"\n }\n}\n````\n\nDioxus 0.5:\n\n````rust@migration_props.rs\n#[derive(Props, Clone, PartialEq)]\nstruct ManualProps {\n name: String,\n}\n\n// Functions accept the props directly instead of the component\nfn ManualPropsComponent(props: ManualProps) -> Element {\n rsx! {\"Hello {props.name}\"}\n}\n````" + } + 42usize => { + "# Defining Routes\n\nWhen creating a \\[`Routable`\\] enum, we can define routes for our application using the `route(\"path\")` attribute.\n\n## Route Segments\n\nEach route is made up of segments. Most segments are separated by `/` characters in the path.\n\nThere are four fundamental types of segments:\n\n1. [Static segments](#static-segments) are fixed strings that must be present in the path.\n1. [Dynamic segments](#dynamic-segments) are types that can be parsed from a segment.\n1. [Catch-all segments](#catch-all-segments) are types that can be parsed from multiple segments.\n1. [Query segments](#query-segments) are types that can be parsed from the query string.\n\nRoutes are matched:\n\n* First, from most specific to least specific (Static then Dynamic then Catch All) (Query is always matched)\n* Then, if multiple routes match the same path, the order in which they are defined in the enum is followed.\n\n## Static segments\n\nFixed routes match a specific path. For example, the route `#[route(\"/about\")]` will match the path `/about`.\n\n````rust@static_segments.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // Routes always start with a slash\n #[route(\"/\")]\n Home {},\n // You can have multiple segments in a route\n #[route(\"/hello/world\")]\n HelloWorld {},\n}\n\n#[component]\nfn Home() -> Element {\n todo!()\n}\n\n#[component]\nfn HelloWorld() -> Element {\n todo!()\n}\n````\n\n## Dynamic Segments\n\nDynamic segments are in the form of `:name` where `name` is\nthe name of the field in the route variant. If the segment is parsed\nsuccessfully then the route matches, otherwise the matching continues.\n\nThe segment can be of any type that implements `FromStr`.\n\n````rust@dynamic_segments.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // segments that start with : are dynamic segments\n #[route(\"/post/:name\")]\n BlogPost {\n // You must include dynamic segments in child variants\n name: String,\n },\n #[route(\"/document/:id\")]\n Document {\n // You can use any type that implements FromStr\n // If the segment can't be parsed, the route will not match\n id: usize,\n },\n}\n\n// Components must contain the same dynamic segments as their corresponding variant\n#[component]\nfn BlogPost(name: String) -> Element {\n todo!()\n}\n\n#[component]\nfn Document(id: usize) -> Element {\n todo!()\n}\n````\n\n## Catch All Segments\n\nCatch All segments are in the form of `:..name` where `name` is the name of the field in the route variant. If the segments are parsed successfully then the route matches, otherwise the matching continues.\n\nThe segment can be of any type that implements `FromSegments`. (Vec implements this by default)\n\nCatch All segments must be the *last route segment* in the path (query segments are not counted) and cannot be included in nests.\n\n````rust@catch_all_segments.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // segments that start with :.. are catch all segments\n #[route(\"/blog/:..segments\")]\n BlogPost {\n // You must include catch all segment in child variants\n segments: Vec,\n },\n}\n\n// Components must contain the same catch all segments as their corresponding variant\n#[component]\nfn BlogPost(segments: Vec) -> Element {\n todo!()\n}\n````\n\n## Query Segments\n\nQuery segments are in the form of `?:name&:othername` where `name` and `othername` are the names of fields in the route variant.\n\nUnlike [Dynamic Segments](#dynamic-segments) and [Catch All Segments](#catch-all-segments), parsing a Query segment must not fail.\n\nThe segment can be of any type that implements `FromQueryArgument`.\n\nQuery segments must be the *after all route segments* and cannot be included in nests.\n\n````rust@query_segments.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // segments that start with ?: are query segments\n #[route(\"/blog?:name&:surname\")]\n BlogPost {\n // You must include query segments in child variants\n name: String,\n surname: String,\n },\n}\n\n#[component]\nfn BlogPost(name: String, surname: String) -> Element {\n rsx! {\n div { \"This is your blogpost with a query segment:\" }\n div { \"Name: {name}\" }\n div { \"Surname: {surname}\" }\n }\n}\n\nfn App() -> Element {\n rsx! { Router:: {} }\n}\n\nfn main() {}\n````" + } + 0usize => { + "# Introduction\n\nDioxus 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.\n\n````rust@readme.rs\nuse dioxus::prelude::*;\n\npub fn App() -> Element {\n let mut count = use_signal(|| 0);\n\n rsx! {\n h1 { \"High-Five counter: {count}\" }\n button { onclick: move |_| count += 1, \"Up high!\" }\n button { onclick: move |_| count -= 1, \"Down low!\" }\n }\n}\n\n````\n\n````inject-dioxus\nDemoFrame {\n readme::App {}\n}\n````\n\nDioxus is heavily inspired by React. If you know React, getting started with Dioxus will be a breeze.\n\n > \n > 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.\n\n## Features\n\n* Cross platform apps in three lines of code. (Web, Desktop, Server, Mobile, and more)\n* Incredibly ergonomic and powerful state management that combines the best parts of react, solid and svelte.\n* Comprehensive inline documentation – hover and guides for all HTML elements, listeners, and events.\n* High performance applications [approaching the fastest web frameworks on the web](https://dioxuslabs.com/blog/templates-diffing) and native speeds on desktop.\n* First-class async support.\n\n### Multiplatform\n\nDioxus 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.\n\nRight now, we have several 1st-party renderers:\n\n* WebSys/Sledgehammer (for WASM): Great support\n* Tao/Tokio (for Desktop apps): Good support\n* Tao/Tokio (for Mobile apps): Poor support\n* Fullstack (for SSR and server functions): Good support\n* TUI/Plasmo (for terminal-based apps): Experimental\n\n## Stability\n\nDioxus has not reached a stable release yet.\n\nWeb: Since the web is a fairly mature platform, we expect there to be very little API churn for web-based features.\n\nDesktop: APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart.\n\nFullstack: APIs will likely be in flux as we figure out the best API for server communication." + } + 45usize => { + "# Links & Navigation\n\nWhen we split our app into pages, we need to provide our users with a way to\nnavigate between them. On regular web pages, we'd use an anchor element for that,\nlike this:\n\n````html\nLink to an other page\n````\n\nHowever, we cannot do that when using the router for three reasons:\n\n1. Anchor tags make the browser load a new page from the server. This takes a\n lot of time, and it is much faster to let the router handle the navigation\n client-side.\n1. Navigation using anchor tags only works when the app is running inside a\n browser. This means we cannot use them inside apps using Dioxus Desktop.\n1. Anchor tags cannot check if the target page exists. This means we cannot\n prevent accidentally linking to non-existent pages.\n\nTo solve these problems, the router provides us with a \\[`Link`\\] component we can\nuse like this:\n\n````rust@links.rs\n#[component]\nfn NavBar() -> Element {\n rsx! {\n nav {\n ul {\n li {\n Link { to: Route::Home {}, \"Home\" }\n }\n }\n }\n Outlet:: {}\n }\n}\n````\n\nThe `target` in the example above is similar to the `href` of a regular anchor\nelement. However, it tells the router more about what kind of navigation it\nshould perform. It accepts something that can be converted into a\n\\[`NavigationTarget`\\]:\n\n* The example uses a Internal route. This is the most common type of navigation.\n It tells the router to navigate to a page within our app by passing a variant of a \\[`Routable`\\] enum. This type of navigation can never fail if the link component is used inside a router component.\n* \\[`External`\\] allows us to navigate to URLs outside of our app. This is useful\n for links to external websites. NavigationTarget::External accepts an URL to navigate to. This type of navigation can fail if the URL is invalid.\n\n > \n > The \\[`Link`\\] accepts several props that modify its behavior. See the API docs\n > for more details." + } + 72usize => { + "# How to Upgrade to Dioxus 0.5\n\nThis guide will outline the API changes between the `0.4` and `0.5` releases.\n\n`0.5` has includes significant changes to hooks, props, and global state.\n\n## Cheat Sheet\n\nHere is a quick cheat sheet for the changes:\n\n### Scope\n\nDioxus 0.4:\n\n````rust\nfn app(cx: Scope) -> Element {\n cx.use_hook(|| {\n /*...*/\n });\n cx.provide_context({\n /*...*/\n });\n cx.spawn(async move {\n /*...*/\n });\n cx.render(rsx! {\n /*...*/\n })\n}\n````\n\nDioxus 0.5:\n\n````rust@migration.rs\nuse dioxus::prelude::*;\n\n// In dioxus 0.5, the scope is no longer passed as an argument to the function\nfn app() -> Element {\n // Hooks, context, and spawn are now called directly\n use_hook(|| { /*...*/ });\n provide_context({ /*...*/ });\n spawn(async move { /*...*/ });\n rsx! {\n /*...*/\n }\n}\n````\n\n### Props\n\nDioxus 0.4:\n\n````rust\n#[component]\nfn Comp(cx: Scope, name: String) -> Element {\n // You pass in an owned prop, but inside the component, it is borrowed (name is the type &String inside the function)\n let owned_name: String = name.clone();\n\n cx.render(rsx! {\n \"Hello {owned_name}\"\n BorrowedComp {\n \"{name}\"\n }\n ManualPropsComponent {\n name: name\n }\n })\n}\n\n#[component]\nfn BorrowedComp<'a>(cx: Scope<'a>, name: &'a str) -> Element<'a> {\n cx.render(rsx! {\n \"Hello {name}\"\n })\n}\n\n#[derive(Props, PartialEq)]\nstruct ManualProps {\n name: String\n}\n\nfn ManualPropsComponent(cx: Scope) -> Element {\n cx.render(rsx! {\n \"Hello {cx.props.name}\"\n })\n}\n````\n\nDioxus 0.5:\n\n````rust@migration.rs\nuse dioxus::prelude::*;\n\n// In dioxus 0.5, props are always owned. You pass in owned props and you get owned props in the body of the component\n#[component]\nfn Comp(name: String) -> Element {\n // Name is owned here already (name is the type String inside the function)\n let owned_name: String = name;\n\n rsx! {\n \"Hello {owned_name}\"\n BorrowedComp {\n name: \"other name\"\n }\n ManualPropsComponent {\n name: \"other name 2\"\n }\n }\n}\n\n// Borrowed props are removed in dioxus 0.5. Mapped signals can act similarly to borrowed props if your props are borrowed from state\n// ReadOnlySignal is a copy wrapper over a state that will be automatically converted to\n#[component]\nfn BorrowedComp(name: ReadOnlySignal) -> Element {\n rsx! {\n \"Hello {name}\"\n }\n}\n\n// In dioxus 0.5, props need to implement Props, Clone, and PartialEq\n#[derive(Props, Clone, PartialEq)]\nstruct ManualProps {\n name: String,\n}\n\n// Functions accept the props directly instead of the scope\nfn ManualPropsComponent(props: ManualProps) -> Element {\n rsx! {\n \"Hello {props.name}\"\n }\n}\n````\n\nYou can read more about the new props API in the [Props Migration](props.md) guide.\n\n### Futures\n\nDioxus 0.4:\n\n````rust\nuse_future((dependency1, dependency2,), move |(dependency1, dependency2,)| async move {\n\t/*use dependency1 and dependency2*/\n});\n````\n\nDioxus 0.5:\n\n````rust@migration.rs\n// dependency1 and dependency2 must be Signal-like types like Signal, ReadOnlySignal, GlobalSignal, or another Resource\nuse_resource(|| async move { /*use dependency1 and dependency2*/ });\n\nlet non_reactive_state = 0;\n// You can also add non-reactive state to the resource hook with the use_reactive macro\nuse_resource(use_reactive!(|(non_reactive_state,)| async move {\n tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n non_reactive_state + 1\n}));\n````\n\nRead more about the `use_resource` hook in the [Hook Migration](hooks.md) guide.\n\n### State Hooks\n\nDioxus 0.4:\n\n````rust\nlet copy_state = use_state(cx, || 0);\nlet clone_local_state = use_ref(cx, || String::from(\"Hello\"));\nuse_shared_state_provider(cx, || String::from(\"Hello\"));\nlet clone_shared_state = use_shared_state::(cx);\n\nlet copy_state_value = **copy_state;\nlet clone_local_state_value = clone_local_state.read();\nlet clone_shared_state_value = clone_shared_state.read();\n\ncx.render(rsx!{\n\t\"{copy_state_value}\"\n\t\"{clone_shared_state_value}\"\n\t\"{clone_local_state_value}\"\n\tbutton {\n\t\tonclick: move |_| {\n\t\t\tcopy_state.set(1);\n\t\t\t*clone_local_state.write() = \"World\".to_string();\n\t\t\t*clone_shared_state.write() = \"World\".to_string();\n\t\t},\n\t\t\"Set State\"\n\t}\n})\n````\n\nDioxus 0.5:\n\n````rust@migration.rs\n// You can now use signals for local copy state, local clone state, and shared state with the same API\nlet mut copy_state = use_signal(|| 0);\nlet mut clone_shared_state = use_context_provider(|| Signal::new(String::from(\"Hello\")));\nlet mut clone_local_state = use_signal(|| String::from(\"Hello\"));\n\n// Call the signal like a function to clone the current value\nlet copy_state_value = copy_state();\n// Or use the read method to borrow the current value\nlet clone_local_state_value = clone_local_state.read();\nlet clone_shared_state_value = clone_shared_state.read();\n\nrsx! {\n \"{copy_state_value}\"\n \"{clone_shared_state_value}\"\n \"{clone_local_state_value}\"\n button {\n onclick: move |_| {\n // All three states have the same API for updating the state\n copy_state.set(1);\n clone_shared_state.set(\"World\".to_string());\n clone_local_state.set(\"World\".to_string());\n },\n \"Set State\"\n }\n}\n````\n\nRead more about the `use_signal` hook in the [State Migration](state.md) guide.\n\n### Fermi\n\nDioxus 0.4:\n\n````rust\nuse dioxus::prelude::*;\nuse fermi::*;\n\nstatic NAME: Atom = Atom(|_| \"world\".to_string());\n\nfn app(cx: Scope) -> Element {\n use_init_atom_root(cx);\n let name = use_read(cx, &NAME);\n\n cx.render(rsx! {\n div { \"hello {name}!\" }\n Child {}\n ChildWithRef {}\n })\n}\n\nfn Child(cx: Scope) -> Element {\n let set_name = use_set(cx, &NAME);\n\n cx.render(rsx! {\n button {\n onclick: move |_| set_name(\"dioxus\".to_string()),\n \"reset name\"\n }\n })\n}\n\nstatic NAMES: AtomRef> = AtomRef(|_| vec![\"world\".to_string()]);\n\nfn ChildWithRef(cx: Scope) -> Element {\n let names = use_atom_ref(cx, &NAMES);\n\n cx.render(rsx! {\n div {\n ul {\n names.read().iter().map(|f| rsx!{\n li { \"hello: {f}\" }\n })\n }\n button {\n onclick: move |_| {\n let names = names.clone();\n cx.spawn(async move {\n names.write().push(\"asd\".to_string());\n })\n },\n \"Add name\"\n }\n }\n })\n}\n````\n\nDioxus 0.5:\n\n````rust@migration.rs\nuse dioxus::prelude::*;\n\n// Atoms and AtomRefs have been replaced with GlobalSignals\nstatic NAME: GlobalSignal = Signal::global(|| \"world\".to_string());\n\nfn app() -> Element {\n rsx! {\n // You can use global state directly without the use_read or use_set hooks\n div { \"hello {NAME}!\" }\n Child {}\n ChildWithRef {}\n }\n}\n\nfn Child() -> Element {\n rsx! {\n button {\n onclick: move |_| *NAME.write() = \"dioxus\".to_string(),\n \"reset name\"\n }\n }\n}\n\n// Atoms and AtomRefs have been replaced with GlobalSignals\nstatic NAMES: GlobalSignal> = Signal::global(|| vec![\"world\".to_string()]);\n\nfn ChildWithRef() -> Element {\n rsx! {\n div {\n ul {\n for name in NAMES.read().iter() {\n li { \"hello: {name}\" }\n }\n }\n button {\n onclick: move |_| {\n // No need to clone the signal into futures, you can use it directly\n async move {\n NAMES.write().push(\"asd\".to_string());\n }\n },\n \"Add name\"\n }\n }\n }\n}\n````\n\nYou can read more about global signals in the [Fermi migration guide](fermi.md)." + } + 9usize => { + "# Components\n\nJust 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, you should break down the functionality of an app in logical parts called components.\n\nA component is a Rust function, named in UpperCamelCase, that either takes no parameters or a properties struct and returns an `Element` describing the UI it wants to render.\n\n````rust, no_run@hello_world_desktop.rs\n// define a component that renders a div with the text \"Hello, world!\"\nfn App() -> Element {\n rsx! {\n div { \"Hello, world!\" }\n }\n}\n````\n\n > \n > You'll probably want to add `#![allow(non_snake_case)]` to the top of your crate to avoid warnings about UpperCamelCase component names\n\nA 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:\n\n````rust, no_run@components.rs\npub fn About() -> Element {\n rsx! {\n p {\n b { \"Dioxus Labs\" }\n \" An Open Source project dedicated to making Rust UI wonderful.\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\tcomponents::About {}\n}\n````\n\nThen, you can render your component in another component, similarly to how elements are rendered:\n\n````rust, no_run@components.rs\npub fn App() -> Element {\n rsx! {\n About {}\n About {}\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\tcomponents::App {}\n}\n````\n\n > \n > 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!" + } + 50usize => { + "# Cookbook\n\nThe cookbook contains common recipes for different patterns within Dioxus.\n\nThere are a few different sections in the cookbook:\n\n* [Publishing](publishing.md) will teach you how to present your app in a variety of delicious forms.\n* Explore the [Anti-patterns](antipatterns.md) section to discover what ingredients to avoid when preparing your application.\n* Within [Error Handling](error_handling.md), we'll master the fine art of managing spoiled ingredients in Dioxus.\n* Take a culinary journey through [State management](state/index.md), where we'll explore the world of handling local, global, and external state in Dioxus.\n* [Integrations](integrations/index.md) will guide you how to seamlessly blend external libraries into your Dioxus culinary creations.\n* [Testing](testing.md) explains how to examine the unique flavor of Dioxus-specific features, like components.\n* [Tailwind](tailwind.md) reveals the secrets of combining your Tailwind and Dioxus ingredients into a complete meal. You will also learn about using other NPM ingredients (packages) with Dioxus.\n* In the [Custom Renderer](custom_renderer.md) section, we embark on a cooking adventure, inventing new ways to cook with Dioxus!\n* [Optimizing](optimizing.md) will show you how to maximize the quality of your ingredients." + } + 60usize => { + "# Testing\n\nWhen building application or libraries with Dioxus, you may want to include some tests to check the behavior of parts of your application. This guide will teach you how to test different parts of your Dioxus application.\n\n## Component Testing\n\nYou can use a combination of [pretty-assertions](https://docs.rs/pretty_assertions/latest/pretty_assertions/) and [dioxus-ssr](https://docs.rs/dioxus-ssr/latest/dioxus_ssr/) to check that two snippets of rsx are equal:\n\n````rust@component_test.rs\nuse futures::FutureExt;\nuse std::{cell::RefCell, sync::Arc};\n\nuse dioxus::prelude::*;\n\n#[test]\nfn test() {\n assert_rsx_eq(\n rsx! {\n div { \"Hello world\" }\n div { \"Hello world\" }\n },\n rsx! {\n for _ in 0..2 {\n div { \"Hello world\" }\n }\n },\n )\n}\n\nfn assert_rsx_eq(first: Element, second: Element) {\n let first = dioxus_ssr::render_element(first);\n let second = dioxus_ssr::render_element(second);\n pretty_assertions::assert_str_eq!(first, second);\n}\n\n````\n\n## Hook Testing\n\nWhen creating libraries around Dioxus, it can be helpful to make tests for your [custom hooks](./state/custom_hooks/index.md).\n\nDioxus does not currently have a full hook testing library, but you can build a bespoke testing framework by manually driving the virtual dom.\n\n````rust@hook_test.rs\nuse futures::FutureExt;\nuse std::{cell::RefCell, rc::Rc, sync::Arc, thread::Scope};\n\nuse dioxus::{dioxus_core::NoOpMutations, prelude::*};\n\n#[test]\nfn test() {\n test_hook(\n || use_signal(|| 0),\n |mut value, mut proxy| match proxy.generation {\n 0 => {\n value.set(1);\n }\n 1 => {\n assert_eq!(*value.read(), 1);\n value.set(2);\n }\n 2 => {\n proxy.rerun();\n }\n 3 => {}\n _ => todo!(),\n },\n |proxy| assert_eq!(proxy.generation, 4),\n );\n}\n\nfn test_hook(\n initialize: impl FnMut() -> V + 'static,\n check: impl FnMut(V, MockProxy) + 'static,\n mut final_check: impl FnMut(MockProxy) + 'static,\n) {\n #[derive(Props)]\n struct MockAppComponent {\n hook: Rc>,\n check: Rc>,\n }\n\n impl PartialEq for MockAppComponent {\n fn eq(&self, _: &Self) -> bool {\n true\n }\n }\n\n impl Clone for MockAppComponent {\n fn clone(&self) -> Self {\n Self {\n hook: self.hook.clone(),\n check: self.check.clone(),\n }\n }\n }\n\n fn mock_app V, C: FnMut(V, MockProxy), V>(\n props: MockAppComponent,\n ) -> Element {\n let value = props.hook.borrow_mut()();\n\n props.check.borrow_mut()(value, MockProxy::new());\n\n rsx! { div {} }\n }\n\n let mut vdom = VirtualDom::new_with_props(\n mock_app,\n MockAppComponent {\n hook: Rc::new(RefCell::new(initialize)),\n check: Rc::new(RefCell::new(check)),\n },\n );\n\n vdom.rebuild_in_place();\n\n while vdom.wait_for_work().now_or_never().is_some() {\n vdom.render_immediate(&mut NoOpMutations);\n }\n\n vdom.in_runtime(|| {\n ScopeId::ROOT.in_runtime(|| {\n final_check(MockProxy::new());\n })\n })\n}\n\nstruct MockProxy {\n rerender: Arc,\n pub generation: usize,\n}\n\nimpl MockProxy {\n fn new() -> Self {\n let generation = generation();\n let rerender = schedule_update();\n\n Self {\n rerender,\n generation,\n }\n }\n\n pub fn rerun(&mut self) {\n (self.rerender)();\n }\n}\n\n````\n\n## End to End Testing\n\nYou can use [Playwright](https://playwright.dev/) to create end to end tests for your dioxus application.\n\nIn your `playwright.config.js`, you will need to run cargo run or dx serve instead of the default build command. Here is a snippet from the end to end web example:\n\n````js\n//...\nwebServer: [\n {\n cwd: path.join(process.cwd(), 'playwright-tests', 'web'),\n command: 'dx serve',\n port: 8080,\n timeout: 10 * 60 * 1000,\n reuseExistingServer: !process.env.CI,\n stdout: \"pipe\",\n },\n],\n````\n\n* [Web example](https://github.com/DioxusLabs/dioxus/tree/v0.5/playwright-tests/web)\n* [Liveview example](https://github.com/DioxusLabs/dioxus/tree/v0.5/playwright-tests/liveview)\n* [Fullstack example](https://github.com/DioxusLabs/dioxus/tree/v0.5/playwright-tests/fullstack)" + } + 6usize => { + "# Conclusion\n\nWell done! You've completed the Dioxus guide and built a hackernews application in Dioxus.\n\nTo continue your journey, you can attempt a challenge listed below, or look at the [Dioxus reference](../reference/index.md).\n\n## Challenges\n\n* Organize your components into separate files for better maintainability.\n* Give your app some style if you haven't already.\n* Integrate your application with the [Dioxus router](../router/index.md).\n\n## The full code for the hacker news project\n\n````rust@hackernews_complete.rs\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\n\nfn main() {\n launch(App);\n}\n\npub fn App() -> Element {\n use_context_provider(|| Signal::new(PreviewState::Unset));\n\n rsx! {\n div { display: \"flex\", flex_direction: \"row\", width: \"100%\",\n div { width: \"50%\", Stories {} }\n div { width: \"50%\", Preview {} }\n }\n }\n}\n\nfn Stories() -> Element {\n let stories = use_resource(move || get_stories(10));\n\n match &*stories.read_unchecked() {\n Some(Ok(list)) => rsx! {\n div {\n for story in list {\n StoryListing { story: story.clone() }\n }\n }\n },\n Some(Err(err)) => rsx! {\"An error occurred while fetching stories {err}\"},\n None => rsx! {\"Loading items\"},\n }\n}\n\nasync fn resolve_story(\n mut full_story: Signal>,\n mut preview_state: Signal,\n story_id: i64,\n) {\n if let Some(cached) = full_story.as_ref() {\n *preview_state.write() = PreviewState::Loaded(cached.clone());\n return;\n }\n\n *preview_state.write() = PreviewState::Loading;\n if let Ok(story) = get_story(story_id).await {\n *preview_state.write() = PreviewState::Loaded(story.clone());\n *full_story.write() = Some(story);\n }\n}\n\n#[component]\nfn StoryListing(story: ReadOnlySignal) -> Element {\n let preview_state = consume_context::>();\n let StoryItem {\n title,\n url,\n by,\n score,\n time,\n kids,\n id,\n ..\n } = story();\n let full_story = use_signal(|| None);\n\n let url = url.as_deref().unwrap_or_default();\n let hostname = url\n .trim_start_matches(\"https://\")\n .trim_start_matches(\"http://\")\n .trim_start_matches(\"www.\");\n let score = format!(\"{score} {}\", if score == 1 { \" point\" } else { \" points\" });\n let comments = format!(\n \"{} {}\",\n kids.len(),\n if kids.len() == 1 {\n \" comment\"\n } else {\n \" comments\"\n }\n );\n let time = time.format(\"%D %l:%M %p\");\n\n rsx! {\n div {\n padding: \"0.5rem\",\n position: \"relative\",\n onmouseenter: move |_event| { resolve_story(full_story, preview_state, id) },\n div { font_size: \"1.5rem\",\n a {\n href: url,\n onfocus: move |_event| { resolve_story(full_story, preview_state, id) },\n \"{title}\"\n }\n a {\n color: \"gray\",\n href: \"https://news.ycombinator.com/from?site={hostname}\",\n text_decoration: \"none\",\n \" ({hostname})\"\n }\n }\n div { display: \"flex\", flex_direction: \"row\", color: \"gray\",\n div { \"{score}\" }\n div { padding_left: \"0.5rem\", \"by {by}\" }\n div { padding_left: \"0.5rem\", \"{time}\" }\n div { padding_left: \"0.5rem\", \"{comments}\" }\n }\n }\n }\n}\n\n#[derive(Clone, Debug)]\nenum PreviewState {\n Unset,\n Loading,\n Loaded(StoryPageData),\n}\n\nfn Preview() -> Element {\n let preview_state = consume_context::>();\n\n match preview_state() {\n PreviewState::Unset => rsx! {\"Hover over a story to preview it here\"},\n PreviewState::Loading => rsx! {\"Loading...\"},\n PreviewState::Loaded(story) => {\n rsx! {\n div { padding: \"0.5rem\",\n div { font_size: \"1.5rem\", a { href: story.item.url, \"{story.item.title}\" } }\n div { dangerous_inner_html: story.item.text }\n for comment in &story.comments {\n Comment { comment: comment.clone() }\n }\n }\n }\n }\n }\n}\n\n#[component]\nfn Comment(comment: CommentData) -> Element {\n rsx! {\n div { padding: \"0.5rem\",\n div { color: \"gray\", \"by {comment.by}\" }\n div { dangerous_inner_html: \"{comment.text}\" }\n for kid in &comment.sub_comments {\n Comment { comment: kid.clone() }\n }\n }\n }\n}\n\n// Define the Hackernews API and types\nuse chrono::{DateTime, Utc};\nuse futures::future::join_all;\nuse serde::{Deserialize, Serialize};\n\npub static BASE_API_URL: &str = \"https://hacker-news.firebaseio.com/v0/\";\npub static ITEM_API: &str = \"item/\";\npub static USER_API: &str = \"user/\";\nconst COMMENT_DEPTH: i64 = 2;\n\npub async fn get_story_preview(id: i64) -> Result {\n let url = format!(\"{}{}{}.json\", BASE_API_URL, ITEM_API, id);\n reqwest::get(&url).await?.json().await\n}\n\npub async fn get_stories(count: usize) -> Result, reqwest::Error> {\n let url = format!(\"{}topstories.json\", BASE_API_URL);\n let stories_ids = &reqwest::get(&url).await?.json::>().await?[..count];\n\n let story_futures = stories_ids[..usize::min(stories_ids.len(), count)]\n .iter()\n .map(|&story_id| get_story_preview(story_id));\n Ok(join_all(story_futures)\n .await\n .into_iter()\n .filter_map(|story| story.ok())\n .collect())\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryPageData {\n #[serde(flatten)]\n pub item: StoryItem,\n #[serde(default)]\n pub comments: Vec,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct CommentData {\n pub id: i64,\n /// there will be no by field if the comment was deleted\n #[serde(default)]\n pub by: String,\n #[serde(default)]\n pub text: String,\n #[serde(with = \"chrono::serde::ts_seconds\")]\n pub time: DateTime,\n #[serde(default)]\n pub kids: Vec,\n #[serde(default)]\n pub sub_comments: Vec,\n pub r#type: String,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryItem {\n pub id: i64,\n pub title: String,\n pub url: Option,\n pub text: Option,\n #[serde(default)]\n pub by: String,\n #[serde(default)]\n pub score: i64,\n #[serde(default)]\n pub descendants: i64,\n #[serde(with = \"chrono::serde::ts_seconds\")]\n pub time: DateTime,\n #[serde(default)]\n pub kids: Vec,\n pub r#type: String,\n}\n\npub async fn get_story(id: i64) -> Result {\n let url = format!(\"{}{}{}.json\", BASE_API_URL, ITEM_API, id);\n let mut story = reqwest::get(&url).await?.json::().await?;\n let comment_futures = story.item.kids.iter().map(|&id| get_comment(id));\n let comments = join_all(comment_futures)\n .await\n .into_iter()\n .filter_map(|c| c.ok())\n .collect();\n\n story.comments = comments;\n Ok(story)\n}\n\n#[async_recursion::async_recursion(?Send)]\npub async fn get_comment_with_depth(id: i64, depth: i64) -> Result {\n let url = format!(\"{}{}{}.json\", BASE_API_URL, ITEM_API, id);\n let mut comment = reqwest::get(&url).await?.json::().await?;\n if depth > 0 {\n let sub_comments_futures = comment\n .kids\n .iter()\n .map(|story_id| get_comment_with_depth(*story_id, depth - 1));\n comment.sub_comments = join_all(sub_comments_futures)\n .await\n .into_iter()\n .filter_map(|c| c.ok())\n .collect();\n }\n Ok(comment)\n}\n\npub async fn get_comment(comment_id: i64) -> Result {\n get_comment_with_depth(comment_id, COMMENT_DEPTH).await\n}\n\n````" + } + 67usize => { + "# Translating existing HTML\n\nDioxus uses a custom format called RSX to represent the HTML because it is more concise and looks more like Rust code. However, it can be a pain to convert existing HTML to RSX. That's why Dioxus comes with a tool called `dx translate` that can automatically convert HTML to RSX!\n\nDx translate can make converting large chunks of HTML to RSX much easier! Lets try translating some of the HTML from the Dioxus homepage:\n\n````sh\ndx translate --raw \"
Fullstack, crossplatform,lightning fast, fully typed.

Dioxus is a Rust library for building apps that run on desktop, web, mobile, and more.

Trusted by top companies

\"\n````\n\nWe get the following RSX you can easily copy and paste into your code:\n\n````rs\ndiv { class: \"relative w-full mx-4 sm:mx-auto text-gray-600\",\n div { class: \"text-[3em] md:text-[5em] font-semibold dark:text-white text-ghdarkmetal font-sans py-12 flex flex-col\",\n span { \"Fullstack, crossplatform,\" }\n span { \"lightning fast, fully typed.\" }\n }\n h3 { class: \"text-[2em] dark:text-white font-extralight text-ghdarkmetal pt-4 max-w-screen-md mx-auto\",\n \"Dioxus is a Rust library for building apps that run on desktop, web, mobile, and more.\"\n }\n div { class: \"pt-12 text-white text-[1.2em] font-sans font-bold flex flex-row justify-center space-x-4\",\n a {\n href: \"/learn/0.5/getting_started\",\n data_dioxus_id: \"216\",\n dioxus_prevent_default: \"onclick\",\n class: \"bg-red-600 py-2 px-8 hover:-translate-y-2 transition-transform duration-300\",\n \"Quickstart\"\n }\n a {\n dioxus_prevent_default: \"onclick\",\n href: \"/learn/0.5/reference\",\n data_dioxus_id: \"214\",\n class: \"bg-blue-500 py-2 px-8 hover:-translate-y-2 transition-transform duration-300\",\n \"Read the docs\"\n }\n }\n div { class: \"max-w-screen-2xl mx-auto pt-36\",\n h1 { class: \"text-md\", \"Trusted by top companies\" }\n div { class: \"pt-4 flex flex-row flex-wrap justify-center\",\n div { class: \"h-12 w-40 p-2 m-4 flex justify-center items-center\",\n img { src: \"static/futurewei_bw.png\" }\n }\n div { class: \"h-12 w-40 p-2 m-4 flex justify-center items-center\",\n img { src: \"static/airbuslogo.svg\" }\n }\n div { class: \"h-12 w-40 p-2 m-4 flex justify-center items-center\",\n img { src: \"static/ESA_logo.svg\" }\n }\n div { class: \"h-12 w-40 p-2 m-4 flex justify-center items-center\",\n img { src: \"static/yclogo.svg\" }\n }\n div { class: \"h-12 w-40 p-2 m-4 flex justify-center items-center\",\n img { src: \"static/satellite.webp\" }\n }\n }\n }\n}\n````\n\n## Usage\n\nThe `dx translate` command has several flags you can use to control your html input and rsx output.\n\nYou can use the `--file` flag to translate an HTML file to RSX:\n\n````sh\ndx translate --file index.html\n````\n\nOr you can use the `--raw` flag to translate a string of HTML to RSX:\n\n````sh\ndx translate --raw \"
Hello world
\"\n````\n\nBoth of those commands will output the following RSX:\n\n````rs\ndiv { \"Hello world\" }\n````\n\nThe `dx translate` command will output the RSX to stdout. You can use the `--output` flag to write the RSX to a file instead.\n\n````sh\ndx translate --raw \"
Hello world
\" --output index.rs\n````\n\nYou can automatically create a component with the `--component` flag.\n\n````sh\ndx translate --raw \"
Hello world
\" --component\n````\n\nThis will output the following component:\n\n````rs\nfn component() -> Element {\n rsx! {\n div { \"Hello world\" }\n }\n}\n````\n\nTo learn more about the different flags `dx translate` supports, run `dx translate --help`." + } + 18usize => { + "# Coroutines\n\nAnother tool in your async toolbox are coroutines. Coroutines are futures that can have values sent to them.\n\nLike regular futures, code in a coroutine will run until the next `await` point before yielding. This low-level control over asynchronous tasks is quite powerful, allowing for infinitely looping tasks like WebSocket polling, background timers, and other periodic actions.\n\n## use_coroutine\n\nThe `use_coroutine` hook allows you to create a coroutine. Most coroutines we write will be polling loops using await.\n\n````rust, no_run@use_coroutine_reference.rs\nuse futures_util::StreamExt;\n\nfn app() {\n let ws: Coroutine<()> = use_coroutine(|rx| async move {\n // Connect to some sort of service\n let mut conn = connect_to_ws_server().await;\n\n // Wait for data on the service\n while let Some(msg) = conn.next().await {\n // handle messages\n }\n });\n}\n````\n\nFor many services, a simple async loop will handle the majority of use cases.\n\n## Yielding Values\n\nTo yield values from a coroutine, simply bring in a `Signal` handle and set the value whenever your coroutine completes its work.\n\nThe future must be `'static` – so any values captured by the task cannot carry any references to `cx`, such as a `Signal`.\n\nYou can use [to_owned](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned) to create a clone of the hook handle which can be moved into the async closure.\n\n````rust, no_run@use_coroutine_reference.rs\nlet sync_status = use_signal(|| Status::Launching);\nlet sync_task = use_coroutine(|rx: UnboundedReceiver| {\n let mut sync_status = sync_status.to_owned();\n async move {\n loop {\n tokio::time::sleep(Duration::from_secs(1)).await;\n sync_status.set(Status::Working);\n }\n }\n});\n````\n\nTo 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.\n\n````rust, no_run@use_coroutine_reference.rs\nlet sync_status = use_signal(|| Status::Launching);\nlet load_status = use_signal(|| Status::Launching);\nlet sync_task = use_coroutine(|rx: UnboundedReceiver| {\n async move {\n // ...\n }\n});\n````\n\n## Sending Values\n\nYou might've noticed the `use_coroutine` closure takes an argument called `rx`. What is that? Well, a common pattern in complex apps is to handle a bunch of async code at once. With libraries like Redux Toolkit, managing multiple promises at once can be challenging and a common source of bugs.\n\nWith Coroutines, we can centralize our async logic. The `rx` parameter is an Channel that allows code external to the coroutine to send data *into* the coroutine. Instead of looping on an external service, we can loop on the channel itself, processing messages from within our app without needing to spawn a new future. To send data into the coroutine, we would call \"send\" on the handle.\n\n````rust, no_run@use_coroutine_reference.rs\nuse futures_util::StreamExt;\n\nenum ProfileUpdate {\n SetUsername(String),\n SetAge(i32),\n}\n\nlet profile = use_coroutine(|mut rx: UnboundedReceiver| async move {\n let mut server = connect_to_server().await;\n\n while let Some(msg) = rx.next().await {\n match msg {\n ProfileUpdate::SetUsername(name) => server.update_username(name).await,\n ProfileUpdate::SetAge(age) => server.update_age(age).await,\n }\n }\n});\n\nrsx! {\n button { onclick: move |_| profile.send(ProfileUpdate::SetUsername(\"Bob\".to_string())),\n \"Update username\"\n }\n}\n````\n\n > \n > Note: In order to use/run the `rx.next().await` statement you will need to extend the \\[`Stream`\\] trait (used by \\[`UnboundedReceiver`\\]) by adding 'futures_util' as a dependency to your project and adding the `use futures_util::stream::StreamExt;`.\n\nFor sufficiently complex apps, we could build a bunch of different useful \"services\" that loop on channels to update the app.\n\n````rust, no_run@use_coroutine_reference.rs\nlet profile = use_coroutine(profile_service);\nlet editor = use_coroutine(editor_service);\nlet sync = use_coroutine(sync_service);\n\nasync fn profile_service(rx: UnboundedReceiver) {\n // do stuff\n}\n\nasync fn sync_service(rx: UnboundedReceiver) {\n // do stuff\n}\n\nasync fn editor_service(rx: UnboundedReceiver) {\n // do stuff\n}\n````\n\nWe can combine coroutines with Global State 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 `Signal::global` or Redux – the only Atoms that need to exist are those that are used to drive the display/UI.\n\n````rust, no_run@use_coroutine_reference.rs\nstatic USERNAME: GlobalSignal = Signal::global(|| \"default\".to_string());\n\nfn app() -> Element {\n use_coroutine(sync_service);\n\n rsx! { Banner {} }\n}\n\nfn Banner() -> Element {\n rsx! { h1 { \"Welcome back, {USERNAME}\" } }\n}\n````\n\nNow, in our sync service, we can structure our state however we want. We only need to update the view values when ready.\n\n````rust, no_run@use_coroutine_reference.rs\nuse futures_util::StreamExt;\n\nstatic USERNAME: GlobalSignal = Signal::global(|| \"default\".to_string());\nstatic ERRORS: GlobalSignal> = Signal::global(|| Vec::new());\n\nenum SyncAction {\n SetUsername(String),\n}\n\nasync fn sync_service(mut rx: UnboundedReceiver) {\n while let Some(msg) = rx.next().await {\n match msg {\n SyncAction::SetUsername(name) => {\n if set_name_on_server(&name).await.is_ok() {\n *USERNAME.write() = name;\n } else {\n *ERRORS.write() = vec![\"Failed to set username\".to_string()];\n }\n }\n }\n }\n}\n````\n\n## Automatic injection into the Context API\n\nCoroutine handles are automatically injected through the context API. You can use the `use_coroutine_handle` hook with the message type as a generic to fetch a handle.\n\n````rust, no_run@use_coroutine_reference.rs\nfn Child() -> Element {\n let sync_task = use_coroutine_handle::();\n\n sync_task.send(SyncAction::SetUsername);\n\n todo!()\n}\n````" + } + 8usize => { + "# Describing the UI\n\nDioxus is a *declarative* framework. This means that instead of telling Dioxus what to do (e.g. to \"create an element\" or \"set the color to red\") we simply *declare* what we want the UI to look like using RSX.\n\nYou have already seen a simple example of RSX syntax in the \"hello world\" application:\n\n````rust, no_run@hello_world_desktop.rs\n// define a component that renders a div with the text \"Hello, world!\"\nfn App() -> Element {\n rsx! {\n div { \"Hello, world!\" }\n }\n}\n````\n\nHere, 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.\n\n## RSX Features\n\nRSX is very similar to HTML in that it describes elements with attributes and children. Here's an empty `button` element in RSX, as well as the resulting HTML:\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n button {\n // attributes / listeners\n // children\n \"Hello, World!\"\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Button {}\n}\n````\n\n### Attributes\n\nAttributes (and [event handlers](event_handlers.md)) modify the behavior or appearance of the element they are attached to. They are specified inside the `{}` brackets, using the `name: value` syntax. You can provide the value as a literal in the RSX:\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n img {\n src: \"https://avatars.githubusercontent.com/u/79236386?s=200&v=4\",\n class: \"primary_button\",\n width: \"10px\",\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Attributes {}\n}\n````\n\nSome attributes, such as the `type` attribute for `input` elements won't work on their own in Rust. This is because `type` is a reserved Rust keyword. To get around this, Dioxus uses the `r#` specifier:\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n input { r#type: \"text\", color: \"red\" }\n}\n````\n\n > \n > Note: All attributes defined in `dioxus-html` follow the snake_case naming convention. They transform their `snake_case` names to HTML's `camelCase` attributes.\n\n > \n > Note: Styles can be used directly outside of the `style:` attribute. In the above example, `color: \"red\"` is turned into `style=\"color: red\"`.\n\n#### Conditional Attributes\n\nYou can also conditionally include attributes by using an if statement without an else branch. This is useful for adding an attribute only if a certain condition is met:\n\n````rust, no_run@rsx_overview.rs\nlet large_font = true;\nrsx! {\n div { class: if large_font { \"text-xl\" }, \"Hello, World!\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::ConditionalAttributes {}\n}\n````\n\nRepeating an attribute joins the values with a space. This makes it easy to add values like classes conditionally:\n\n````rust, no_run@rsx_overview.rs\nlet large_font = true;\nrsx! {\n div {\n class: \"base-class another-class\",\n class: if large_font { \"text-xl\" },\n \"Hello, World!\"\n }\n}\n````\n\n#### Custom Attributes\n\nDioxus 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:\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n div { \"style\": \"width: 20px; height: 20px; background-color: red;\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::CustomAttributes {}\n}\n````\n\n### Special Attributes\n\nWhile most attributes are simply passed on to the HTML, some have special behaviors.\n\n#### The HTML Escape Hatch\n\nIf 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`.\n\nFor 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):\n\n````rust, no_run@dangerous_inner_html.rs\n// this should come from a trusted source\nlet contents = \"live dangerously\";\n\nrsx! {\n div { dangerous_inner_html: \"{contents}\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\tdangerous_inner_html::App {}\n}\n````\n\n > \n > 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.\n > \n > 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.\n\n#### Boolean Attributes\n\nMost 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.\n\nSo this RSX wouldn't actually render the `hidden` attribute:\n\n````rust, no_run@boolean_attribute.rs\nrsx! {\n div { hidden: false, \"hello\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\tboolean_attribute::App {}\n}\n````\n\nNot all attributes work like this however. *Only the following attributes* have this behavior:\n\n* `allowfullscreen`\n* `allowpaymentrequest`\n* `async`\n* `autofocus`\n* `autoplay`\n* `checked`\n* `controls`\n* `default`\n* `defer`\n* `disabled`\n* `formnovalidate`\n* `hidden`\n* `ismap`\n* `itemscope`\n* `loop`\n* `multiple`\n* `muted`\n* `nomodule`\n* `novalidate`\n* `open`\n* `playsinline`\n* `readonly`\n* `required`\n* `reversed`\n* `selected`\n* `truespeed`\n\nFor any other attributes, a value of `\"false\"` will be sent directly to the DOM.\n\n### Interpolation\n\nSimilarly 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:\n\n````rust, no_run@rsx_overview.rs\nlet coordinates = (42, 0);\nlet country = \"es\";\nrsx! {\n div {\n class: \"country-{country}\",\n left: \"{coordinates.0:?}\",\n top: \"{coordinates.1:?}\",\n // arbitrary expressions are allowed,\n // as long as they don't contain `{}`\n div { \"{country.to_uppercase()}\" }\n div { \"{7*6}\" }\n // {} can be escaped with {{}}\n div { \"{{}}\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Formatting {}\n}\n````\n\n### Children\n\nTo add children to an element, put them inside the `{}` brackets after all attributes and listeners in the element. They can be other elements, text, or [components](components.md). For example, you could have an `ol` (ordered list) element, containing 3 `li` (list item) elements, each of which contains some text:\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n ol {\n li { \"First Item\" }\n li { \"Second Item\" }\n li { \"Third Item\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Children {}\n}\n````\n\n### Fragments\n\nYou can render multiple elements at the top level of `rsx!` and they will be automatically grouped.\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n p { \"First Item\" }\n p { \"Second Item\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::ManyRoots {}\n}\n````\n\n### Expressions\n\nYou can include arbitrary Rust expressions as children within RSX by surrounding your expression with `{}`s. Any expression that implements [IntoDynNode](https://docs.rs/dioxus-core/0.3/dioxus_core/trait.IntoDynNode.html) can be used within rsx. This is useful for displaying data from an [iterator](https://doc.rust-lang.org/stable/book/ch13-02-iterators.html#processing-a-series-of-items-with-iterators):\n\n````rust, no_run@rsx_overview.rs\nlet text = \"Dioxus\";\nrsx! {\n span {\n {text.to_uppercase()}\n // create a list of text from 0 to 9\n {(0..10).map(|i| rsx! {\n \"{i}\"\n })}\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Expression {}\n}\n````\n\n### Loops\n\nIn addition to iterators you can also use for loops directly within RSX:\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n // use a for loop where the body itself is RSX\n div {\n // create a list of text from 0 to 9\n for i in 0..3 {\n // NOTE: the body of the loop is RSX not a rust statement\n div { \"{i}\" }\n }\n }\n // iterator equivalent\n div {\n {(0..3).map(|i| rsx! {\n div { \"{i}\" }\n })}\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Loops {}\n}\n````\n\n### If statements\n\nYou can also use if statements without an else branch within RSX:\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n // use if statements without an else\n if true {\n div { \"true\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::IfStatements {}\n}\n````" + } + 56usize => { + "# Internationalization\n\nIf your application supports multiple languages, the [Dioxus SDK](https://github.com/DioxusLabs/sdk) crate contains helpers to make working with translations in your application easier.\n\n## The full code for internationalization\n\n````rust@i18n.rs\nuse dioxus::prelude::*;\nuse dioxus_sdk::i18n::*;\nuse dioxus_sdk::translate;\nuse std::str::FromStr;\n\nfn main() {\n launch(app);\n}\n\nstatic EN_US: &str = r#\"{\n \"id\": \"en-US\",\n \"texts\": {\n \"messages\": {\n \"hello_world\": \"Hello World!\"\n },\n \"messages.hello\": \"Hello {name}\"\n }\n}\"#;\nstatic ES_ES: &str = r#\"{\n \"id\": \"es-ES\",\n \"texts\": {\n \"messages\": {\n \"hello_world\": \"Hola Mundo!\"\n },\n \"messages.hello\": \"Hola {name}\"\n }\n}\"#;\n\n#[allow(non_snake_case)]\nfn Body() -> Element {\n let mut i18 = use_i18();\n\n let change_to_english = move |_| i18.set_language(\"en-US\".parse().unwrap());\n let change_to_spanish = move |_| i18.set_language(\"es-ES\".parse().unwrap());\n\n rsx! {\n button { onclick: change_to_english, label { \"English\" } }\n button { onclick: change_to_spanish, label { \"Spanish\" } }\n p { {translate!(i18, \"messages.hello_world\")} }\n p { {translate!(i18, \"messages.hello\", name: \"Dioxus\")} }\n }\n}\n\nfn app() -> Element {\n use_init_i18n(\"en-US\".parse().unwrap(), \"en-US\".parse().unwrap(), || {\n let en_us = Language::from_str(EN_US).unwrap();\n let es_es = Language::from_str(ES_ES).unwrap();\n vec![en_us, es_es]\n });\n\n rsx! { Body {} }\n}\n\n````" + } + 55usize => { + "# Logging\n\nDioxus has a wide range of supported platforms, each with their own logging requirements. We'll discuss the different options available for your projects.\n\n#### The Tracing Crate\n\nThe [Tracing](https://crates.io/crates/tracing) crate is the logging interface that the Dioxus library uses. It is not required to use the Tracing crate, but you will not recieve logs from the Dioxus library.\n\nThe Tracing crate provides a variety of simple `println`-like macros with varying levels of severity.\nThe available macros are as follows with the highest severity on the bottom:\n\n````rs\nfn main() {\n tracing::trace!(\"trace\");\n tracing::debug!(\"debug\");\n tracing::info!(\"info\");\n tracing::warn!(\"warn\");\n tracing::error!(\"error\");\n}\n````\n\nAll the loggers provided on this page are, besides configuration and initialization, interfaced using these macros. Often you will also utilize the Tracing crate's `Level` enum. This enum usually represents the maximum log severity you want your application to emit and can be loaded from a variety of sources such as configuration file, environment variable, and more.\n\nFor more information, visit the Tracing crate's [docs](https://docs.rs/tracing/latest/tracing/).\n\n## Dioxus Logger\n\n[Dioxus Logger](https://crates.io/crates/dioxus-logger) is a logging utility that will start the appropriate logger for the platform. Currently every platform except mobile is supported.\n\nTo use Dioxus Logger, call the `init()` function:\n\n````rs\nuse tracing::Level;\n\nfn main() {\n // Init logger\n dioxus_logger::init(Level::INFO).expect(\"failed to init logger\");\n // Dioxus launch code\n}\n````\n\nThe `dioxus_logger::init()` function initializes Dioxus Logger with the appropriate tracing logger using the default configuration and provided `Level`.\n\n#### Platform Intricacies\n\nOn web, Dioxus Logger will use [tracing-wasm](https://crates.io/crates/tracing-wasm). On Desktop and server-based targets, Dioxus Logger will use [tracing-subscriber](https://crates.io/crates/tracing-subscriber)'s `FmtSubscriber`.\n\n#### Final Notes\n\nDioxus Logger is the preferred logger to use with Dioxus if it suites your needs. There are more features to come and Dioxus Logger is planned to become an integral part of Dioxus. If there are any feature suggestions or issues with Dioxus Logger, feel free to reach out on the [Dioxus Discord Server](https://discord.gg/XgGxMSkvUM)!\n\nFor more information, visit Dioxus Logger's [docs](https://docs.rs/dioxus-logger/latest/dioxus_logger/).\n\n## Desktop and Server\n\nFor Dioxus' desktop and server targets, you can generally use the logger of your choice.\n\nSome popular options are:\n\n* [tracing-subscriber](https://crates.io/crates/tracing-subscriber)'s `FmtSubscriber` for console output.\n* [tracing-appender](https://crates.io/crates/tracing-appender) for logging to files.\n* [tracing-bunyan-formatter](https://crates.io/crates/tracing-bunyan-formatter) for the Bunyan format.\n\nTo keep this guide short, we will not be covering the usage of these crates.\n\nFor a full list of popular tracing-based logging crates, visit [this](https://docs.rs/tracing/latest/tracing/#related-crates) list in the Tracing crate's docs.\n\n## Web\n\n[tracing-wasm](https://crates.io/crates/tracing-wasm) is a logging interface that can be used with Dioxus' web platform.\n\nThe easiest way to use WASM Logger is with the `set_as_global_default` function:\n\n````rs\nfn main() {\n // Init logger\n tracing_wasm::set_as_global_default();\n // Dioxus code\n}\n````\n\nThis starts tracing with a `Level` of `Trace`.\n\nUsing a custom `level` is a little trickier. We need to use the `WasmLayerConfigBuilder` and start the logger with `set_as_global_default_with_config()`:\n\n````rs\nuse tracing::Level;\n\nfn main() {\n // Init logger\n let tracing_config = tracing_wasm::WASMLayerConfigBuilder::new().set_max_level(Level::INFO).build();\n tracing_wasm::set_as_global_default_with_config(tracing_config);\n // Dioxus code\n}\n````\n\n# Mobile\n\nUnfortunately there are no tracing crates that work with mobile targets. As an alternative you can use the [log](https://crates.io/crates/log) crate.\n\n## Android\n\n[Android Logger](https://crates.io/crates/android_logger) is a logging interface that can be used when targeting Android. Android Logger runs whenever an event `native_activity_create` is called by the Android system:\n\n````rs\nuse log::LevelFilter;\nuse android_logger::Config;\n\nfn native_activity_create() {\n android_logger::init_once(\n Config::default()\n .with_max_level(LevelFilter::Info)\n .with_tag(\"myapp\");\n );\n}\n````\n\nThe `with_tag()` is what your app's logs will show as.\n\n#### Viewing Android Logs\n\nAndroid logs are sent to logcat. To use logcat through the Android debugger, run:\n\n````cmd\nadb -d logcat\n````\n\nYour Android device will need developer options/usb debugging enabled.\n\nFor more information, visit android_logger's [docs](https://docs.rs/android_logger/latest/android_logger/).\n\n## iOS\n\nThe current option for iOS is the [oslog](https://crates.io/crates/oslog) crate.\n\n````rs\nfn main() {\n // Init logger\n OsLogger::new(\"com.example.test\")\n .level_filter(LevelFilter::Debug)\n .init()\n .expect(\"failed to init logger\");\n // Dioxus code\n}\n````\n\n#### Viewing IOS Logs\n\nYou can view the emitted logs in Xcode.\n\nFor more information, visit [oslog](https://crates.io/crates/oslog)." + } + 13usize => { + "# User Input\n\nInterfaces 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.\n\n## Controlled Inputs\n\nWith 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:\n\n````rust, no_run@input_controlled.rs\npub fn App() -> Element {\n let mut name = use_signal(|| \"bob\".to_string());\n\n rsx! {\n input {\n // we tell the component what to render\n value: \"{name}\",\n // and what to do when the value changes\n oninput: move |event| name.set(event.value())\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n input_controlled::App {}\n}\n````\n\nNotice the flexibility – you can:\n\n* Also display the same contents in another element, and they will be in sync\n* Transform the input every time it is modified (e.g. to make sure it is upper case)\n* Validate the input every time it changes\n* Have custom logic happening when the input changes (e.g. network request for autocompletion)\n* Programmatically change the value (e.g. a \"randomize\" button that fills the input with nonsense)\n\n## Uncontrolled Inputs\n\nAs 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 will be editable anyway (this is built into the browser). This approach can be more performant, but less flexible. For example, it's harder to keep the input in sync with another element.\n\nSince 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`):\n\n````rust, no_run@input_uncontrolled.rs\npub fn App() -> Element {\n rsx! {\n form { onsubmit: move |event| { log::info!(\"Submitted! {event:?}\") },\n input { name: \"name\" }\n input { name: \"age\" }\n input { name: \"date\" }\n input { r#type: \"submit\" }\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n input_uncontrolled::App {}\n}\n````\n\n````\nSubmitted! UiEvent { data: FormData { value: \"\", values: {\"age\": \"very old\", \"date\": \"1966\", \"name\": \"Fred\"} } }\n````\n\n## Handling files\n\nYou can insert a file picker by using an input element of type `file`. This element supports the `multiple` attribute, to let you pick more files at the same time. You can select a folder by adding the `directory` attribute: Dioxus will map this attribute to browser specific attributes, because there is no standardized way to allow a directory to be selected.\n\n`type` is a Rust keyword, so when specifying the type of the input field, you have to write it as `r#type:\"file\"`.\n\nExtracting the selected files is a bit different from what you may typically use in Javascript.\n\nThe `FormData` event contains a `files` field with data about the uploaded files. This field contains a `FileEngine` struct which lets you fetch the filenames selected by the user. This example saves the filenames of the selected files to a `Vec`:\n\n````rust, no_run@input_fileengine.rs\npub fn App() -> Element {\n let mut filenames: Signal> = use_signal(Vec::new);\n rsx! {\n input {\n // tell the input to pick a file\n r#type: \"file\",\n // list the accepted extensions\n accept: \".txt,.rs\",\n // pick multiple files\n multiple: true,\n onchange: move |evt| {\n if let Some(file_engine) = &evt.files() {\n let files = file_engine.files();\n for file_name in files {\n filenames.write().push(file_name);\n }\n }\n }\n }\n }\n}\n````\n\nIf you're planning to read the file content, you need to do it asynchronously, to keep the rest of the UI interactive. This example event handler loads the content of the selected files in an async closure:\n\n````rust, no_run@input_fileengine_async.rs\nonchange: move |evt| {\n async move {\n if let Some(file_engine) = evt.files() {\n let files = file_engine.files();\n for file_name in &files {\n if let Some(file) = file_engine.read_file_to_string(file_name).await\n {\n files_uploaded.write().push(file);\n }\n }\n }\n }\n}\n````\n\nLastly, this example shows you how to select a folder, by setting the `directory` attribute to `true`.\n\n````rust, no_run@input_fileengine_folder.rs\ninput {\n r#type: \"file\",\n // Select a folder by setting the directory attribute\n directory: true,\n onchange: move |evt| {\n if let Some(file_engine) = evt.files() {\n let files = file_engine.files();\n for file_name in files {\n println!(\"{}\", file_name);\n }\n }\n }\n}\n````" + } + 26usize => { + "# Server-Side Rendering\n\nFor lower-level control over the rendering process, you can use the `dioxus-ssr` crate directly. This can be useful when integrating with a web framework that `dioxus-fullstack` does not support, or pre-rendering pages.\n\n## Setup\n\nFor this guide, we're going to show how to use Dioxus SSR with [Axum](https://docs.rs/axum/latest/axum/).\n\nMake sure you have Rust and Cargo installed, and then create a new project:\n\n````shell\ncargo new --bin demo\ncd demo\n````\n\nAdd Dioxus and the ssr renderer as dependencies:\n\n````shell\ncargo add dioxus@0.5.0\ncargo add dioxus-ssr@0.5.0\n````\n\nNext, add all the Axum dependencies. This will be different if you're using a different Web Framework\n\n````\ncargo add tokio --features full\ncargo add axum\n````\n\nYour dependencies should look roughly like this:\n\n````toml\n[dependencies]\naxum = \"0.7\"\ndioxus = { version = \"*\" }\ndioxus-ssr = { version = \"*\" }\ntokio = { version = \"1.15.0\", features = [\"full\"] }\n````\n\nNow, set up your Axum app to respond on an endpoint.\n\n````rust@ssr.rs\nuse axum::{response::Html, routing::get, Router};\nuse dioxus::prelude::*;\n\n#[tokio::main]\nasync fn main() {\n let listener = tokio::net::TcpListener::bind(\"127.0.0.1:3000\")\n .await\n .unwrap();\n\n println!(\"listening on http://127.0.0.1:3000\");\n\n axum::serve(\n listener,\n Router::new()\n .route(\"/\", get(app_endpoint))\n .into_make_service(),\n )\n .await\n .unwrap();\n}\n````\n\nAnd then add our endpoint. We can either render `rsx!` directly:\n\n````rust@ssr.rs\nasync fn app_endpoint() -> Html {\n // render the rsx! macro to HTML\n Html(dioxus_ssr::render_element(rsx! { div { \"hello world!\" } }))\n}\n````\n\nOr we can render VirtualDoms.\n\n````rust@ssr.rs\nasync fn app_endpoint() -> Html {\n // create a component that renders a div with the text \"hello world\"\n fn app() -> Element {\n rsx! { div { \"hello world\" } }\n }\n // create a VirtualDom with the app component\n let mut app = VirtualDom::new(app);\n // rebuild the VirtualDom before rendering\n app.rebuild_in_place();\n\n // render the VirtualDom to HTML\n Html(dioxus_ssr::render(&app))\n}\n````\n\nFinally, you can run it using `cargo run` rather than `dx serve`.\n\n## Multithreaded Support\n\nThe Dioxus VirtualDom, sadly, is not currently `Send`. Internally, we use quite a bit of interior mutability which is not thread-safe.\nWhen 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 spawn a VirtualDom on its own thread and communicate with it via channels or create a pool of VirtualDoms.\nYou might notice that you cannot hold the VirtualDom across an await point. Because Dioxus is currently not ThreadSafe, it *must* remain on the thread it started. We are working on loosening this requirement." + } + 48usize => { + "# History Buttons\n\nSome platforms, like web browsers, provide users with an easy way to navigate\nthrough an app's history. They have UI elements or integrate with the OS.\n\nHowever, native platforms usually don't provide such amenities, which means that\napps wanting users to have access to them, need to implement them. For this\nreason, the router comes with two components, which emulate a browser's back and\nforward buttons:\n\n* [`GoBackButton`]\n* [`GoForwardButton`]\n\n > \n > If you want to navigate through the history programmatically, take a look at\n > [`programmatic navigation`](./navigation/programmatic.md).\n\n````rust@history_buttons.rs\nfn HistoryNavigation() -> Element {\n rsx! {\n GoBackButton { \"Back to the Past\" }\n GoForwardButton { \"Back to the Future\" }\n }\n}\n````\n\nAs you might know, browsers usually disable the back and forward buttons if\nthere is no history to navigate to. The router's history buttons try to do that\ntoo, but depending on the \\[history provider\\] that might not be possible.\n\nImportantly, neither `WebHistory` supports that feature.\nThis is due to limitations of the browser History API.\n\nHowever, in both cases, the router will just ignore button presses, if there is\nno history to navigate to.\n\nAlso, when using `WebHistory`, the history buttons might\nnavigate a user to a history entry outside your app." + } + 62usize => { + "# Custom Renderer\n\nDioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer.\n\nGreat news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require processing `Mutations` and sending `UserEvents`.\n\n## The specifics:\n\nImplementing the renderer is fairly straightforward. The renderer needs to:\n\n1. Handle the stream of edits generated by updates to the virtual DOM\n1. Register listeners and pass events into the virtual DOM's event system\n\nEssentially, your renderer needs to process edits and generate events to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen.\n\nInternally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves.\n\nFor reference, check out the [javascript interpreter](https://github.com/DioxusLabs/dioxus/tree/v0.5/packages/interpreter) or [tui renderer](https://github.com/DioxusLabs/blitz/tree/master/packages/dioxus-tui) as a starting point for your custom renderer.\n\n## Templates\n\nDioxus is built around the concept of [Templates](https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html). Templates describe a UI tree known at compile time with dynamic parts filled at runtime. This is useful internally to make skip diffing static nodes, but it is also useful for the renderer to reuse parts of the UI tree. This can be useful for things like a list of items. Each item could contain some static parts and some dynamic parts. The renderer can use the template to create a static part of the UI once, clone it for each element in the list, and then fill in the dynamic parts.\n\n## Mutations\n\nThe `Mutation` type is a serialized enum that represents an operation that should be applied to update the UI. The variants roughly follow this set:\n\n````rust\nenum Mutation {\n\tAppendChildren,\n\tAssignId,\n\tCreatePlaceholder,\n\tCreateTextNode,\n\tHydrateText,\n\tLoadTemplate,\n\tReplaceWith,\n\tReplacePlaceholder,\n\tInsertAfter,\n\tInsertBefore,\n\tSetAttribute,\n\tSetText,\n\tNewEventListener,\n\tRemoveEventListener,\n\tRemove,\n\tPushRoot,\n}\n````\n\nThe Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia.org/wiki/Stack_machine) where the [LoadTemplate](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate), [CreatePlaceholder](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.CreatePlaceholder), and [CreateTextNode](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.CreateTextNode) mutations pushes a new \"real\" DOM node onto the stack and [AppendChildren](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.AppendChildren), [InsertAfter](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.InsertAfter), [InsertBefore](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.InsertBefore), [ReplacePlaceholder](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.ReplacePlaceholder), and [ReplaceWith](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.ReplaceWith) all remove nodes from the stack.\n\n## Node storage\n\nDioxus saves and loads elements with IDs. Inside the VirtualDOM, this is just tracked as as a u64.\n\nWhenever a `CreateElement` edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when id is used in a mutation. Dioxus reclaims the IDs of elements when removed. To stay in sync with Dioxus you can use a sparse Vec (Vec\\\\>) with possibly unoccupied items. You can use the ids as indexes into the Vec for elements, and grow the Vec when an id does not exist.\n\n### An Example\n\nFor the sake of understanding, let's consider this example – a very simple UI declaration:\n\n````rust\nrsx! {\n\th1 { \"count: {x}\" }\n}\n````\n\n#### Building Templates\n\nThe above rsx will create a template that contains one static h1 tag and a placeholder for a dynamic text node. The template contains the static parts of the UI, and ids for the dynamic parts along with the paths to access them.\n\nThe template will look something like this:\n\n````rust\nTemplate {\n\t// Some id that is unique for the entire project\n\tname: \"main.rs:1:1:0\",\n\t// The root nodes of the template\n\troots: &[\n\t\tTemplateNode::Element {\n\t\t\ttag: \"h1\",\n\t\t\tnamespace: None,\n\t\t\tattrs: &[],\n\t\t\tchildren: &[\n\t\t\t\tTemplateNode::DynamicText {\n\t\t\t\t\tid: 0\n\t\t\t\t},\n\t\t\t],\n\t\t}\n\t],\n\t// the path to each of the dynamic nodes\n\tnode_paths: &[\n\t\t// the path to dynamic node with a id of 0\n\t\t&[\n\t\t\t// on the first root node\n\t\t\t0,\n\t\t\t// the first child of the root node\n\t\t\t0,\n\t\t]\n\t],\n\t// the path to each of the dynamic attributes\n\tattr_paths: &'a [&'a [u8]],\n}\n````\n\n > \n > For more detailed docs about the structure of templates see the [Template api docs](https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html)\n\nThis template will be sent to the renderer in the [list of templates](https://docs.rs/dioxus-core/latest/dioxus_core/struct.Mutations.html#structfield.templates) supplied with the mutations the first time it is used. Any time the renderer encounters a [LoadTemplate](https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate) mutation after this, it should clone the template and store it in the given id.\n\nFor dynamic nodes and dynamic text nodes, a placeholder node should be created and inserted into the UI so that the node can be modified later.\n\nIn HTML renderers, this template could look like this:\n\n````html\n

\"\"

\n````\n\n#### Applying Mutations\n\nAfter the renderer has created all of the new templates, it can begin to process the mutations.\n\nWhen the renderer starts, it should contain the Root node on the stack and store the Root node with an id of 0. The Root node is the top-level node of the UI. In HTML, this is the `
` element.\n\n````rust\ninstructions: []\nstack: [\n\tRootNode,\n]\nnodes: [\n\tRootNode,\n]\n````\n\nThe first mutation is a `LoadTemplate` mutation. This tells the renderer to load a root from the template with the given id. The renderer will then push the root node of the template onto the stack and store it with an id for later. In this case, the root node is an h1 element.\n\n````rust\ninstructions: [\n\tLoadTemplate {\n\t\t// the id of the template\n\t\tname: \"main.rs:1:1:0\",\n\t\t// the index of the root node in the template\n\t\tindex: 0,\n\t\t// the id to store\n\t\tid: ElementId(1),\n\t}\n]\nstack: [\n\tRootNode,\n\t

\"\"

,\n]\nnodes: [\n\tRootNode,\n\t

\"\"

,\n]\n````\n\nNext, Dioxus will create the dynamic text node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the Mutation `HydrateText`. When the renderer receives this instruction, it will navigate to the placeholder text node in the template and replace it with the new text.\n\n````rust\ninstructions: [\n\tLoadTemplate {\n\t\tname: \"main.rs:1:1:0\",\n\t\tindex: 0,\n\t\tid: ElementId(1),\n\t},\n\tHydrateText {\n\t\t// the id to store the text node\n\t\tid: ElementId(2),\n\t\t// the text to set\n\t\ttext: \"count: 0\",\n\t}\n]\nstack: [\n\tRootNode,\n\t

\"count: 0\"

,\n]\nnodes: [\n\tRootNode,\n\t

\"count: 0\"

,\n\t\"count: 0\",\n]\n````\n\nRemember, the h1 node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the h1 node to the Root. It depends on the situation, but in this case, we use `AppendChildren`. This pops the text node off the stack, leaving the Root element as the next element on the stack.\n\n````rust\ninstructions: [\n\tLoadTemplate {\n\t\tname: \"main.rs:1:1:0\",\n\t\tindex: 0,\n\t\tid: ElementId(1),\n\t},\n\tHydrateText {\n\t\tid: ElementId(2),\n\t\ttext: \"count: 0\",\n\t},\n\tAppendChildren {\n\t\t// the id of the parent node\n\t\tid: ElementId(0),\n\t\t// the number of nodes to pop off the stack and append\n\t\tm: 1\n\t}\n]\nstack: [\n\tRootNode,\n]\nnodes: [\n\tRootNode,\n\t

\"count: 0\"

,\n\t\"count: 0\",\n]\n````\n\nOver time, our stack looked like this:\n\n````rust\n[Root]\n[Root,

\"\"

]\n[Root,

\"count: 0\"

]\n[Root]\n````\n\nConveniently, this approach completely separates the Virtual DOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits make Dioxus independent of platform specifics.\n\nDioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing.\n\nThis little demo serves to show exactly how a Renderer would need to process a mutation stream to build UIs.\n\n## Event loop\n\nLike most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important for your custom renderer can handle those too.\n\nThe code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is:\n\n````rust, ignore\npub async fn run(&mut self) -> dioxus_core::error::Result<()> {\n\t// Push the body element onto the WebsysDom's stack machine\n\tlet mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom());\n\twebsys_dom.stack.push(root_node);\n\n\t// Rebuild or hydrate the virtualdom\n\tlet mutations = self.internal_dom.rebuild();\n\twebsys_dom.apply_mutations(mutations);\n\n\t// Wait for updates from the real dom and progress the virtual dom\n\tloop {\n\t\tlet user_input_future = websys_dom.wait_for_event();\n\t\tlet internal_event_future = self.internal_dom.wait_for_work();\n\n\t\tmatch select(user_input_future, internal_event_future).await {\n\t\t\tEither::Left((_, _)) => {\n\t\t\t\tlet mutations = self.internal_dom.work_with_deadline(|| false);\n\t\t\t\twebsys_dom.apply_mutations(mutations);\n\t\t\t},\n\t\t\tEither::Right((event, _)) => websys_dom.handle_event(event),\n\t\t}\n\n\t\t// render\n\t}\n}\n````\n\nIt's important to decode what the real events are for your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `UserEvent` type. Right now, the virtual event system is modeled almost entirely around the HTML spec, but we are interested in slimming it down.\n\n````rust, ignore\nfn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {\n\tmatch event.type_().as_str() {\n\t\t\"keydown\" => {\n\t\t\tlet event: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();\n\t\t\tUserEvent::KeyboardEvent(UserEvent {\n\t\t\t\tscope_id: None,\n\t\t\t\tpriority: EventPriority::Medium,\n\t\t\t\tname: \"keydown\",\n\t\t\t\t// This should be whatever element is focused\n\t\t\t\telement: Some(ElementId(0)),\n\t\t\t\tdata: Arc::new(KeyboardData{\n\t\t\t\t\tchar_code: event.char_code(),\n\t\t\t\t\tkey: event.key(),\n\t\t\t\t\tkey_code: event.key_code(),\n\t\t\t\t\talt_key: event.alt_key(),\n\t\t\t\t\tctrl_key: event.ctrl_key(),\n\t\t\t\t\tmeta_key: event.meta_key(),\n\t\t\t\t\tshift_key: event.shift_key(),\n\t\t\t\t\tlocation: event.location(),\n\t\t\t\t\trepeat: event.repeat(),\n\t\t\t\t\twhich: event.which(),\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\t\t_ => todo!()\n\t}\n}\n````\n\n## Custom raw elements\n\nIf you need to go as far as relying on custom elements/attributes 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. You can drop in your elements any time you want, with little hassle. However, you must be sure your renderer can handle the new namespace.\n\nFor more examples and information on how to create custom namespaces, see the [`dioxus_html` crate](https://github.com/DioxusLabs/dioxus/blob/main/packages/html/README.md#how-to-extend-it).\n\n# Native Core\n\nIf you are creating a renderer in rust, the [native-core](https://github.com/DioxusLabs/blitz/tree/master/packages/native-core) crate provides some utilities to implement a renderer. It provides an abstraction over Mutations and Templates and contains helpers that can handle the layout and text editing for you.\n\n## The RealDom\n\nThe `RealDom` is a higher-level abstraction over updating the Dom. It uses an entity component system to manage the state of nodes. This system allows you to modify insert and modify arbitrary components on nodes. On top of this, the RealDom provides a way to manage a tree of nodes, and the State trait provides a way to automatically add and update these components when the tree is modified. It also provides a way to apply `Mutations` to the RealDom.\n\n### Example\n\nLet's build a toy renderer with borders, size, and text color.\nBefore we start let's take a look at an example element we can render:\n\n````rust\nrsx!{\n\tdiv{\n\t\tcolor: \"red\",\n\t\tp{\n\t\t\tborder: \"1px solid black\",\n\t\t\t\"hello world\"\n\t\t}\n\t}\n}\n````\n\nIn this tree, the color depends on the parent's color. The layout depends on the children's layout, the current text, and the text size. The border depends on only the current node.\n\nIn the following diagram arrows represent dataflow:\n\n[![](https://mermaid.ink/img/pako:eNqllV1vgjAUhv8K6W4wkQVa2QdLdrHsdlfukmSptEhjoaSWqTH-9xVwONAKst70g5739JzzlO5BJAgFAYi52EQJlsr6fAszS7d1sVhKnCdWJDJFt6peLVs5-9owohK7HFrVcFJ_pxnpmK8VVvRkTJikkWIiaxy1dhP23bUwW1WW5WbPrrqJ4ziR4EJ6dtVN2ls5y1ZztePUcrWZFCvqVEcPPDffvlyS1XoLIQnVgnVvVPR6FU9Zc-6dV453ojjOPbuetRJ57gIeXQR3cez7rjtteZyZQ2j5MqmjqwE0ZW0VKx9RKtgpFewp1aw3sXXFy6TWgiYlv8mfq1scD8ofbBCAfQg8_AMBOAyBxzEIwA4CxgQ99QbQkjnD2KT7_CfxGF8_9WXQEsq5sDZCcjICOXRCri4h6r3NA38Q6Jdi1EOx5w3DGDYYI6MUvJFjM3VoGHUeGoMd6mBnDmh2E3fo7O4Yhf0x4OkBmIKUyhQzol_GfbkcApXQlIYg0EOC5SoEYXbQ-3ChxHyXRSBQsqBTUOREx_7OsAY3BUGM-VqvUsKUkB_1U6vf05gtweEHTk4_HQ?type=png)](https://mermaid.live/edit#pako:eNqllV1vgjAUhv8K6W4wkQVa2QdLdrHsdlfukmSptEhjoaSWqTH-9xVwONAKst70g5739JzzlO5BJAgFAYi52EQJlsr6fAszS7d1sVhKnCdWJDJFt6peLVs5-9owohK7HFrVcFJ_pxnpmK8VVvRkTJikkWIiaxy1dhP23bUwW1WW5WbPrrqJ4ziR4EJ6dtVN2ls5y1ZztePUcrWZFCvqVEcPPDffvlyS1XoLIQnVgnVvVPR6FU9Zc-6dV453ojjOPbuetRJ57gIeXQR3cez7rjtteZyZQ2j5MqmjqwE0ZW0VKx9RKtgpFewp1aw3sXXFy6TWgiYlv8mfq1scD8ofbBCAfQg8_AMBOAyBxzEIwA4CxgQ99QbQkjnD2KT7_CfxGF8_9WXQEsq5sDZCcjICOXRCri4h6r3NA38Q6Jdi1EOx5w3DGDYYI6MUvJFjM3VoGHUeGoMd6mBnDmh2E3fo7O4Yhf0x4OkBmIKUyhQzol_GfbkcApXQlIYg0EOC5SoEYXbQ-3ChxHyXRSBQsqBTUOREx_7OsAY3BUGM-VqvUsKUkB_1U6vf05gtweEHTk4_HQ)\n\nTo help in building a Dom, native-core provides the State trait and a RealDom struct. The State trait provides a way to describe how states in a node depend on other states in its relatives. By describing how to update a single node from its relations, native-core will derive a way to update the states of all nodes for you. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom.\n\nNative Core cannot create all of the required methods for the State trait, but it can derive some of them. To implement the State trait, you must implement the following methods and let the `#[partial_derive_state]` macro handle the rest:\n\n````rust, ignore@custom_renderer.rs\n\n````\n\nLets take a look at how to implement the State trait for a simple renderer.\n\n````rust@custom_renderer.rs\n\n````\n\nNow that we have our state, we can put it to use in our RealDom. We can update the RealDom with apply_mutations to update the structure of the dom (adding, removing, and changing properties of nodes) and then update_state to update the States for each of the nodes that changed.\n\n````rust@custom_renderer.rs\n\n````\n\n## Layout\n\nFor most platforms, the layout of the Elements will stay the same. The [layout_attributes](https://docs.rs/dioxus-native-core/latest/dioxus_native_core/layout_attributes/index.html) module provides a way to apply HTML attributes a [Taffy](https://docs.rs/taffy/latest/taffy/index.html) layout style.\n\n## Text Editing\n\nTo make it easier to implement text editing in rust renderers, `native-core` also contains a renderer-agnostic cursor system. The cursor can handle text editing, selection, and movement with common keyboard shortcuts integrated.\n\n````rust@custom_renderer.rs\n\n````\n\n## Conclusion\n\nThat should be it! You should have nearly all the knowledge required on how to implement your renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderers, video game UI, and even augmented reality! If you're interested in contributing to any of these projects, don't be afraid to reach out or join the [community](https://discord.gg/XgGxMSkvUM)." + } + 36usize => { + "# Creating Our First Route\n\nIn this chapter, we will start utilizing Dioxus Router and add a homepage and a\n404 page to our project.\n\n## Fundamentals\n\nThe core of the Dioxus Router is the [`Routable`] macro and the [`Router`] component.\n\nRoutable is a trait for anything that can:\n\n* Be parsed from a URL\n* Be turned into a URL\n* Be rendered as to a Element\n\nLet's create a new router. First, we need an actual page to route to! Let's add a homepage component:\n\n````rust@first_route.rs\n#[component]\nfn Home() -> Element {\n rsx! { h1 { \"Welcome to the Dioxus Blog!\" } }\n}\n````\n\n## Creating Routes\n\nWe want to use Dioxus Router to separate our application into different \"pages\".\nDioxus Router will then determine which page to render based on the URL path.\n\nTo start using Dioxus Router, we need to use the [`Routable`] macro.\n\nThe [`Routable`] macro takes an enum with all of the possible routes in our application. Each variant of the enum represents a route and must be annotated with the `#[route(path)]` attribute.\n\n````rust@first_route.rs\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n/// An enum of all of the possible routes in the app.\n#[derive(Routable, Clone)]\nenum Route {\n // The home page is at the / route\n #[route(\"/\")]\n Home {},\n}\n````\n\nThe [`Router`] component will provide a router context for all the inner components and hooks to use. You usually will want to place this at the top of your components tree.\n\n````rust@first_route.rs\nfn App() -> Element {\n rsx! { Router:: {} }\n}\n````\n\nIf you head to your application's browser tab, you should now see the text\n`Welcome to Dioxus Blog!` when on the root URL (`http://localhost:8080/`). If\nyou enter a different path for the URL, nothing should be displayed.\n\nThis is because we told Dioxus Router to render the `Home` component only when\nthe URL path is `/`.\n\n## Fallback Route\n\nIn our example, when a route doesn't exist Dioxus Router doesn't render anything. Many sites also have a \"404\" page when a path does not exist. Let's add one to our site.\n\nFirst, we create a new `PageNotFound` component.\n\n````rust@catch_all.rs\n#[component]\nfn PageNotFound(route: Vec) -> Element {\n rsx! {\n h1 { \"Page not found\" }\n p { \"We are terribly sorry, but the page you requested doesn't exist.\" }\n pre { color: \"red\", \"log:\\nattemped to navigate to: {route:?}\" }\n }\n}\n````\n\nNext, register the route in the Route enum to match if all other routes fail.\n\n````rust@catch_all.rs\n#[derive(Routable, Clone)]\nenum Route {\n #[route(\"/\")]\n Home {},\n // PageNotFound is a catch all route that will match any route and placing the matched segments in the route field\n #[route(\"/:..route\")]\n PageNotFound { route: Vec },\n}\n````\n\nNow when you go to a route that doesn't exist, you should see the page not found\ntext.\n\n## Conclusion\n\nIn this chapter, we learned how to create a route and tell Dioxus Router what\ncomponent to render when the URL path is `/`. We also created a 404 page to\nhandle when a route doesn't exist. Next, we'll create the blog portion of our\nsite. We will utilize nested routes and URL parameters." + } + 49usize => { + "# Routing Update Callback\n\nIn some cases, we might want to run custom code when the current route changes. For this reason, the [`RouterConfig`] exposes an `on_update` field.\n\n## How does the callback behave?\n\nThe `on_update` is called whenever the current routing information changes. It is called after the router updated its internal state, but before dependent components and hooks are updated.\n\nIf the callback returns a [`NavigationTarget`], the router will replace the current location with the specified target. It will not call the `on_update` again.\n\nIf at any point the router encounters a navigation failure, it will go to the appropriate state without calling the `on_update`. It doesn't matter if the invalid target initiated the navigation, was found as a redirect target, or was returned by the `on_update` itself.\n\n## Code Example\n\n````rust@routing_update.rs\n#[derive(Routable, Clone, PartialEq)]\nenum Route {\n #[route(\"/\")]\n Index {},\n #[route(\"/home\")]\n Home {},\n}\n\n#[component]\nfn Home() -> Element {\n rsx! { p { \"Home\" } }\n}\n\n#[component]\nfn Index() -> Element {\n rsx! { p { \"Index\" } }\n}\n\nfn app() -> Element {\n rsx! {\n Router:: {\n config: || {\n RouterConfig::default()\n .on_update(|state| {\n (state.current() == Route::Index {})\n .then_some(NavigationTarget::Internal(Route::Home {}))\n })\n }\n }\n }\n}\n````" + } + 63usize => { + "# Optimizing\n\n*Note: This is written primarily for the web, but the main optimizations will work on other platforms too.*\n\nYou might have noticed that Dioxus binaries are pretty big.\nThe WASM binary of a [TodoMVC app](https://github.com/tigerros/dioxus-todo-app) weighs in at 2.36mb!\nDon't worry; we can get it down to a much more manageable 234kb.\nThis will get obviously lower over time.\nWith nightly features, you can even reduce the binary size of a hello world app to less than 100kb!\n\nWe will also discuss ways to optimize your app for increased speed.\n\nHowever, certain optimizations will sacrifice speed for decreased binary size or the other way around.\nThat's what you need to figure out yourself. Does your app perform performance-intensive tasks, such as graphical processing or tons of DOM manipulations?\nYou could go for increased speed. In most cases, though, decreased binary size is the better choice, especially because Dioxus WASM binaries are quite large.\n\nTo test binary sizes, we will use [this](https://github.com/tigerros/dioxus-todo-app) repository as a sample app.\nThe `no-optimizations` package will serve as the base, which weighs 2.36mb as of right now.\n\nAdditional resources:\n\n* [WASM book - Shrinking `.wasm` code size](https://rustwasm.github.io/docs/book/reference/code-size.html)\n* [min-sized-rust](https://github.com/johnthagen/min-sized-rust)\n\n## Building in release mode\n\nThis is the best way to optimize. In fact, the 2.36mb figure at the start of the guide is with release mode.\nIn debug mode, it's actually a whopping 32mb! It also increases the speed of your app.\n\nThankfully, no matter what tool you're using to build your app, it will probably have a `--release` flag to do this.\n\nUsing the [Dioxus CLI](https://dioxuslabs.com/learn/0.5/CLI) or [Trunk](https://trunkrs.dev/):\n\n* Dioxus CLI: `dx build --release`\n* Trunk: `trunk build --release`\n\n## UPX\n\nIf you're not targeting web, you can use the [UPX](https://github.com/upx/upx) CLI tool to compress your executables.\n\nSetup:\n\n* Download a [release](https://github.com/upx/upx/releases) and extract the directory inside to a sensible location.\n* Add the executable located in the directory to your path variable.\n\nYou can run `upx --help` to get the CLI options, but you should also view `upx-doc.html` for more detailed information.\nIt's included in the extracted directory.\n\nAn example command might be: `upx --best -o target/release/compressed.exe target/release/your-executable.exe`.\n\n## Build configuration\n\n*Note: Settings defined in `.cargo/config.toml` will override settings in `Cargo.toml`.*\n\nOther than the `--release` flag, this is the easiest way to optimize your projects, and also the most effective way,\nat least in terms of reducing binary size.\n\n### Stable\n\nThis configuration is 100% stable and decreases the binary size from 2.36mb to 310kb.\nAdd this to your `.cargo/config.toml`:\n\n````toml\n[profile.release]\nopt-level = \"z\"\ndebug = false\nlto = true\ncodegen-units = 1\npanic = \"abort\"\nstrip = true\nincremental = false\n````\n\nLinks to the documentation of each value:\n\n* [`opt-level`](https://doc.rust-lang.org/rustc/codegen-options/index.html#opt-level)\n* [`debug`](https://doc.rust-lang.org/rustc/codegen-options/index.html#debuginfo)\n* [`lto`](https://doc.rust-lang.org/rustc/codegen-options/index.html#lto)\n* [`codegen-units`](https://doc.rust-lang.org/rustc/codegen-options/index.html#codegen-units)\n* [`panic`](https://doc.rust-lang.org/rustc/codegen-options/index.html#panic)\n* [`strip`](https://doc.rust-lang.org/rustc/codegen-options/index.html#strip)\n* [`incremental`](https://doc.rust-lang.org/rustc/codegen-options/index.html#incremental)\n\n### Unstable\n\nThis configuration contains some unstable features, but it should work just fine.\nIt decreases the binary size from 310kb to 234kb.\nAdd this to your `.cargo/config.toml`:\n\n````toml\n[unstable]\nbuild-std = [\"std\", \"panic_abort\", \"core\", \"alloc\"]\nbuild-std-features = [\"panic_immediate_abort\"]\n\n[build]\nrustflags = [\n \"-Clto\",\n \"-Zvirtual-function-elimination\",\n \"-Zlocation-detail=none\"\n]\n\n# Same as in the Stable section\n[profile.release]\nopt-level = \"z\"\ndebug = false\nlto = true\ncodegen-units = 1\npanic = \"abort\"\nstrip = true\nincremental = false\n````\n\n*Note: The omitted space in each flag (e.g., `-Clto`) is intentional. It is not a typo.*\n\nThe values in `[profile.release]` are documented in the [Stable](#stable) section. Links to the documentation of each value:\n\n* [`[build.rustflags]`](https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags)\n* [`-C lto`](https://doc.rust-lang.org/rustc/codegen-options/index.html#lto)\n* [`-Z virtual-function-elimination`](https://doc.rust-lang.org/stable/unstable-book/compiler-flags/virtual-function-elimination.html)\n* [`-Z location-detail`](https://doc.rust-lang.org/stable/unstable-book/compiler-flags/location-detail.html)\n\n## wasm-opt\n\n*Note: In the future, `wasm-opt` will be supported natively through the [Dioxus CLI](https://crates.io/crates/dioxus-cli).*\n\n`wasm-opt` is a tool from the [binaryen](https://github.com/WebAssembly/binaryen) library that optimizes your WASM files.\nTo use it, install a [binaryen release](https://github.com/WebAssembly/binaryen/releases) and run this command from the package directory:\n\n````\nwasm-opt dist/assets/dioxus/APP_NAME_bg.wasm -o dist/assets/dioxus/APP_NAME_bg.wasm -Oz\n````\n\nThe `-Oz` flag specifies that `wasm-opt` should optimize for size. For speed, use `-O4`.\n\n## Improving Dioxus code\n\nLet's talk about how you can improve your Dioxus code to be more performant.\n\nIt's important to minimize the number of dynamic parts in your `rsx`, like conditional rendering.\nWhen Dioxus is rendering your component, it will skip parts that are the same as the last render.\nThat means that if you keep dynamic rendering to a minimum, your app will speed up, and quite a bit if it's not just hello world.\nTo see an example of this, check out [Dynamic Rendering](../reference/dynamic_rendering.md).\n\nAlso check out [Anti-patterns](antipatterns.md) for patterns that you should avoid.\nObviously, not all of them are just about performance, but some of them are.\n\n## Optimizing the size of assets\n\nAssets can be a significant part of your app's size. Dioxus includes alpha support for first party [assets](../reference/assets.md). Any assets you include with the `mg!` macro will be optimized for production in release builds." + } + 5usize => { + "# Fetching Data\n\nIn this chapter, we will fetch data from the hacker news API and use it to render the list of top posts in our application.\n\n## Defining the API\n\nFirst we need to create some utilities to fetch data from the hackernews API using [reqwest](https://docs.rs/reqwest/latest/reqwest/index.html):\n\n````rust@hackernews_async.rs\n// Define the Hackernews API\nuse futures::future::join_all;\n\npub static BASE_API_URL: &str = \"https://hacker-news.firebaseio.com/v0/\";\npub static ITEM_API: &str = \"item/\";\npub static USER_API: &str = \"user/\";\nconst COMMENT_DEPTH: i64 = 2;\n\npub async fn get_story_preview(id: i64) -> Result {\n let url = format!(\"{}{}{}.json\", BASE_API_URL, ITEM_API, id);\n reqwest::get(&url).await?.json().await\n}\n\npub async fn get_stories(count: usize) -> Result, reqwest::Error> {\n let url = format!(\"{}topstories.json\", BASE_API_URL);\n let stories_ids = &reqwest::get(&url).await?.json::>().await?[..count];\n\n let story_futures = stories_ids[..usize::min(stories_ids.len(), count)]\n .iter()\n .map(|&story_id| get_story_preview(story_id));\n let stories = join_all(story_futures)\n .await\n .into_iter()\n .filter_map(|story| story.ok())\n .collect();\n Ok(stories)\n}\n\npub async fn get_story(id: i64) -> Result {\n let url = format!(\"{}{}{}.json\", BASE_API_URL, ITEM_API, id);\n let mut story = reqwest::get(&url).await?.json::().await?;\n let comment_futures = story.item.kids.iter().map(|&id| get_comment(id));\n let comments = join_all(comment_futures)\n .await\n .into_iter()\n .filter_map(|c| c.ok())\n .collect();\n\n story.comments = comments;\n Ok(story)\n}\n\n#[async_recursion::async_recursion(?Send)]\npub async fn get_comment_with_depth(id: i64, depth: i64) -> Result {\n let url = format!(\"{}{}{}.json\", BASE_API_URL, ITEM_API, id);\n let mut comment = reqwest::get(&url).await?.json::().await?;\n if depth > 0 {\n let sub_comments_futures = comment\n .kids\n .iter()\n .map(|story_id| get_comment_with_depth(*story_id, depth - 1));\n comment.sub_comments = join_all(sub_comments_futures)\n .await\n .into_iter()\n .filter_map(|c| c.ok())\n .collect();\n }\n Ok(comment)\n}\n\npub async fn get_comment(comment_id: i64) -> Result {\n let comment = get_comment_with_depth(comment_id, COMMENT_DEPTH).await?;\n Ok(comment)\n}\n````\n\nThe code above requires you to add the [reqwest](https://crates.io/crates/reqwest), [async_recursion](https://crates.io/crates/async-recursion), and [futures](https://crates.io/crates/futures) crate:\n\n````bash\ncargo add reqwest --features json\ncargo add async_recursion\ncargo add futures\n````\n\nA quick overview of the supporting crates:\n\n* [reqwest](https://crates.io/crates/reqwest) allows us to create HTTP calls to the hackernews API.\n* [async_recursion](https://crates.io/crates/async-recursion) provides a utility macro to allow us to recursively use an async function.\n* [futures](https://crates.io/crates/futures) provides us with utilities all around Rust's futures.\n\n## Working with Async\n\n[`use_resource`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_resource.html) is a [hook](./state.md) that lets you run an async closure, and provides you with its result.\n\nFor example, we can make an API request (using [reqwest](https://docs.rs/reqwest/latest/reqwest/index.html)) inside `use_resource`:\n\n````rust@hackernews_async.rs\nfn Stories() -> Element {\n // Fetch the top 10 stories on Hackernews\n let stories = use_resource(move || get_stories(10));\n\n // check if the future is resolved\n match &*stories.read_unchecked() {\n Some(Ok(list)) => {\n // if it is, render the stories\n rsx! {\n div {\n // iterate over the stories with a for loop\n for story in list {\n // render every story with the StoryListing component\n StoryListing { story: story.clone() }\n }\n }\n }\n }\n Some(Err(err)) => {\n // if there was an error, render the error\n rsx! {\"An error occurred while fetching stories {err}\"}\n }\n None => {\n // if the future is not resolved yet, render a loading message\n rsx! {\"Loading items\"}\n }\n }\n}\n````\n\nThe code inside `use_resource` will be submitted to the Dioxus scheduler once the component has rendered.\n\nWe can use `.read()` 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.\n\nWe can then render the result by looping over each of the posts and rendering them with the `StoryListing` component.\n\n````inject-dioxus\nDemoFrame {\n\thackernews_async::fetch::App {}\n}\n````\n\n > \n > You can read more about working with Async in Dioxus in the [Async reference](../reference/index.md)\n\n## Lazily Fetching Data\n\nFinally, we will lazily fetch the comments on each post as the user hovers over the post.\n\nWe need to revisit the code that handles hovering over an item. Instead of passing an empty list of comments, we can fetch all the related comments when the user hovers over the item.\n\nWe will cache the list of comments with a [use_signal](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_signal.html) hook. This hook allows you to store some state in a single component. When the user triggers fetching the comments we will check if the response has already been cached before fetching the data from the hackernews API.\n\n````rust@hackernews_async.rs\n// New\nasync fn resolve_story(\n mut full_story: Signal>,\n mut preview_state: Signal,\n story_id: i64,\n) {\n if let Some(cached) = full_story.as_ref() {\n *preview_state.write() = PreviewState::Loaded(cached.clone());\n return;\n }\n\n *preview_state.write() = PreviewState::Loading;\n if let Ok(story) = get_story(story_id).await {\n *preview_state.write() = PreviewState::Loaded(story.clone());\n *full_story.write() = Some(story);\n }\n}\n\n#[component]\nfn StoryListing(story: ReadOnlySignal) -> Element {\n let mut preview_state = consume_context::>();\n let StoryItem {\n title,\n url,\n by,\n score,\n time,\n kids,\n id,\n ..\n } = story();\n // New\n let full_story = use_signal(|| None);\n\n let url = url.as_deref().unwrap_or_default();\n let hostname = url\n .trim_start_matches(\"https://\")\n .trim_start_matches(\"http://\")\n .trim_start_matches(\"www.\");\n let score = format!(\"{score} {}\", if score == 1 { \" point\" } else { \" points\" });\n let comments = format!(\n \"{} {}\",\n kids.len(),\n if kids.len() == 1 {\n \" comment\"\n } else {\n \" comments\"\n }\n );\n let time = time.format(\"%D %l:%M %p\");\n\n rsx! {\n div {\n padding: \"0.5rem\",\n position: \"relative\",\n onmouseenter: move |_event| { resolve_story(full_story, preview_state, id) },\n div { font_size: \"1.5rem\",\n a {\n href: url,\n onfocus: move |_event| { resolve_story(full_story, preview_state, id) },\n // ...\n\n````\n\n````inject-dioxus\nDemoFrame {\n\thackernews_async::App {}\n}\n````" + } + 14usize => { + "# Sharing State\n\nOften, multiple components need to access the same state. Depending on your needs, there are several ways to implement this.\n\n## Lifting State\n\nOne approach to share state between components is to \"lift\" it up to the nearest common ancestor. This means putting the `use_signal` hook in a parent component, and passing the needed values down as props.\n\nSuppose 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).\n\n > \n > 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, maintainable, and performant (this is even more important for larger, complex apps).\n\nWe start with a `Meme` component, responsible for rendering a meme with a given caption:\n\n````rust, no_run@meme_editor.rs\n#[component]\nfn Meme(caption: String) -> Element {\n let container_style = r#\"\n position: relative;\n width: fit-content;\n \"#;\n\n let caption_container_style = r#\"\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 16px 8px;\n \"#;\n\n let caption_style = r\"\n font-size: 32px;\n margin: 0;\n color: white;\n text-align: center;\n \";\n\n rsx! {\n div { style: \"{container_style}\",\n img { src: \"https://i.imgflip.com/2zh47r.jpg\", height: \"500px\" }\n div { style: \"{caption_container_style}\", p { style: \"{caption_style}\", \"{caption}\" } }\n }\n }\n}\n````\n\n > \n > Note that the `Meme` component is unaware where the caption is coming from – it could be stored in `use_signal`, or a constant. This ensures that it is very reusable – the same component can be used for a meme gallery without any changes!\n\nWe 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:\n\n````rust, no_run@meme_editor.rs\n#[component]\nfn CaptionEditor(caption: String, oninput: EventHandler) -> Element {\n let input_style = r\"\n border: none;\n background: cornflowerblue;\n padding: 8px 16px;\n margin: 0;\n border-radius: 4px;\n color: white;\n \";\n\n rsx! {\n input {\n style: \"{input_style}\",\n value: \"{caption}\",\n oninput: move |event| oninput.call(event)\n }\n }\n}\n````\n\nFinally, a third component will render the other two as children. It will be responsible for keeping the state and passing down the relevant props.\n\n````rust, no_run@meme_editor.rs\nfn MemeEditor() -> Element {\n let container_style = r\"\n display: flex;\n flex-direction: column;\n gap: 16px;\n margin: 0 auto;\n width: fit-content;\n \";\n\n let mut caption = use_signal(|| \"me waiting for my rust code to compile\".to_string());\n\n rsx! {\n div { style: \"{container_style}\",\n h1 { \"Meme Editor\" }\n Meme { caption: caption }\n CaptionEditor { caption: caption, oninput: move |event: FormEvent| caption.set(event.value()) }\n }\n }\n}\n````\n\n![Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: \"me waiting for a language feature\"](/assets/static/meme_editor_screenshot.png)\n\n## Using Shared State\n\nSometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient.\n\nSuppose 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.\n\n > \n > 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!\n\nNow, we could write another `use_signal` 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!\n\nDioxus 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 provides any Clone context (including Signals!) to any child components. Child components can use the [`use_context`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html) hook to get that context and if it is a Signal, they can read and write to it.\n\nFirst, we have to create a struct for our dark mode configuration:\n\n````rust, no_run@meme_editor_dark_mode.rs\n#[derive(Clone, Copy)]\nstruct DarkMode(bool);\n````\n\nNow, in a top-level component (like `App`), we can provide the `DarkMode` context to all children components:\n\n````rust, no_run@meme_editor_dark_mode.rs\nuse_context_provider(|| Signal::new(DarkMode(false)));\n````\n\nAs a result, any child component of `App` (direct or not), can access the `DarkMode` context.\n\n````rust, no_run@meme_editor_dark_mode.rs\nlet dark_mode_context = use_context::>();\n````\n\n > \n > `use_context` returns `Signal` here, because the Signal was provided by the parent. If the context hadn't been provided `use_context` would have panicked.\n\nIf you have a component where the context might or not be provided, you might want to use `try_consume_context`instead, so you can handle the `None` case. The drawback of this method is that it will not memoize the value between renders, so it won't be as as efficient as `use_context`, you could do it yourself with `use_hook` though.\n\nFor 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):\n\n````rust, no_run@meme_editor_dark_mode.rs\npub fn DarkModeToggle() -> Element {\n let mut dark_mode = use_context::>();\n\n let style = if dark_mode().0 { \"color:white\" } else { \"\" };\n\n rsx! {\n label { style: \"{style}\",\n \"Dark Mode\"\n input {\n r#type: \"checkbox\",\n oninput: move |event| {\n let is_enabled = event.value() == \"true\";\n dark_mode.write().0 = is_enabled;\n }\n }\n }\n }\n}\n````" + } + 17usize => { + "# Resource\n\n[`use_resource`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_resource.html) lets you run an async closure, and provides you with its result.\n\nFor example, we can make an API request (using [reqwest](https://docs.rs/reqwest/latest/reqwest/index.html)) inside `use_resource`:\n\n````rust@use_resource.rs\nlet mut future = use_resource(|| async move {\n reqwest::get(\"https://dog.ceo/api/breeds/image/random\")\n .await\n .unwrap()\n .json::()\n .await\n});\n````\n\nThe code inside `use_resource` will be submitted to the Dioxus scheduler once the component has rendered.\n\nWe can use `.read()` 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.\n\nWe can then render that result:\n\n````rust@use_resource.rs\nmatch &*future.read_unchecked() {\n Some(Ok(response)) => rsx! {\n button { onclick: move |_| future.restart(), \"Click to fetch another doggo\" }\n div {\n img {\n max_width: \"500px\",\n max_height: \"500px\",\n src: \"{response.image_url}\",\n }\n }\n },\n Some(Err(_)) => rsx! {\n div { \"Loading dogs failed\" }\n },\n None => rsx! {\n div { \"Loading dogs...\" }\n },\n}\n````\n\n````inject-dioxus\nDemoFrame {\n use_resource::App {}\n}\n````\n\n## Restarting the Future\n\nThe `Resource` handle provides a `restart` method. It can be used to execute the future again, producing a new value.\n\n## Dependencies\n\nOften, you will need to run the future again every time some value (e.g. a state) changes. Rather than calling `restart` manually, you can read a signal inside of the future. It will automatically re-run the future when any of the states you read inside the future change. Example:\n\n````rust, no_run@use_resource.rs\nlet future = use_resource(move || async move {\n reqwest::get(format!(\"https://dog.ceo/api/breed/{breed}/images/random\"))\n .await\n .unwrap()\n .json::()\n .await\n});\n\n// You can also add non-reactive state to the resource hook with the use_reactive method\nlet non_reactive_state = \"poodle\";\nuse_resource(use_reactive!(|(non_reactive_state,)| async move {\n reqwest::get(format!(\n \"https://dog.ceo/api/breed/{non_reactive_state}/images/random\"\n ))\n .await\n .unwrap()\n .json::()\n .await\n}));\n````" + } + 41usize => { + "# Adding the router to your application\n\nIn this chapter, we will learn how to add the router to our app. By itself, this\nis not very useful. However, it is a prerequisite for all the functionality\ndescribed in the other chapters.\n\n > \n > Make sure you added the `dioxus-router` dependency as explained in the\n > [introduction](../index.md).\n\nIn most cases, we want to add the router to the root component of our app. This\nway, we can ensure that we have access to all its functionality everywhere.\n\nFirst, we define the router with the router macro:\n\n````rust@first_route.rs\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n/// An enum of all of the possible routes in the app.\n#[derive(Routable, Clone)]\nenum Route {\n // The home page is at the / route\n #[route(\"/\")]\n Home {},\n}\n````\n\nThen we render the router with the \\[`Router`\\] component.\n\n````rust@first_route.rs\nfn App() -> Element {\n rsx! { Router:: {} }\n}\n````" + } + 3usize => { + "# Your First Component\n\nThis chapter will teach you how to create a [Component](../reference/components.md) that displays a link to a post on hackernews.\n\n## Setup\n\n > \n > Before you start the guide, make sure you have the dioxus CLI and any required dependencies for your platform as described in the [getting started](../getting_started/index.md) guide.\n\nFirst, let's create a new project for our hacker news app. We can use the CLI to create a new project. You can select a platform of your choice or view the getting started guide for more information on each option. If you aren't sure what platform to try out, we recommend getting started with web or desktop:\n\n````sh\ndx new\n````\n\nThe template contains some boilerplate to help you get started. For this guide, we will be rebuilding some of the code from scratch for learning purposes. You can clear the `src/main.rs` file. We will be adding new code in the next sections.\n\nNext, let's setup our dependencies. We need to set up a few dependencies to work with the hacker news API:\n\n````sh\ncargo add chrono --features serde\ncargo add futures\ncargo add reqwest --features json\ncargo add serde --features derive\ncargo add serde_json\ncargo add async_recursion\n````\n\n## Describing the UI\n\nNow, we can define how to display a post. Dioxus is a *declarative* framework. This means that instead of telling Dioxus what to do (e.g. to \"create an element\" or \"set the color to red\") we simply *declare* how we want the UI to look.\n\nTo declare what you want your UI to look like, you will need to use the `rsx` macro. Let's create a `main` function and an `App` component to show information about our story:\n\n````rust@hackernews_post.rs\nfn main() {\n launch(App);\n}\n\npub fn App() -> Element {\n rsx! {\"story\"}\n}\n````\n\nNow if you run your application you should see something like this:\n\n````inject-dioxus\nDemoFrame {\n\thackernews_post::story_v1::App {}\n}\n````\n\n > \n > RSX mirrors HTML. Because of this you will need to know some html to use Dioxus.\n > \n > Here are some resources to help get you started learning HTML:\n > \n > * [MDN HTML Guide](https://developer.mozilla.org/en-US/docs/Learn/HTML)\n > * [W3 Schools HTML Tutorial](https://www.w3schools.com/html/default.asp)\n > \n > In addition to HTML, Dioxus uses CSS to style applications. You can either use traditional CSS (what this guide uses) or use a tool like [tailwind CSS](https://tailwindcss.com/docs/installation):\n > \n > * [MDN Traditional CSS Guide](https://developer.mozilla.org/en-US/docs/Learn/HTML)\n > * [W3 Schools Traditional CSS Tutorial](https://www.w3schools.com/css/default.asp)\n > * [Tailwind tutorial](https://tailwindcss.com/docs/installation) (used with the [Tailwind setup example](https://github.com/DioxusLabs/dioxus/tree/v0.5/examples/tailwind))\n > \n > If you have existing html code, you can use the [translate](../CLI/translate.md) command to convert it to RSX. Or if you prefer to write html, you can use the [html! macro](https://github.com/DioxusLabs/dioxus-html-macro) to write html directly in your code.\n\n## Dynamic Text\n\nLet's expand our `App` component to include the story title, author, score, time posted, and number of comments. We can insert dynamic text in the render macro by inserting variables inside `{}`s (this works similarly to the formatting in the [println!](https://doc.rust-lang.org/std/macro.println.html) macro):\n\n````rust@hackernews_post.rs\npub fn App() -> Element {\n let title = \"title\";\n let by = \"author\";\n let score = 0;\n let time = chrono::Utc::now();\n let comments = \"comments\";\n\n rsx! {\"{title} by {by} ({score}) {time} {comments}\"}\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\thackernews_post::story_v2::App {}\n}\n````\n\n## Creating Elements\n\nNext, let's wrap our post description in a [`div`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div). You can create HTML elements in Dioxus by putting a `{` after the element name and a `}` after the last child of the element:\n\n````rust@hackernews_post.rs\npub fn App() -> Element {\n let title = \"title\";\n let by = \"author\";\n let score = 0;\n let time = chrono::Utc::now();\n let comments = \"comments\";\n\n rsx! { div { \"{title} by {by} ({score}) {time} {comments}\" } }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\thackernews_post::story_v3::App {}\n}\n````\n\n > \n > You can read more about elements in the [rsx reference](../reference/rsx.md).\n\n## Setting Attributes\n\nNext, let's add some padding around our post listing with an attribute.\n\nAttributes (and [listeners](../reference/event_handlers.md)) modify the behavior or appearance of the element they are attached to. They are specified inside the `{}` brackets before any children, using the `name: value` syntax. You can format the text in the attribute as you would with a text node:\n\n````rust@hackernews_post.rs\npub fn App() -> Element {\n let title = \"title\";\n let by = \"author\";\n let score = 0;\n let time = chrono::Utc::now();\n let comments = \"comments\";\n\n rsx! {\n div { padding: \"0.5rem\", position: \"relative\",\n \"{title} by {by} ({score}) {time} {comments}\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\thackernews_post::story_v4::App {}\n}\n````\n\n > \n > Note: All attributes defined in [`dioxus-html`](https://docs.rs/dioxus-html/latest/dioxus_html/) follow the snake_case naming convention. They transform their `snake_case` names to HTML's `camelCase` attributes.\n\n > \n > Note: Styles can be used directly outside of the `style:` attribute. In the above example, `padding: \"0.5rem\"` is turned into `style=\"padding: 0.5rem\"`.\n\n > \n > You can read more about elements in the [attribute reference](../reference/rsx.md)\n\n## Creating a Component\n\nJust 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, you should break down the functionality of an app in logical parts called components.\n\nA component is a Rust function, named in UpperCamelCase, that takes a props parameter and returns an `Element` describing the UI it wants to render. In fact, our `App` function is a component!\n\nLet's pull our story description into a new component:\n\n````rust@hackernews_post.rs\nfn StoryListing() -> Element {\n let title = \"title\";\n let by = \"author\";\n let score = 0;\n let time = chrono::Utc::now();\n let comments = \"comments\";\n\n rsx! {\n div { padding: \"0.5rem\", position: \"relative\",\n \"{title} by {by} ({score}) {time} {comments}\"\n }\n }\n}\n````\n\nWe can render our component like we would an element by putting `{}`s after the component name. Let's modify our `App` component to render our new StoryListing component:\n\n````rust@hackernews_post.rs\npub fn App() -> Element {\n rsx! { StoryListing {} }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\thackernews_post::story_v5::App {}\n}\n````\n\n > \n > You can read more about elements in the [component reference](../reference/components.md)\n\n## Creating Props\n\nJust like you can pass arguments to a function or attributes to an element, you can pass props to a component that customize its behavior!\n\nWe can define arguments that components can take when they are rendered (called `Props`) by adding the `#[component]` macro before our function definition and adding extra function arguments.\n\nCurrently, our `StoryListing` component always renders the same story. We can modify it to accept a story to render as a prop.\n\nWe will also define what a post is and include information for how to transform our post to and from a different format using [serde](https://serde.rs). This will be used with the hackernews API in a later chapter:\n\n````rust@hackernews_post.rs\nuse chrono::{DateTime, Utc};\nuse serde::{Deserialize, Serialize};\n\n// Define the Hackernews types\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryPageData {\n #[serde(flatten)]\n pub item: StoryItem,\n #[serde(default)]\n pub comments: Vec,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct CommentData {\n pub id: i64,\n /// there will be no by field if the comment was deleted\n #[serde(default)]\n pub by: String,\n #[serde(default)]\n pub text: String,\n #[serde(with = \"chrono::serde::ts_seconds\")]\n pub time: DateTime,\n #[serde(default)]\n pub kids: Vec,\n #[serde(default)]\n pub sub_comments: Vec,\n pub r#type: String,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryItem {\n pub id: i64,\n pub title: String,\n pub url: Option,\n pub text: Option,\n #[serde(default)]\n pub by: String,\n #[serde(default)]\n pub score: i64,\n #[serde(default)]\n pub descendants: i64,\n #[serde(with = \"chrono::serde::ts_seconds\")]\n pub time: DateTime,\n #[serde(default)]\n pub kids: Vec,\n pub r#type: String,\n}\n\n#[component]\nfn StoryListing(story: ReadOnlySignal) -> Element {\n let StoryItem {\n title,\n url,\n by,\n score,\n time,\n kids,\n ..\n } = &*story.read();\n\n let comments = kids.len();\n\n rsx! {\n div { padding: \"0.5rem\", position: \"relative\",\n \"{title} by {by} ({score}) {time} {comments}\"\n }\n }\n}\n````\n\nMake sure to also add [serde](https://serde.rs) as a dependency:\n\n````bash\ncargo add serde --features derive\ncargo add serde_json\n````\n\nWe will also use the [chrono](https://crates.io/crates/chrono) crate to provide utilities for handling time data from the hackernews API:\n\n````bash\ncargo add chrono --features serde\n````\n\nNow, let's modify the `App` component to pass the story to our `StoryListing` component like we would set an attribute on an element:\n\n````rust@hackernews_post.rs\npub fn App() -> Element {\n rsx! {\n StoryListing {\n story: StoryItem {\n id: 0,\n title: \"hello hackernews\".to_string(),\n url: None,\n text: None,\n by: \"Author\".to_string(),\n score: 0,\n descendants: 0,\n time: chrono::Utc::now(),\n kids: vec![],\n r#type: \"\".to_string(),\n }\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\thackernews_post::story_v6::App {}\n}\n````\n\n > \n > You can read more about Props in the [Props reference](../reference/component_props.md)\n\n## Cleaning Up Our Interface\n\nFinally, by combining elements and attributes, we can make our post listing much more appealing:\n\nFull code up to this point:\n\n````rust@hackernews_post.rs\nuse dioxus::prelude::*;\n\n// Define the Hackernews types\nuse chrono::{DateTime, Utc};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryPageData {\n #[serde(flatten)]\n pub item: StoryItem,\n #[serde(default)]\n pub comments: Vec,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct CommentData {\n pub id: i64,\n /// there will be no by field if the comment was deleted\n #[serde(default)]\n pub by: String,\n #[serde(default)]\n pub text: String,\n #[serde(with = \"chrono::serde::ts_seconds\")]\n pub time: DateTime,\n #[serde(default)]\n pub kids: Vec,\n #[serde(default)]\n pub sub_comments: Vec,\n pub r#type: String,\n}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryItem {\n pub id: i64,\n pub title: String,\n pub url: Option,\n pub text: Option,\n #[serde(default)]\n pub by: String,\n #[serde(default)]\n pub score: i64,\n #[serde(default)]\n pub descendants: i64,\n #[serde(with = \"chrono::serde::ts_seconds\")]\n pub time: DateTime,\n #[serde(default)]\n pub kids: Vec,\n pub r#type: String,\n}\n\nfn main() {\n launch(App);\n}\n\npub fn App() -> Element {\n rsx! {\n StoryListing {\n story: StoryItem {\n id: 0,\n title: \"hello hackernews\".to_string(),\n url: None,\n text: None,\n by: \"Author\".to_string(),\n score: 0,\n descendants: 0,\n time: Utc::now(),\n kids: vec![],\n r#type: \"\".to_string(),\n }\n }\n }\n}\n\n#[component]\nfn StoryListing(story: ReadOnlySignal) -> Element {\n let StoryItem {\n title,\n url,\n by,\n score,\n time,\n kids,\n ..\n } = &*story.read();\n\n let url = url.as_deref().unwrap_or_default();\n let hostname = url\n .trim_start_matches(\"https://\")\n .trim_start_matches(\"http://\")\n .trim_start_matches(\"www.\");\n let score = format!(\"{score} {}\", if *score == 1 { \" point\" } else { \" points\" });\n let comments = format!(\n \"{} {}\",\n kids.len(),\n if kids.len() == 1 {\n \" comment\"\n } else {\n \" comments\"\n }\n );\n let time = time.format(\"%D %l:%M %p\");\n\n rsx! {\n div { padding: \"0.5rem\", position: \"relative\",\n div { font_size: \"1.5rem\",\n a { href: url, \"{title}\" }\n a {\n color: \"gray\",\n href: \"https://news.ycombinator.com/from?site={hostname}\",\n text_decoration: \"none\",\n \" ({hostname})\"\n }\n }\n div { display: \"flex\", flex_direction: \"row\", color: \"gray\",\n div { \"{score}\" }\n div { padding_left: \"0.5rem\", \"by {by}\" }\n div { padding_left: \"0.5rem\", \"{time}\" }\n div { padding_left: \"0.5rem\", \"{comments}\" }\n }\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\thackernews_post::story_final::App {}\n}\n````" + } + 21usize => { + "# Choosing a web renderer\n\nDioxus has three different renderers that target the web:\n\n* [dioxus-web](web/index.md) allows you to render your application to HTML with [WebAssembly](https://rustwasm.github.io/docs/book/) on the client\n* [dioxus-liveview](liveview.md) allows you to run your application on the server and render it to HTML on the client with a websocket\n* [dioxus-fullstack](fullstack/index.md) allows you to initially render static HTML on the server and then update that HTML from the client with [WebAssembly](https://rustwasm.github.io/docs/book/)\n\nEach approach has its tradeoffs:\n\n### Dioxus Liveview\n\n* Liveview rendering communicates with the server over a WebSocket connection. It essentially moves all of the work that Client-side rendering does to the server.\n\n* This makes it **easy to communicate with the server, but more difficult to communicate with the client/browser APIS**.\n\n* Each interaction also requires a message to be sent to the server and back which can cause **issues with latency**.\n\n* Because Liveview uses a websocket to render, the page will be blank until the WebSocket connection has been established and the first renderer has been sent from the websocket. Just like with client side rendering, this can make the page **less SEO-friendly**.\n\n* Because the page is rendered on the server and the page is sent to the client piece by piece, you never need to send the entire application to the client. The initial load time can be faster than client-side rendering with large applications because Liveview only needs to send a constant small websocket script regardless of the size of the application.\n\n > \n > Liveview is a good fit for applications that already need to communicate with the server frequently (like real time collaborative apps), but don't need to communicate with as many client/browser APIs.\n\n[![](https://mermaid.ink/img/pako:eNplULFOw0AM_RXLc7Mw3sBQVUIMRYgKdcli5ZzkRHIuPl8QqvrvXJICRXiy3nt-9-6dsRHP6DAZGe8CdUpjNd3VEcpsVT4SK1TVPRxYJ1YHL_yeOdkqWMGF3w4U32Y6nSQmXvknMQYNXW8g7bfk2JPBg0g3MCTmdH1rJhenx2is1FiYri43wJ8or3O2H1Liv0w3hw724kMb2MMzdcUYNziyjhR8-f15Pq3Reh65RldWzy3lwWqs46VIKZscPmODzjTzBvPJ__aFrqUhFZR9MNH92uhS7OULYSF1lw?type=png)](https://mermaid.live/edit#pako:eNplULFOw0AM_RXLc7Mw3sBQVUIMRYgKdcli5ZzkRHIuPl8QqvrvXJICRXiy3nt-9-6dsRHP6DAZGe8CdUpjNd3VEcpsVT4SK1TVPRxYJ1YHL_yeOdkqWMGF3w4U32Y6nSQmXvknMQYNXW8g7bfk2JPBg0g3MCTmdH1rJhenx2is1FiYri43wJ8or3O2H1Liv0w3hw724kMb2MMzdcUYNziyjhR8-f15Pq3Reh65RldWzy3lwWqs46VIKZscPmODzjTzBvPJ__aFrqUhFZR9MNH92uhS7OULYSF1lw)\n\n### Dioxus Web\n\n* With Client side rendering, you send your application to the client, and then the client generates all of the HTML of the page dynamically.\n\n* This means that the page will be blank until the JavaScript bundle has loaded and the application has initialized. This can result in **slower first render times and poor SEO performance**.\n\n > \n > SEO stands for Search Engine Optimization. It refers to the practice of making your website more likely to appear in search engine results. Search engines like Google and Bing use web crawlers to index the content of websites. Most of these crawlers are not able to run JavaScript, so they will not be able to index the content of your page if it is rendered client-side.\n\n* Client-side rendered applications need to use **weakly typed requests to communicate with the server**.\n\n > \n > Client-side rendering is a good starting point for most applications. It is well supported and makes it easy to communicate with the client/browser APIs.\n\n[![](https://mermaid.ink/img/pako:eNpVkDFPwzAQhf-KdXOzMHpgqJAQAwytEIsXK35JLBJfez4Xoar_HSemQtzke9_z2e-u1HMAWcrqFU_Rj-KX7vLgkqm1F_7KENN1j-YIuUCsOeBckLUZmrjx_ezT54rziVNG42-sMBLHSQ0Pd8vH5NU8M48zTAby71sr3CYdkAIEoen37h-y5n3910tSiO81cqIdLZDFx1DDXNerjnTCAke2HgMGX2Z15NKtWn1RPn6nnqxKwY7KKfzFJzv4OVcVISrLa1vQtqfbDzd0ZKY?type=png)](https://mermaid.live/edit#pako:eNpVkDFPwzAQhf-KdXOzMHpgqJAQAwytEIsXK35JLBJfez4Xoar_HSemQtzke9_z2e-u1HMAWcrqFU_Rj-KX7vLgkqm1F_7KENN1j-YIuUCsOeBckLUZmrjx_ezT54rziVNG42-sMBLHSQ0Pd8vH5NU8M48zTAby71sr3CYdkAIEoen37h-y5n3910tSiO81cqIdLZDFx1DDXNerjnTCAke2HgMGX2Z15NKtWn1RPn6nnqxKwY7KKfzFJzv4OVcVISrLa1vQtqfbDzd0ZKY)\n\n### Dioxus Fullstack\n\nFullstack rendering happens in two parts:\n\n1. The page is rendered on the server. This can include fetching any data you need to render the page.\n1. The page is hydrated on the client. (Hydration is taking the HTML page from the server and adding all of the event listeners Dioxus needs on the client). Any updates to the page happen on the client after this point.\n\nBecause the page is initially rendered on the server, the page will be fully rendered when it is sent to the client. This results in a faster first render time and makes the page more SEO-friendly.\n\n* **Fast initial render**\n* **Works well with SEO**\n* **Type safe easy communication with the server**\n* **Access to the client/browser APIs**\n* **Fast interactivity**\n\nFinally, we can use [server functions](../reference/fullstack/server_functions.md) to communicate with the server in a type-safe way.\n\nThis approach uses both the dioxus-web and dioxus-ssr crates. To integrate those two packages Dioxus provides the `dioxus-fullstack` crate.\n\nThere can be more complexity with fullstack applications because your code runs in two different places. Dioxus tries to mitigate this with server functions and other helpers.\n\n[![](https://mermaid.ink/img/pako:eNpdkL1uwzAMhF9F4BwvHTV0KAIUHdohQdFFi2CdbQG2mFCUiyDIu9e2-hOUE3H34UDelVoOIEtZvWIffS9-auYHl8wyT8KfGWKa5tEcITPEmgPOBVkrUMXNPyAFCMJK5BOnjIq8scJI7Ac13N1RH4NX88zcjzAZyJX-8bfIl6QQ32qcv7PuhP-ANe_rpb8KJ9rRBJl8DMt71zXAkQ6Y4Mgua0Dny6iOXLotqC_Kx0tqyaoU7Kicwl8hZDs_5kVFiMryWivbmrt9AacxbGg?type=png)](https://mermaid.live/edit#pako:eNpdkL1uwzAMhF9F4BwvHTV0KAIUHdohQdFFi2CdbQG2mFCUiyDIu9e2-hOUE3H34UDelVoOIEtZvWIffS9-auYHl8wyT8KfGWKa5tEcITPEmgPOBVkrUMXNPyAFCMJK5BOnjIq8scJI7Ac13N1RH4NX88zcjzAZyJX-8bfIl6QQ32qcv7PuhP-ANe_rpb8KJ9rRBJl8DMt71zXAkQ6Y4Mgua0Dny6iOXLotqC_Kx0tqyaoU7Kicwl8hZDs_5kVFiMryWivbmrt9AacxbGg)" + } + 31usize => { + "# Middleware\n\nExtractors allow you to wrap your server function in some code that changes either the request or the response. Dioxus fullstack integrates with [Tower](https://docs.rs/tower/latest/tower/index.html) to allow you to wrap your server functions in middleware.\n\nYou can use the `#[middleware]` attribute to add a layer of middleware to your server function. Let's add a timeout middleware to a server function. This middleware will stop running the server function if it reaches a certain timeout:\n\n````rust@server_function_middleware.rs\n#[server]\n// Add a timeout middleware to the server function that will return an error if the function takes longer than 1 second to execute\n#[middleware(tower_http::timeout::TimeoutLayer::new(std::time::Duration::from_secs(1)))]\npub async fn timeout() -> Result<(), ServerFnError> {\n tokio::time::sleep(std::time::Duration::from_secs(2)).await;\n Ok(())\n}\n````" + } + 39usize => { + "# Redirection Perfection\n\nYou're well on your way to becoming a routing master!\n\nIn this chapter, we will cover creating redirects\n\n## Creating Redirects\n\nA redirect is very simple. When dioxus encounters a redirect while finding out\nwhat components to render, it will redirect the user to the target of the\nredirect.\n\nAs a simple example, let's say you want user to still land on your blog, even\nif they used the path `/myblog` or `/myblog/:name`.\n\nRedirects are special attributes in the router enum that accept a route and a closure\nwith the route parameters. The closure should return a route to redirect to.\n\nLet's add a redirect to our router enum:\n\n````rust@full_example.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(NavBar)]\n #[route(\"/\")]\n Home {},\n #[nest(\"/blog\")]\n #[layout(Blog)]\n #[route(\"/\")]\n BlogList {},\n #[route(\"/post/:name\")]\n BlogPost { name: String },\n #[end_layout]\n #[end_nest]\n #[end_layout]\n #[nest(\"/myblog\")]\n #[redirect(\"/\", || Route::BlogList {})]\n #[redirect(\"/:name\", |name: String| Route::BlogPost { name })]\n #[end_nest]\n #[route(\"/:..route\")]\n PageNotFound {\n route: Vec,\n },\n}\n````\n\nThat's it! Now your users will be redirected to the blog.\n\n### Conclusion\n\nWell done! You've completed the Dioxus Router guide. You've built a small\napplication and learned about the many things you can do with Dioxus Router.\nTo continue your journey, you attempt a challenge listed below, look at the [router examples](https://github.com/DioxusLabs/dioxus/tree/v0.5/packages/router/examples), or\nthe [API reference](https://docs.rs/dioxus-router/).\n\n### Challenges\n\n* Organize your components into separate files for better maintainability.\n* Give your app some style if you haven't already.\n* Build an about page so your visitors know who you are.\n* Add a user system that uses URL parameters.\n* Create a simple admin system to create, delete, and edit blogs.\n* If you want to go to the max, hook up your application to a rest API and database." + } + 29usize => { + "# Communicating with the server\n\n`dioxus-fullstack` provides server functions that allow you to call an automatically generated API on the server from the client as if it were a local function.\n\nTo make a server function, simply add the `#[server(YourUniqueType)]` attribute to a function. The function must:\n\n* Be an async function\n* Have arguments and a return type that both implement serialize and deserialize (with [serde](https://serde.rs/)).\n* Return a `Result` with an error type of ServerFnError\n\n > \n > If you are targeting WASM on the server with WASI, you must call `register` on the type you passed into the server macro in your main function before starting your server to tell Dioxus about the server function. For all other targets, the server function will be registered automatically.\n\nLet's continue building on the app we made in the [getting started](../../getting_started/index.md) guide. We will add a server function to our app that allows us to double the count on the server.\n\nFirst, add serde as a dependency:\n\n````shell\ncargo add serde\n````\n\nNext, add the server function to your `main.rs`:\n\n````rust@server_function.rs\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\n\nfn main() {\n launch(App)\n}\n\nfn App() -> Element {\n let mut count = use_signal(|| 0);\n\n rsx! {\n h1 { \"High-Five counter: {count}\" }\n button { onclick: move |_| count += 1, \"Up high!\" }\n button { onclick: move |_| count -= 1, \"Down low!\" }\n button {\n onclick: move |_| {\n async move {\n if let Ok(new_count) = double_server(count()).await {\n count.set(new_count);\n }\n }\n },\n \"Double\"\n }\n }\n}\n\n#[server]\nasync fn double_server(number: i32) -> Result {\n // Perform some expensive computation or access a database on the server\n tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n let result = number * 2;\n println!(\"server calculated {result}\");\n Ok(result)\n}\n\n````\n\nNow, build your client-side bundle with `dx build --features web` and run your server with `cargo run --features ssr`. You should see a new button that multiplies the count by 2.\n\n## Cached data fetching\n\nOne common use case for server functions is fetching data from the server:\n\n````rust@server_data_fetch.rs\n#![allow(non_snake_case, unused)]\n\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app)\n}\n\nfn app() -> Element {\n let mut count = use_resource(get_server_data);\n\n rsx! {\"server data is {count.value():?}\"}\n}\n\n#[server]\nasync fn get_server_data() -> Result {\n // Access a database\n tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n Ok(\"Hello from the server!\".to_string())\n}\n\n````\n\nIf you navigate to the site above, you will first see `server data is None`, then after the `WASM` has loaded and the request to the server has finished, you will see `server data is Some(Ok(\"Hello from the server!\"))`.\n\nThis approach works, but it can be slow. Instead of waiting for the client to load and send a request to the server, what if we could get all of the data we needed for the page on the server and send it down to the client with the initial HTML page?\n\nThis is exactly what the `use_server_future` hook allows us to do! `use_server_future` is similar to the `use_resource` hook, but it allows you to wait for a future on the server and send the result of the future down to the client.\n\nLet's change our data fetching to use `use_server_future`:\n\n````rust@server_data_prefetch.rs\n#![allow(non_snake_case, unused)]\n\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app);\n}\n\nfn app() -> Element {\n let mut count = use_server_future(get_server_data)?;\n\n rsx! {\"server data is {count.value():?}\"}\n}\n\n#[server]\nasync fn get_server_data() -> Result {\n // Access a database\n tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n Ok(\"Hello from the server!\".to_string())\n}\n\n````\n\n > \n > Notice the `?` after `use_server_future`. This is what tells Dioxus fullstack to wait for the future to resolve before continuing rendering. If you want to not wait for a specific future, you can just remove the ? and deal with the `Option` manually.\n\nNow when you load the page, you should see `server data is Ok(\"Hello from the server!\")`. No need to wait for the `WASM` to load or wait for the request to finish!\n\n````inject-dioxus\nSandBoxFrame {\n\turl: \"https://codesandbox.io/p/sandbox/dioxus-fullstack-server-future-qwpp4p?file=/src/main.rs:3,24\"\n}\n````\n\n## Running the client with dioxus-desktop\n\nThe project presented so far makes a web browser interact with the server, but it is also possible to make a desktop program interact with the server in a similar fashion. (The full example code is available in the [Dioxus repo](https://github.com/DioxusLabs/dioxus/tree/v0.5/packages/fullstack/examples/axum-desktop))\n\nFirst, we need to make two binary targets, one for the desktop program (the `client.rs` file), one for the server (the `server.rs` file). The client app and the server functions are written in a shared `lib.rs` file.\n\nThe desktop and server targets have slightly different build configuration to enable additional dependencies or features.\nThe Cargo.toml in the full example has more information, but the main points are:\n\n* the client.rs has to be run with the `desktop` feature, so that the optional `dioxus-desktop` dependency is included\n* the server.rs has to be run with the `ssr` features; this will generate the server part of the server functions and will run our backend server.\n\nOnce you create your project, you can run the server executable with:\n\n````bash\ncargo run --bin server --features ssr\n````\n\nand the client desktop executable with:\n\n````bash\ncargo run --bin client --features desktop\n````\n\n### Client code\n\nThe client file is pretty straightforward. You only need to set the server url in the client code, so it knows where to send the network requests. Then, dioxus_desktop launches the app.\n\nFor development, the example project runs the server on `localhost:8080`. **Before you release remember to update the url to your production url.**\n\n### Server code\n\nIn the server code, first you have to set the network address and port where the server will listen to.\n\n````rust@server_function_desktop_client.rs\nlet listener = tokio::net::TcpListener::bind(\"127.0.0.1:3000\")\n .await\n .unwrap();\nprintln!(\"listening on http://127.0.0.1:3000\");\n````\n\nThen, you have to register the types declared in the server function macros into the server.\nFor example, consider this server function:\n\n````rust@server_function_desktop_client.rs\n#[server(GetServerData)]\nasync fn get_server_data() -> Result {\n Ok(\"Hello from the server!\".to_string())\n}\n````" + } + 24usize => { + "# Mobile\n\nThis guide will cover concepts specific to the Dioxus mobile renderer.\n\n## Running Javascript\n\nDioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose.\n\nFor these cases, Dioxus desktop exposes the use_eval hook that allows you to run raw Javascript in the webview:\n\n````rust@eval.rs\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app);\n}\n\nfn app() -> Element {\n // You can create as many eval instances as you want\n let mut eval = eval(\n r#\"\n // You can send messages from JavaScript to Rust with the dioxus.send function\n dioxus.send(\"Hi from JS!\");\n // You can receive messages from Rust to JavaScript with the dioxus.recv function\n let msg = await dioxus.recv();\n console.log(msg);\n \"#,\n );\n\n // You can send messages to JavaScript with the send method\n eval.send(\"Hi from Rust!\".into()).unwrap();\n\n let future = use_resource(move || {\n to_owned![eval];\n async move {\n // You can receive any message from JavaScript with the recv method\n eval.recv().await.unwrap()\n }\n });\n\n match future.read_unchecked().as_ref() {\n Some(v) => rsx! {\n p { \"{v}\" }\n },\n _ => rsx! {\n p { \"hello\" }\n },\n }\n}\n\n````\n\n## Custom Assets\n\nYou can link to local assets in dioxus mobile instead of using a url:\n\n````rust@custom_assets.rs\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app);\n}\n\nfn app() -> Element {\n rsx! {\n div {\n img { src: \"/public/static/scanner.png\" }\n }\n }\n}\n\n````\n\n## Integrating with Wry\n\nIn cases where you need more low level control over your window, you can use wry APIs exposed through the [Desktop Config](https://docs.rs/dioxus-desktop/0.5.0/dioxus_desktop/struct.Config.html) and the [use_window hook](https://docs.rs/dioxus-desktop/0.5.0/dioxus_desktop/struct.DesktopContext.html)" + } + 10usize => { + "# Component Props\n\nJust like you can pass arguments to a function or attributes to an element, 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.\n\n## derive(Props)\n\nComponent props are a single struct annotated with `#[derive(PartialEq, Clone, Props)]`. For a component to accept props, the type of its argument must be `YourPropsStruct`.\n\nExample:\n\n````rust, no_run@component_owned_props.rs\n#[derive(PartialEq, Props, Clone)]\nstruct LikesProps {\n score: i32,\n}\n\nfn Likes(props: LikesProps) -> Element {\n rsx! {\n div {\n \"This post has \"\n b { \"{props.score}\" }\n \" likes\"\n }\n }\n}\n````\n\nYou can then pass prop values to the component the same way you would pass attributes to an element:\n\n````rust, no_run@component_owned_props.rs\npub fn App() -> Element {\n rsx! { Likes { score: 42 } }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n component_owned_props::App {}\n}\n````\n\n## Prop Options\n\nThe `#[derive(Props)]` macro has some features that let you customize the behavior of props.\n\n### Optional Props\n\nYou can create optional fields by using the `Option<…>` type for a field:\n\n````rust, no_run@component_props_options.rs\n#[derive(PartialEq, Clone, Props)]\nstruct OptionalProps {\n title: String,\n subtitle: Option,\n}\n\nfn Title(props: OptionalProps) -> Element {\n rsx! {\n h1 { \"{props.title}: \", {props.subtitle.unwrap_or_else(|| \"No subtitle provided\".to_string())} }\n }\n}\n````\n\nThen, you can choose to either provide them or not:\n\n````rust, no_run@component_props_options.rs\nTitle { title: \"Some Title\" }\nTitle { title: \"Some Title\", subtitle: \"Some Subtitle\" }\n// Providing an Option explicitly won't compile though:\n// Title {\n// title: \"Some Title\",\n// subtitle: None,\n// },\n````\n\n### Explicitly Required Option\n\nIf you want to explicitly require an `Option`, and not an optional prop, you can annotate it with `#[props(!optional)]`:\n\n````rust, no_run@component_props_options.rs\n#[derive(PartialEq, Clone, Props)]\nstruct ExplicitOptionProps {\n title: String,\n #[props(!optional)]\n subtitle: Option,\n}\n\nfn ExplicitOption(props: ExplicitOptionProps) -> Element {\n rsx! {\n h1 { \"{props.title}: \", {props.subtitle.unwrap_or_else(|| \"No subtitle provided\".to_string())} }\n }\n}\n````\n\nThen, you have to explicitly pass either `Some(\"str\")` or `None`:\n\n````rust, no_run@component_props_options.rs\nExplicitOption { title: \"Some Title\", subtitle: None }\nExplicitOption { title: \"Some Title\", subtitle: Some(\"Some Title\".to_string()) }\n// This won't compile:\n// ExplicitOption {\n// title: \"Some Title\",\n// },\n````\n\n### Default Props\n\nYou can use `#[props(default = 42)]` to make a field optional and specify its default value:\n\n````rust, no_run@component_props_options.rs\n#[derive(PartialEq, Props, Clone)]\nstruct DefaultProps {\n // default to 42 when not provided\n #[props(default = 42)]\n number: i64,\n}\n\nfn DefaultComponent(props: DefaultProps) -> Element {\n rsx! { h1 { \"{props.number}\" } }\n}\n````\n\nThen, similarly to optional props, you don't have to provide it:\n\n````rust, no_run@component_props_options.rs\nDefaultComponent { number: 5 }\nDefaultComponent {}\n````\n\n### Automatic Conversion with into\n\nIt 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`:\n\n````rust, no_run@component_props_options.rs\n#[derive(PartialEq, Props, Clone)]\nstruct IntoProps {\n #[props(into)]\n string: String,\n}\n\nfn IntoComponent(props: IntoProps) -> Element {\n rsx! { h1 { \"{props.string}\" } }\n}\n````\n\nThen, you can use it so:\n\n````rust, no_run@component_props_options.rs\nIntoComponent { string: \"some &str\" }\n````\n\n## The component macro\n\nSo 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 `props.whatever`, we could just use `whatever` directly!\n\n`component` allows you to do just that. Instead of typing the \"full\" version:\n\n````rust, no_run\n#[derive(Props, Clone, PartialEq)]\nstruct TitleCardProps {\n title: String,\n}\n\nfn TitleCard(props: TitleCardProps) -> Element {\n rsx!{\n h1 { \"{props.title}\" }\n }\n}\n````\n\n...you can define a function that accepts props as arguments. Then, just annotate it with `#[component]`, and the macro will turn it into a regular Component for you:\n\n````rust, no_run\n#[component]\nfn TitleCard(title: String) -> Element {\n rsx!{\n h1 { \"{title}\" }\n }\n}\n````\n\n > \n > 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.\n\n## Component Children\n\nIn 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`:\n\n````rust, no_run@component_element_props.rs\n#[derive(PartialEq, Clone, Props)]\nstruct ClickableProps {\n href: String,\n body: Element,\n}\n\nfn Clickable(props: ClickableProps) -> Element {\n rsx! {\n a { href: \"{props.href}\", class: \"fancy-button\", {props.body} }\n }\n}\n````\n\nThen, when rendering the component, you can pass in the output of `rsx!{...}`:\n\n````rust, no_run@component_element_props.rs\nrsx! {\n Clickable {\n href: \"https://www.youtube.com/watch?v=C-M2hs3sXGo\",\n body: rsx! {\n \"How to \" i { \"not\" } \" be seen\"\n }\n }\n}\n````\n\n > \n > Warning: While it may compile, do not include the same `Element` more than once in the RSX. The resulting behavior is unspecified.\n\n### The children field\n\nRather 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:\n\n````rust, no_run@component_children.rs\n#[derive(PartialEq, Clone, Props)]\nstruct ClickableProps {\n href: String,\n children: Element,\n}\n\nfn Clickable(props: ClickableProps) -> Element {\n rsx! {\n a { href: \"{props.href}\", class: \"fancy-button\", {props.children} }\n }\n}\n````\n\nThis 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!\n\n````rust, no_run@component_children.rs\nrsx! {\n Clickable { href: \"https://www.youtube.com/watch?v=C-M2hs3sXGo\",\n \"How to \"\n i { \"not\" }\n \" be seen\"\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n component_children::App {}\n}\n````" + } + 51usize => { + "# Publishing\n\nAfter you have build your application, you will need to publish it somewhere. This reference will outline different methods of publishing your desktop or web application.\n\n## Web: Publishing with GitHub Pages\n\nEdit your `Dioxus.toml` to point your `out_dir` to the `docs` folder and the `base_path` to the name of your repo:\n\n````toml\n[application]\n# ...\nout_dir = \"docs\"\n\n[web.app]\nbase_path = \"your_repo\"\n````\n\nThen build your app and publish it to Github:\n\n* Make sure GitHub Pages is set up for your repo to publish any static files in the docs directory\n* Build your app with:\n\n````sh\ndx build --release\n````\n\n* Make a copy of your `docs/index.html` file and rename the copy to `docs/404.html` so that your app will work with client-side routing\n* Add and commit with git\n* Push to GitHub\n\n## Desktop: Creating an installer\n\nDioxus desktop app uses your operating system's WebView library, so it's portable to be distributed for other platforms.\n\nIn this section, we'll cover how to bundle your app for macOS, Windows, and Linux.\n\n## Preparing your application for bundling\n\nDepending on your platform, you may need to add some additional code to your `main.rs` file to make sure your app is ready for bundling. On Windows, you'll need to add the `#![windows_subsystem = \"windows\"]` attribute to your `main.rs` file to hide the terminal window that pops up when you run your app. **If you're developing on Windows, only use this when bundling.** It will disable the terminal, so you will not get logs of any kind. You can gate it behind a feature, like so:\n\n````toml\n# Cargo.toml\n[features]\nbundle = []\n````\n\nAnd then your `main.rs`:\n\n````rust\n#![cfg_attr(feature = \"bundle\", windows_subsystem = \"windows\")]\n````\n\n## Adding assets to your application\n\nIf you want to bundle assets with your application, you can either use them with the `manganis` crate (covered more in the [assets](../reference/assets.md) page), or you can include them in your `Dioxus.toml` file:\n\n````toml\n[bundle]\n# The list of files to include in the bundle. These can contain globs.\nresources = [\"main.css\", \"header.svg\", \"**/*.png\"]\n````\n\n## Install `dioxus CLI`\n\nThe first thing we'll do is install the [dioxus-cli](https://github.com/DioxusLabs/dioxus/tree/v0.5/packages/cli). This extension to cargo will make it very easy to package our app for the various platforms.\n\nTo install, simply run\n\n`cargo install dioxus-cli`\n\n## Building\n\nTo bundle your application you can simply run `dx bundle --release` (also add `--features bundle` if you're using that, see the [this](#preparing-your-application-for-bundling) for more) to produce a final app with all the optimizations and assets builtin.\n\nOnce you've ran the command, your app should be accessible in `dist/bundle/`.\n\nFor example, a macOS app would look like this:\n\n![Published App](/assets/static/publish.png)\n\nNice! 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.\n\n > \n > 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." + } + 52usize => { + "# Antipatterns\n\nThis example shows what not to do and provides a reason why a given pattern is considered an \"AntiPattern\". Most anti-patterns are considered wrong for performance or code re-usability reasons.\n\n## Unnecessarily Nested Fragments\n\nFragments 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.\n\nOnly 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.\n\n````rust@anti_patterns.rs\n// ❌ Don't unnecessarily nest fragments\nlet _ = rsx! {\n Fragment {\n Fragment {\n Fragment {\n Fragment {\n Fragment { div { \"Finally have a real node!\" } }\n }\n }\n }\n }\n};\n\n// ✅ Render shallow structures\nrsx! { div { \"Finally have a real node!\" } }\n````\n\n## Incorrect Iterator Keys\n\nAs described in the [dynamic rendering chapter](../reference/dynamic_rendering#the-key-attribute), 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 will never change.\n\n````rust@anti_patterns.rs\nlet data: &HashMap<_, _> = &props.data;\n\n// ❌ No keys\nrsx! {\n ul {\n for value in data.values() {\n li { \"List item: {value}\" }\n }\n }\n};\n\n// ❌ Using index as keys\nrsx! {\n ul {\n for (index , value) in data.values().enumerate() {\n li { key: \"{index}\", \"List item: {value}\" }\n }\n }\n};\n\n// ✅ Using unique IDs as keys:\nrsx! {\n ul {\n for (key , value) in props.data.iter() {\n li { key: \"{key}\", \"List item: {value}\" }\n }\n }\n}\n````\n\n## Avoid Interior Mutability in Props\n\nWhile it is technically acceptable to have a `Mutex` or a `RwLock` in the props, they will be difficult to use.\n\nSuppose 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 write to the `username` field. However, when it does, the parent component will not be aware of the change, and the component will not re-render which causes the UI to be out of sync with the state. Instead, consider passing down a reactive value like a `Signal` or immutable data.\n\n````rust@anti_patterns.rs\n// ❌ Mutex/RwLock/RefCell in props\n#[derive(Props, Clone)]\nstruct AntipatternInteriorMutability {\n map: Rc>>,\n}\n\nimpl PartialEq for AntipatternInteriorMutability {\n fn eq(&self, other: &Self) -> bool {\n std::rc::Rc::ptr_eq(&self.map, &other.map)\n }\n}\n\nfn AntipatternInteriorMutability(map: Rc>>) -> Element {\n rsx! {\n button {\n onclick: {\n let map = map.clone();\n move |_| {\n // Writing to map will not rerun any components\n map.borrow_mut().insert(0, \"Hello\".to_string());\n }\n },\n \"Mutate map\"\n }\n // Since writing to map will not rerun any components, this will get out of date\n \"{map.borrow().get(&0).unwrap()}\"\n }\n}\n\n// ✅ Use a signal to pass mutable state\n#[component]\nfn AntipatternInteriorMutabilitySignal(map: Signal>) -> Element {\n rsx! {\n button {\n onclick: move |_| {\n // Writing to map will rerun any components that read the map\n map.write().insert(0, \"Hello\".to_string());\n },\n \"Mutate map\"\n }\n // Since writing to map will rerun subscribers, this will get updated\n \"{map.read().get(&0).unwrap()}\"\n }\n}\n````\n\n## Avoid Updating State During Render\n\nEvery time you update the state, Dioxus needs to re-render the component – this is inefficient! Consider refactoring your code to avoid this.\n\nAlso, if you unconditionally update the state during render, it will be re-rendered in an infinite loop.\n\n````rust@anti_patterns.rs\n// ❌ Updating state in render\nlet first_signal = use_signal(|| 0);\nlet mut second_signal = use_signal(|| 0);\n\n// Updating the state during a render can easily lead to infinite loops\nif first_signal() + 1 != second_signal() {\n second_signal.set(first_signal() + 1);\n}\n\n// ✅ Update state in an effect\nlet first_signal = use_signal(|| 0);\nlet mut second_signal = use_signal(|| 0);\n\n// The closure you pass to use_effect will be rerun whenever any of the dependencies change without re-rendering the component\nuse_effect(move || {\n if first_signal() + 1 != second_signal() {\n second_signal.set(first_signal() + 1);\n }\n});\n\n// ✅ Deriving state with use_memo\nlet first_signal = use_signal(|| 0);\n// Memos are specifically designed for derived state. If your state fits this pattern, use it.\nlet second_signal = use_memo(move || first_signal() + 1);\n````\n\n## Avoid Large Groups of State\n\nIt can be tempting to have a single large state struct that contains all of your application's state. However, this can lead to issues:\n\n* It can be easy to accidentally mutate the state in a way that causes an infinite loop\n* It can be difficult to reason about when and how the state is updated\n* It can lead to performance issues because many components will need to re-render when the state changes\n\nInstead, consider breaking your state into smaller, more manageable pieces. This will make it easier to reason about the state, avoid update loops, and improve performance.\n\n````rust@anti_patterns.rs\nfn app() -> Element {\n // ❌ Large state struct\n #[derive(Props, Clone, PartialEq)]\n struct LargeState {\n users: Vec,\n logged_in: bool,\n warnings: Vec,\n }\n\n #[derive(Props, Clone, PartialEq)]\n struct User {\n name: String,\n email: String,\n }\n\n let mut all_my_state = use_signal(|| LargeState {\n users: vec![User {\n name: \"Alice\".to_string(),\n email: \"alice@example.com\".to_string(),\n }],\n logged_in: true,\n warnings: vec![],\n });\n\n use_effect(move || {\n // It is very easy to accidentally read and write to the state object if it contains all your state\n let read = all_my_state.read();\n let logged_in = read.logged_in;\n if !logged_in {\n all_my_state\n .write_unchecked()\n .warnings\n .push(\"You are not logged in\".to_string());\n }\n });\n\n // ✅ Use multiple signals to manage state\n let users = use_signal(|| {\n vec![User {\n name: \"Alice\".to_string(),\n email: \"alice@example.com\".to_string(),\n }]\n });\n let logged_in = use_signal(|| true);\n let mut warnings = use_signal(|| vec![]);\n\n use_effect(move || {\n // Now you can read and write to separate signals which will not cause issues\n if !logged_in() {\n warnings.write().push(\"You are not logged in\".to_string());\n }\n });\n\n // ✅ Use memos to create derived state when larger states are unavoidable\n // Notice we didn't split everything into separate signals. Users still make sense as a vec of data\n let users = use_signal(|| {\n vec![User {\n name: \"Alice\".to_string(),\n email: \"alice@example.com\".to_string(),\n }]\n });\n let logged_in = use_signal(|| true);\n let warnings: Signal> = use_signal(|| vec![]);\n\n // In child components, you can use the memo to create derived that will only update when a specific part of the state changes\n // This will help you avoid unnecessary re-renders and infinite loops\n #[component]\n fn FirstUser(users: Signal>) -> Element {\n let first_user = use_memo(move || users.read().first().unwrap().clone());\n\n rsx! {\n div {\n \"First user: {first_user().name}\"\n }\n }\n }\n\n rsx! {\n FirstUser {\n users\n }\n }\n}\n````\n\n## Running Non-Deterministic Code in the Body of a Component\n\nIf you have a component that contains non-deterministic code, that code should generally not be run in the body of the component. If it is put in the body of the component, it will be executed every time the component is re-rendered which can lead to performance issues.\n\nInstead, consider moving the non-deterministic code into a hook that only runs when the component is first created or an effect that reruns when dependencies change.\n\n````rust@anti_patterns.rs\n// ❌ Non-deterministic code in the body of a component\n#[component]\nfn NonDeterministic(name: String) -> Element {\n let my_random_id = rand::random::();\n\n rsx! {\n div {\n // Id will change every single time the component is re-rendered\n id: \"{my_random_id}\",\n \"Hello {name}\"\n }\n }\n}\n\n// ✅ Use a hook to run non-deterministic code\nfn NonDeterministicHook(name: String) -> Element {\n // If you store the result of the non-deterministic code in a hook, it will stay the same between renders\n let my_random_id = use_hook(|| rand::random::());\n\n rsx! {\n div {\n id: \"{my_random_id}\",\n \"Hello {name}\"\n }\n }\n}\n````\n\n## Overly Permissive PartialEq for Props\n\nYou may have noticed that `Props` requires a `PartialEq` implementation. That `PartialEq` is very important for Dioxus to work correctly. It is used to determine if a component should re-render or not when the parent component re-renders.\n\nIf you cannot derive `PartialEq` for your `Props`, you will need to implement it yourself. If you do implement `PartialEq`, make sure to return `false` any time the props change in a way that would cause the UI in the child component to change.\n\nIn general, returning `false` from `PartialEq` if you aren't sure if the props have changed or not is better than returning `true`. This will help you avoid out of date UI in your child components.\n\n````rust@anti_patterns.rs\n// ❌ Permissive PartialEq for Props\n#[derive(Props, Clone)]\nstruct PermissivePartialEqProps {\n name: String,\n}\n\n// This will cause the component to **never** re-render when the parent component re-renders\nimpl PartialEq for PermissivePartialEqProps {\n fn eq(&self, _: &Self) -> bool {\n true\n }\n}\n\nfn PermissivePartialEq(name: PermissivePartialEqProps) -> Element {\n rsx! {\n div {\n \"Hello {name.name}\"\n }\n }\n}\n\n#[component]\nfn PermissivePartialEqParent() -> Element {\n let name = use_signal(|| \"Alice\".to_string());\n\n rsx! {\n PermissivePartialEq {\n // The PermissivePartialEq component will not get the updated value of name because the PartialEq implementation says that the props are the same\n name: name()\n }\n }\n}\n\n// ✅ Derive PartialEq for Props\n#[derive(Props, Clone, PartialEq)]\nstruct DerivePartialEqProps {\n name: String,\n}\n\nfn DerivePartialEq(name: DerivePartialEqProps) -> Element {\n rsx! {\n div {\n \"Hello {name.name}\"\n }\n }\n}\n\n#[component]\nfn DerivePartialEqParent() -> Element {\n let name = use_signal(|| \"Alice\".to_string());\n\n rsx! {\n DerivePartialEq {\n name: name()\n }\n }\n}\n\n// ✅ Return false from PartialEq if you are unsure if the props have changed\n#[derive(Debug)]\nstruct NonPartialEq;\n\n#[derive(Props, Clone)]\nstruct RcPartialEqProps {\n name: Rc,\n}\n\nimpl PartialEq for RcPartialEqProps {\n fn eq(&self, other: &Self) -> bool {\n // This will almost always return false because the Rc will likely point to a different value\n // Implementing PartialEq for NonPartialEq would be better, but if it is controlled by another library, it may not be possible\n // **Always** return false if you are unsure if the props have changed\n std::rc::Rc::ptr_eq(&self.name, &other.name)\n }\n}\n\nfn RcPartialEq(name: RcPartialEqProps) -> Element {\n rsx! {\n div {\n \"Hello {name.name:?}\"\n }\n }\n}\n\nfn RcPartialEqParent() -> Element {\n let name = use_signal(|| Rc::new(NonPartialEq));\n\n rsx! {\n RcPartialEq {\n // Generally, RcPartialEq will rerun even if the value of name hasn't actually changed because the Rc will point to a different value\n name: name()\n }\n }\n}\n````" + } + 15usize => { + "# Dynamic Rendering\n\nSometimes 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!\n\n## Conditional Rendering\n\nTo render different elements based on a condition, you could use an `if-else` statement:\n\n````rust, no_run@conditional_rendering.rs\nif is_logged_in {\n rsx! {\n \"Welcome!\"\n button { onclick: move |_| log_out.call(()), \"Log Out\" }\n }\n} else {\n rsx! {\n button { onclick: move |_| log_in.call(()), \"Log In\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n conditional_rendering::App {}\n}\n````\n\n > \n > You could also use `match` statements, or any Rust function to conditionally render different things.\n\n### Improving the `if-else` Example\n\nYou may have noticed some repeated code in the `if-else` example above. Repeating code like this is both bad for maintainability and performance. Dioxus will skip diffing static elements like the button, but when switching between multiple `rsx` calls it cannot perform this optimization. For this example either approach is fine, but for components with large parts that are reused between conditionals, it can be more of an issue.\n\nWe can improve this example by splitting up the dynamic parts and inserting them where they are needed.\n\n````rust, no_run@conditional_rendering.rs\nrsx! {\n // We only render the welcome message if we are logged in\n // You can use if statements in the middle of a render block to conditionally render elements\n if is_logged_in {\n // Notice the body of this if statement is rsx code, not an expression\n \"Welcome!\"\n }\n button {\n // depending on the value of `is_logged_in`, we will call a different event handler\n onclick: move |_| if is_logged_in { log_out.call(()) } else { log_in.call(()) },\n if is_logged_in {\n // if we are logged in, the button should say \"Log Out\"\n \"Log Out\"\n } else {\n // if we are not logged in, the button should say \"Log In\"\n \"Log In\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n conditional_rendering::LogInImprovedApp {}\n}\n````\n\n### Inspecting `Element` props\n\nSince `Element` is a `Option`, components accepting `Element` as a prop can inspect its contents, and render different things based on that. Example:\n\n````rust, no_run@component_children_inspect.rs\nfn Clickable(props: ClickableProps) -> Element {\n match props.children {\n Some(VNode { .. }) => {\n todo!(\"render some stuff\")\n }\n _ => {\n todo!(\"render some other stuff\")\n }\n }\n}\n````\n\nYou 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.\n\n## Rendering Nothing\n\nTo render nothing, you can return `None` from a component. This is useful if you want to conditionally hide something:\n\n````rust, no_run@conditional_rendering.rs\nif is_logged_in {\n return rsx!();\n}\n\nrsx! {\n p { \"You must be logged in to comment\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n conditional_rendering::LogInWarningApp {}\n}\n````\n\nThis works because the `Element` type is just an alias for `Option`\n\n > \n > 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.\n\n## Rendering Lists\n\nOften, you'll want to render a collection of components. For example, you might want to render a list of all comments on a post.\n\nFor this, Dioxus accepts iterators that produce `Element`s. So we need to:\n\n* Get an iterator over all of our items (e.g., if you have a `Vec` of comments, iterate over it with `iter()`)\n* `.map` the iterator to convert each item into a `LazyNode` using `rsx!{...}`\n * Add a unique `key` attribute to each iterator item\n* Include this iterator in the final RSX (or use it inline)\n\nExample: suppose you have a list of comments you want to render. Then, you can render them like this:\n\n````rust, no_run@rendering_lists.rs\nlet mut comment_field = use_signal(String::new);\nlet mut next_id = use_signal(|| 0);\nlet mut comments = use_signal(Vec::::new);\n\nlet comments_lock = comments.read();\nlet comments_rendered = comments_lock.iter().map(|comment| {\n rsx! { Comment { comment: comment.clone() } }\n});\n\nrsx! {\n form {\n onsubmit: move |_| {\n comments\n .write()\n .push(CommentData {\n content: comment_field(),\n id: next_id(),\n });\n next_id += 1;\n comment_field.set(String::new());\n },\n input {\n value: \"{comment_field}\",\n oninput: move |event| comment_field.set(event.value())\n }\n input { r#type: \"submit\" }\n }\n {comments_rendered}\n}\n````\n\n````inject-dioxus\nDemoFrame {\n rendering_lists::App {}\n}\n````\n\n### Inline for loops\n\nBecause of how common it is to render a list of items, Dioxus provides a shorthand for this. Instead of using `.iter`, `.map`, and `rsx`, you can use a `for` loop with a body of rsx code:\n\n````rust, no_run@rendering_lists.rs\nlet mut comment_field = use_signal(String::new);\nlet mut next_id = use_signal(|| 0);\nlet mut comments = use_signal(Vec::::new);\n\nrsx! {\n form {\n onsubmit: move |_| {\n comments\n .write()\n .push(CommentData {\n content: comment_field(),\n id: next_id(),\n });\n next_id += 1;\n comment_field.set(String::new());\n },\n input {\n value: \"{comment_field}\",\n oninput: move |event| comment_field.set(event.value())\n }\n input { r#type: \"submit\" }\n }\n for comment in comments() {\n // Notice the body of this for loop is rsx code, not an expression\n Comment { comment }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n rendering_lists::AppForLoop {}\n}\n````\n\n### The key Attribute\n\nEvery time you re-render your list, Dioxus needs to keep track of which items go where to determine what updates need to be made to the UI.\n\nFor 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!\n\nTo 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 matter where you get the key from, as long as it meets the requirements:\n\n* Keys must be unique in a list\n* The same item should always get associated with the same key\n* 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\n\nYou might be tempted to use an item's index in the list as its key. 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.\n\n > \n > 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." + } + 22usize => { + "# Desktop\n\nThis guide will cover concepts specific to the Dioxus desktop renderer.\n\nApps built with Dioxus desktop use the system WebView to render the page. This makes the final size of application much smaller than other WebView renderers (typically under 5MB).\n\nAlthough desktop apps are rendered in a WebView, your Rust code runs natively. 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 easily accessible though system APIs.\n\nDioxus desktop is built off [Tauri](https://tauri.app/). Right now there are limited Dioxus abstractions over the menubar, event handling, etc. In some places you may need to leverage Tauri directly – through [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao).\n\n > \n > In the future, we plan to move to a custom web renderer-based DOM renderer with WGPU integrations ([Blitz](https://github.com/DioxusLabs/blitz)).\n\n## Examples\n\n* [File Explorer](https://github.com/DioxusLabs/dioxus/blob/main/example-projects/file-explorer)\n* [Tailwind App](https://github.com/DioxusLabs/dioxus/tree/v0.5/examples/tailwind)\n\n[![Tailwind App screenshot](/assets/static/tailwind_desktop_app.png)](https://github.com/DioxusLabs/dioxus/tree/v0.5/examples/tailwind)\n\n## Running Javascript\n\nDioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose.\n\nFor these cases, Dioxus desktop exposes the use_eval hook that allows you to run raw Javascript in the webview:\n\n````rust@eval.rs\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app);\n}\n\nfn app() -> Element {\n // You can create as many eval instances as you want\n let mut eval = eval(\n r#\"\n // You can send messages from JavaScript to Rust with the dioxus.send function\n dioxus.send(\"Hi from JS!\");\n // You can receive messages from Rust to JavaScript with the dioxus.recv function\n let msg = await dioxus.recv();\n console.log(msg);\n \"#,\n );\n\n // You can send messages to JavaScript with the send method\n eval.send(\"Hi from Rust!\".into()).unwrap();\n\n let future = use_resource(move || {\n to_owned![eval];\n async move {\n // You can receive any message from JavaScript with the recv method\n eval.recv().await.unwrap()\n }\n });\n\n match future.read_unchecked().as_ref() {\n Some(v) => rsx! {\n p { \"{v}\" }\n },\n _ => rsx! {\n p { \"hello\" }\n },\n }\n}\n\n````\n\n## Custom Assets\n\nYou can link to local assets in dioxus desktop instead of using a url:\n\n````rust@custom_assets.rs\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app);\n}\n\nfn app() -> Element {\n rsx! {\n div {\n img { src: \"/public/static/scanner.png\" }\n }\n }\n}\n\n````\n\nYou can read more about assets in the [assets](../assets.md) reference.\n\n## Integrating with Wry\n\nIn cases where you need more low level control over your window, you can use wry APIs exposed through the [Desktop Config](https://docs.rs/dioxus-desktop/0.5.0/dioxus_desktop/struct.Config.html) and the [use_window hook](https://docs.rs/dioxus-desktop/0.5.0/dioxus_desktop/fn.use_window.html)" + } + 32usize => { + "# Authentication\n\nYou can use [extractors](./extractors) to integrate auth with your Fullstack application.\n\nYou can create a custom extractors that extracts the auth session from the request. From that auth session, you can check if the user has the required privileges before returning the private data.\n\nA [full auth example](https://github.com/DioxusLabs/dioxus/blob/v0.5/packages/fullstack/examples/axum-auth/src/main.rs) with the complete implementation is available in the fullstack examples." + } + 59usize => { + "# Custom Hooks\n\nHooks are a great way to encapsulate business logic. If none of the existing hooks work for your problem, you can write your own.\n\nWhen writing your hook, you can make a function that starts with `use_` and takes any arguments you need. You can then use the `use_hook` method to create a hook that will be called the first time the component is rendered.\n\n## Composing Hooks\n\nTo avoid repetition, you can encapsulate business logic based on existing hooks to create a new hook.\n\nFor example, if many components need to access an `AppSettings` struct, you can create a \"shortcut\" hook:\n\n````rust@hooks_composed.rs\nfn use_settings() -> Signal {\n consume_context()\n}\n````\n\nOr if you want to wrap a hook that persists reloads with the storage API, you can build on top of the use_signal hook to work with mutable state:\n\n````rust@hooks_composed.rs\nuse gloo_storage::{LocalStorage, Storage};\nuse serde::{de::DeserializeOwned, Serialize};\n\n/// A persistent storage hook that can be used to store data across application reloads.\n#[allow(clippy::needless_return)]\npub fn use_persistent(\n // A unique key for the storage entry\n key: impl ToString,\n // A function that returns the initial value if the storage entry is empty\n init: impl FnOnce() -> T,\n) -> UsePersistent {\n // Use the use_signal hook to create a mutable state for the storage entry\n let state = use_signal(move || {\n // This closure will run when the hook is created\n let key = key.to_string();\n let value = LocalStorage::get(key.as_str()).ok().unwrap_or_else(init);\n StorageEntry { key, value }\n });\n\n // Wrap the state in a new struct with a custom API\n UsePersistent { inner: state }\n}\n\nstruct StorageEntry {\n key: String,\n value: T,\n}\n\n/// Storage that persists across application reloads\npub struct UsePersistent {\n inner: Signal>,\n}\n\nimpl Clone for UsePersistent {\n fn clone(&self) -> Self {\n *self\n }\n}\n\nimpl Copy for UsePersistent {}\n\nimpl UsePersistent {\n /// Returns a reference to the value\n pub fn get(&self) -> T {\n self.inner.read().value.clone()\n }\n\n /// Sets the value\n pub fn set(&mut self, value: T) {\n let mut inner = self.inner.write();\n // Write the new value to local storage\n LocalStorage::set(inner.key.as_str(), &value);\n inner.value = value;\n }\n}\n````\n\n## Custom Hook Logic\n\nYou can use [`use_hook`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_hook.html) to build your own hooks. In fact, this is what all the standard hooks are built on!\n\n`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.\n\n > \n > Note: You can use the `use_on_destroy` hook to clean up any resources the hook uses when the component is destroyed.\n\nInside the initialization closure, you will typically make calls to other `cx` methods. For example:\n\n* The `use_signal` hook tracks state in the hook value, and uses [`schedule_update`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.schedule_update.html) to make Dioxus re-render the component whenever it changes.\n\nHere is a simplified implementation of the `use_signal` hook:\n\n````rust@hooks_custom_logic.rs\nuse std::cell::RefCell;\nuse std::rc::Rc;\nuse std::sync::Arc;\n\nstruct Signal {\n value: Rc>,\n update: Arc,\n}\n\nimpl Clone for Signal {\n fn clone(&self) -> Self {\n Self {\n value: self.value.clone(),\n update: self.update.clone(),\n }\n }\n}\n\nfn my_use_signal(init: impl FnOnce() -> T) -> Signal {\n use_hook(|| {\n // The update function will trigger a re-render in the component cx is attached to\n let update = schedule_update();\n // Create the initial state\n let value = Rc::new(RefCell::new(init()));\n\n Signal { value, update }\n })\n}\n\nimpl Signal {\n fn get(&self) -> T {\n self.value.borrow().clone()\n }\n\n fn set(&self, value: T) {\n // Update the state\n *self.value.borrow_mut() = value;\n // Trigger a re-render on the component the state is from\n (self.update)();\n }\n}\n````\n\n* The `use_context` hook calls [`consume_context`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.consume_context.html) (which would be expensive to call on every render) to get some context from the component\n\nHere is an implementation of the `use_context` and `use_context_provider` hooks:\n\n````rust@hooks_custom_logic.rs\npub fn use_context() -> T {\n use_hook(|| consume_context())\n}\n\npub fn use_context_provider(f: impl FnOnce() -> T) -> T {\n use_hook(|| {\n let val = f();\n // Provide the context state to the component\n provide_context(val.clone());\n val\n })\n}\n\n````" + } + 71usize => { + "# Roadmap & Feature-set\n\nThis feature set and roadmap can help you decide if what Dioxus can do today works for you.\n\nIf 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).\n\nGenerally, here's the status of each platform:\n\n* **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.\n\n* **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`.\n\n* **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.\n\n* **Mobile**: Mobile support is very young. You'll be figuring things out as you go and there are not many support crates for peripherals.\n\n* **LiveView**: LiveView support is very young. You'll be figuring things out as you go. Thankfully, none of it is too hard and any work can be upstreamed into Dioxus.\n\n## Features\n\n---\n\n|Feature|Status|Description|\n|-------|------|-----------|\n|Conditional Rendering|x|if/then to hide/show component|\n|Map, Iterator|x|map/filter/reduce to produce rsx!|\n|Keyed Components|x|advanced diffing with keys|\n|Web|x|renderer for web browser|\n|Desktop (webview)|x|renderer for desktop|\n|Shared State (Context)|x|share state through the tree|\n|Hooks|x|memory cells in components|\n|SSR|x|render directly to string|\n|Component Children|x|cx.children() as a list of nodes|\n|Headless components|x|components that don't return real elements|\n|Fragments|x|multiple elements without a real root|\n|Manual Props|x|Manually pass in props with spread syntax|\n|Controlled Inputs|x|stateful wrappers around inputs|\n|CSS/Inline Styles|x|syntax for inline styles/attribute groups|\n|Custom elements|x|Define new element primitives|\n|Suspense|x|schedule future render from future/promise|\n|Integrated error handling|x|Gracefully handle errors with ? syntax|\n|NodeRef|x|gain direct access to nodes|\n|Re-hydration|x|Pre-render to HTML to speed up first contentful paint|\n|Jank-Free Rendering|x|Large diffs are segmented across frames for silky-smooth transitions|\n|Effects|x|Run effects after a component has been committed to render|\n|Portals|\\*|Render nodes outside of the traditional tree structure|\n|Cooperative Scheduling|\\*|Prioritize important events over non-important events|\n|Server Components|\\*|Hybrid components for SPA and Server|\n|Bundle Splitting|i|Efficiently and asynchronously load the app|\n|Lazy Components|i|Dynamically load the new components as the page is loaded|\n|1st class global state|x|redux/recoil/mobx on top of context|\n|Runs natively|x|runs as a portable binary w/o a runtime (Node)|\n|Subtree Memoization|x|skip diffing static element subtrees|\n|High-efficiency templates|x|rsx! calls are translated to templates on the DOM's side|\n|Compile-time correct|x|Throw errors on invalid template layouts|\n|Heuristic Engine|x|track component memory usage to minimize future allocations|\n|Fine-grained reactivity|i|Skip diffing for fine-grain updates|\n\n* x = implemented and working\n* \\* = actively being worked on\n* i = not yet implemented or being worked on\n\n## Roadmap\n\nThese Features are planned for the future of Dioxus:\n\n### Core\n\n* [x] Release of Dioxus Core\n* [x] Upgrade documentation to include more theory and be more comprehensive\n* [x] Support for HTML-side templates for lightning-fast dom manipulation\n* [ ] Support for multiple renderers for same virtualdom (subtrees)\n* [ ] Support for ThreadSafe (Send + Sync)\n* [ ] Support for Portals\n\n### SSR\n\n* [x] SSR Support + Hydration\n* [x] Integrated suspense support for SSR\n\n### Desktop\n\n* [ ] Declarative window management\n* [ ] Templates for building/bundling\n* [ ] Access to Canvas/WebGL context natively\n\n### Mobile\n\n* [ ] Mobile standard library\n * [ ] GPS\n * [ ] Camera\n * [ ] filesystem\n * [ ] Biometrics\n * [ ] WiFi\n * [ ] Bluetooth\n * [ ] Notifications\n * [ ] Clipboard\n* [ ] Animations\n\n### Bundling (CLI)\n\n* [x] Translation from HTML into RSX\n* [x] Dev server\n* [x] Live reload\n* [x] Translation from JSX into RSX\n* [ ] Hot module replacement\n* [ ] Code splitting\n* [x] Asset macros\n* [x] Css pipeline\n* [x] Image pipeline\n\n### Essential hooks\n\n* [x] Router\n* [x] Global state management\n* [ ] Resize observer\n\n## Work in Progress\n\n### Build Tool\n\nWe are currently working on our own build tool called [Dioxus CLI](https://github.com/DioxusLabs/dioxus/tree/main/packages/cli) which will support:\n\n* an interactive TUI\n* on-the-fly reconfiguration\n* hot CSS reloading\n* two-way data binding between browser and source code\n* an interpreter for `rsx!`\n* ability to publish to github/netlify/vercel\n* bundling for iOS/Desktop/etc\n\n### Server Component Support\n\nWhile 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.\n\n### Native rendering\n\nWe are currently working on a native renderer for Dioxus using WGPU called [Blitz](https://github.com/DioxusLabs/blitz/). This will allow you to build apps that are rendered natively for iOS, Android, and Desktop." + } + 73usize => { + "# Hooks\n\nDioxus now uses signals as the backing for its state management. Signals are a smarter, more flexible version of the `use_ref` hook. Signals now back many hooks in dioxus to provide a more consistent and flexible API.\n\n### State Hooks\n\nState hooks are now backed by signals. `use_state`, `use_ref`, and `use_shared_state` have been replaced with the `use_signal` hook. The `use_signal` hook is a more flexible and powerful version of the `use_ref` hook with smarter scopes that only subscribe to a signal if that signal is read within the scope. You can read more about the `use_signal` hook in the [State Migration](state.md) guide.\n\n### Async Hooks\n\nThe `use_future` hook has been replaced with the `use_resource` hook. `use_resource` automatically subscribes to any signals that are read within the closure instead of using a tuple of dependencies.\n\nDioxus 0.4:\n\n````rust\nfn MyComponent(cx: Scope) -> Element {\n\tlet state = use_state(cx, || 0);\n\tlet my_resource = use_future(cx, (**state,), |(state,)| async move {\n\t\t// start a request that depends on the state\n\t\tprintln!(\"{state}\");\n\t});\n\trender! {\n\t\t\"{state}\"\n\t}\n}\n````\n\nDioxus 0.5:\n\n````rust@migration_hooks.rs\nfn MyComponent() -> Element {\n let state = use_signal(|| 0);\n // No need to manually set the dependencies, the use_resource hook will automatically detect signal dependencies\n let my_resource = use_resource(move || async move {\n // start a request that depends on the state\n // Because we read from the state signal, this future will be re-run whenever the state changes\n println!(\"{state}\");\n });\n rsx! {\"{state}\"}\n}\n````\n\n### Dependencies\n\nSome hooks including `use_effect` and `use_resource` now take a single closure with automatic subscriptions instead of a tuple of dependencies. You can read more about the `use_resource` hook in the [Hook Migration](hooks.md) guide.\n\nDioxus 0.4:\n\n````rust\nfn HasDependencies(cx: Scope) -> Element {\n\tlet state = use_state(cx, || 0);\n\tlet my_resource = use_resource(cx, (**state,), |(state,)| async move {\n\t\tprintln!(\"{state}\");\n\t});\n\tlet state_plus_one = use_memo(cx, (**state,), |(state,)| {\n\t\tstate() + 1\n\t});\n\trender! {\n\t\t\"{state_plus_one}\"\n\t}\n}\n````\n\nDioxus 0.5:\n\n````rust@migration_hooks.rs\nfn HasDependencies() -> Element {\n let state = use_signal(|| 0);\n // No need to manually set the dependencies, the use_resource hook will automatically detect signal dependencies\n let my_resource = use_resource(move || async move {\n // Because we read from the state signal, this future will be re-run whenever the state changes\n println!(\"{state}\");\n });\n let state_plus_one = use_memo(move || {\n // Because we read from the state signal, this future will be re-run whenever the state changes\n state() + 1\n });\n rsx! {\"{state_plus_one}\"}\n}\n````" + } + 38usize => { + "# Navigation Targets\n\nIn the previous chapter, we learned how to create links to pages within our app.\nWe told them where to go using the `target` property. This property takes something that can be converted to a [`NavigationTarget`].\n\n## What is a navigation target?\n\nA [`NavigationTarget`] is similar to the `href` of an HTML anchor element. It\ntells the router where to navigate to. The Dioxus Router knows two kinds of\nnavigation targets:\n\n* [`Internal`]: We used internal links in the previous chapter. It's a link to a page within our\n app represented as a Route enum.\n* [`External`]: This works exactly like an HTML anchors' `href`. Don't use this for in-app\n navigation as it will trigger a page reload by the browser.\n\n## External navigation\n\nIf we need a link to an external page we can do it like this:\n\n````rust@external_link.rs\nfn GoToDioxus() -> Element {\n rsx! {\n Link { to: \"https://dioxuslabs.com\", \"ExternalTarget target\" }\n }\n}\n````" + } + 75usize => { + "# Fermi\n\nIn dioxus 0.5, fermi atoms have been replaced with global signals and included in the main dioxus library.\n\nThe new global signals can be used directly without hooks and include additional functionality like global memos.\n\nDioxus 0.4:\n\n````rust\nuse dioxus::prelude::*;\nuse fermi::*;\n\nstatic NAME: Atom = Atom(|_| \"world\".to_string());\nstatic NAMES: AtomRef> = AtomRef(|_| vec![\"world\".to_string()]);\n\nfn app(cx: Scope) -> Element {\n use_init_atom_root(cx);\n let set_name = use_set(cx, &NAME);\n\tlet names = use_atom_ref(cx, &NAMES);\n\n cx.render(rsx! {\n button {\n\t\t\tonclick: move |_| set_name(\"dioxus\".to_string()),\n\t\t\t\"reset name\"\n\t\t}\n\t\t\"{names.read():?}\"\n })\n}\n````\n\nDioxus 0.5:\n\n````rust@migration_fermi.rs\nuse dioxus::prelude::*;\n\nstatic NAME: GlobalSignal = Signal::global(|| \"world\".to_string());\n// Global signals work for copy and clone types in the same way\nstatic NAMES: GlobalSignal> = Signal::global(|| vec![\"world\".to_string()]);\n\nfn app() -> Element {\n // No need to use use_init_atom_root, use_set, or use_atom_ref. Just use the global signal directly\n rsx! {\n button { onclick: move |_| *NAME.write() = \"reset name\".to_string(), \"reset name\" }\n \"{NAMES:?}\"\n }\n}\n````\n\n## Memos\n\nDioxus 0.5 introduces global memos which can be used to store computed values globally.\n\n````rust@migration_fermi.rs\nstatic COUNT: GlobalSignal = Signal::global(|| 0);\nstatic MEMO: GlobalMemo = Signal::global_memo(|| COUNT() + 1);\n\nfn GlobalMemo() -> Element {\n rsx! {\n button { onclick: move |_| *COUNT.write() += 1, \"increment\" }\n // Global memos can be used like signals\n \"{MEMO}\"\n }\n}\n````" + } + 7usize => { + "# Dioxus Reference\n\nThis Reference contains more detailed explanations for all concepts covered in the [guide](../guide/index.md) and more.\n\n## Rendering\n\n* [`RSX`](rsx.md) Rsx is a HTML-like macro that allows you to declare UI\n* [`Components`](components.md) Components are the building blocks of UI in Dioxus\n* [`Props`](component_props.md) Props allow you pass information to Components\n* [`Event Listeners`](event_handlers.md) Event listeners let you respond to user input\n* [`User Input`](user_input.md) How to handle User input in Dioxus\n* [`Dynamic Rendering`](dynamic_rendering.md) How to dynamically render data in Dioxus\n\n## State\n\n* [`Hooks`](hooks.md) Hooks allow you to create components state\n* [`Context`](context.md) Context allows you to create state in a parent and consume it in children\n* [`Routing`](router.md) The router helps you manage the URL state\n* [`Resource`](use_resource.md) Use future allows you to create an async task and monitor it's state\n* [`UseCoroutine`](use_coroutine.md) Use coroutine helps you manage external state\n* [`Spawn`](spawn.md) Spawn creates an async task\n\n## Platforms\n\n* [`Choosing a Web Renderer`](choosing_a_web_renderer.md) Overview of the different web renderers\n* [`Desktop`](desktop/index.md) Overview of desktop specific APIS\n* [`Web`](web/index.md) Overview of web specific APIS\n* [`Fullstack`](fullstack/index.md) Overview of Fullstack specific APIS\n * [`Server Functions`](fullstack/server_functions.md) Server functions make it easy to communicate between your server and client\n * [`Extractors`](fullstack/extractors.md) Extractors allow you to get extra information out of the headers of a request\n * [`Middleware`](fullstack/middleware.md) Middleware allows you to wrap a server function request or response\n * [`Authentication`](fullstack/authentication.md) An overview of how to handle authentication with server functions\n * [`Routing`](fullstack/routing.md) An overview of how to work with the router in the fullstack renderer\n* [`SSR`](ssr.md) Overview of the SSR renderer\n* [`Liveview`](liveview.md) Overview of liveview specific APIS" + } + 11usize => { + "# Event Handlers\n\nEvent handlers are used to respond to user actions. For example, an event handler could be triggered when the user clicks, scrolls, moves the mouse, or types a character.\n\nEvent handlers are attached to elements. For example, we usually don't care about all the clicks that happen within an app, only those on a particular button.\n\nEvent 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 it listens for is triggered and will be passed that event.\n\nFor example, to handle clicks on an element, we can specify an `onclick` handler:\n\n````rust, no_run@event_click.rs\nrsx! {\n button { onclick: move |event| log::info!(\"Clicked! Event: {event:?}\"), \"click me!\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n event_click::App {}\n}\n````\n\n## The Event object\n\nEvent handlers receive an [`Event`](https://docs.rs/dioxus-core/latest/dioxus_core/struct.Event.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.\n\nIn the example above, this event data was logged to the terminal:\n\n````\nClicked! Event: UiEvent { bubble_state: Cell { value: true }, data: MouseData { coordinates: Coordinates { screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } }\nClicked! Event: UiEvent { bubble_state: Cell { value: true }, data: MouseData { coordinates: Coordinates { screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } }\n````\n\nTo learn what the different event types for HTML provide, read the [events module docs](https://docs.rs/dioxus-html/latest/dioxus_html/events/index.html).\n\n### Event propagation\n\nSome events will trigger first on the element the event originated at upward. For example, a click event on a `button` inside a `div` would first trigger the button's event listener and then the div's event listener.\n\n > \n > For more information about event propagation see [the mdn docs on event bubbling](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling)\n\nIf you want to prevent this behavior, you can call `stop_propagation()` on the event:\n\n````rust, no_run@event_nested.rs\nrsx! {\n div { onclick: move |_event| {},\n \"outer\"\n button {\n onclick: move |event| {\n event.stop_propagation();\n },\n \"inner\"\n }\n }\n}\n````\n\n## Prevent Default\n\nSome events have a default behavior. For keyboard events, this might be entering the typed character. For mouse events, this might be selecting some text.\n\nIn 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 can be used for multiple handlers using their name separated by spaces:\n\n````rust, no_run@event_prevent_default.rs\nrsx! {\n a {\n href: \"https://example.com\",\n prevent_default: \"onclick\",\n \"example.com\"\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n event_prevent_default::App {}\n}\n````\n\nAny event handlers will still be called.\n\n > \n > 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 based on the data in the event.\n\n## Handler Props\n\nSometimes, you might want to make a component that accepts an event handler. A simple example would be a `FancyButton` component, which accepts an `onclick` handler:\n\n````rust, no_run@event_handler_prop.rs\n#[derive(PartialEq, Clone, Props)]\npub struct FancyButtonProps {\n onclick: EventHandler,\n}\n\npub fn FancyButton(props: FancyButtonProps) -> Element {\n rsx! {\n button {\n class: \"fancy-button\",\n onclick: move |evt| props.onclick.call(evt),\n \"click me pls.\"\n }\n }\n}\n````\n\nThen, you can use it like any other handler:\n\n````rust, no_run@event_handler_prop.rs\nrsx! {\n FancyButton {\n onclick: move |event| println!(\"Clicked! {event:?}\"),\n }\n}\n````\n\n > \n > Note: just like any other attribute, you can name the handlers anything you want! Any closure you pass in will automatically be turned into an `EventHandler`.\n\n#### Async Event Handlers\n\nPassing `EventHandler`s as props does not support passing a closure that returns an async block. Instead, you must manually call `spawn` to do async operations:\n\n````rust, no_run@event_handler_prop.rs\nrsx! {\n FancyButton {\n // This does not work!\n // onclick: move |event| async move {\n // println!(\"Clicked! {event:?}\");\n // },\n\n // This does work!\n onclick: move |event| {\n spawn(async move {\n println!(\"Clicked! {event:?}\");\n });\n },\n }\n}\n````\n\nThis is only the case for custom event handlers as props.\n\n## Custom Data\n\nEvent Handlers are generic over any type, so you can pass in any data you want to them, e.g:\n\n````rust, no_run@event_handler_prop.rs\nstruct ComplexData(i32);\n\n#[derive(PartialEq, Clone, Props)]\npub struct CustomFancyButtonProps {\n onclick: EventHandler,\n}\n\npub fn CustomFancyButton(props: CustomFancyButtonProps) -> Element {\n rsx! {\n button {\n class: \"fancy-button\",\n onclick: move |_| props.onclick.call(ComplexData(0)),\n \"click me pls.\"\n }\n }\n}\n````" + } + 57usize => { + "# State Cookbook\n\n* [External State](external/index.md)\n* [Custom Hook](custom_hooks/index.md)" + } + 54usize => { + "This section of the guide provides getting started guides for common tools used with Dioxus.\n\n* [Logging](./logging.md)\n* [Internationalization](./internationalization.md)" + } + 61usize => { + "# Tailwind\n\nYou can style your Dioxus application with whatever CSS framework you choose, or just write vanilla CSS.\n\nOne popular option for styling your Dioxus application is [Tailwind](https://tailwindcss.com/). Tailwind allows you to style your elements with CSS utility classes. This guide will show you how to setup tailwind CSS with your Dioxus application.\n\n## Setup\n\n1. Install the Dioxus CLI:\n\n````bash\ncargo install dioxus-cli\n````\n\n2. Install npm: [https://docs.npmjs.com/downloading-and-installing-node-js-and-npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)\n2. Install the tailwind css cli: [https://tailwindcss.com/docs/installation](https://tailwindcss.com/docs/installation)\n2. Initialize the tailwind css project:\n\n````bash\nnpx tailwindcss init\n````\n\nThis should create a `tailwind.config.js` file in the root of the project.\n\n5. Edit the `tailwind.config.js` file to include rust files:\n\n````js\nmodule.exports = {\n mode: \"all\",\n content: [\n // include all rust, html and css files in the src directory\n \"./src/**/*.{rs,html,css}\",\n // include all html files in the output (dist) directory\n \"./dist/**/*.html\",\n ],\n theme: {\n extend: {},\n },\n plugins: [],\n}\n````\n\n6. Create a `input.css` file in the root of your project with the following content:\n\n````css\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n````\n\n7. Add [Manganis](https://github.com/DioxusLabs/manganis) to your project to handle asset collection.\n\n````sh\ncargo add manganis\n````\n\n8. Create a link to the `tailwind.css` file using manganis somewhere in your rust code:\n\n````rust@tailwind.rs\n// Urls are relative to your Cargo.toml file\nconst _TAILWIND_URL: &str = manganis::mg!(file(\"public/tailwind.css\"));\n\n````\n\n### Bonus Steps\n\n1. Install the tailwind css vs code extension\n1. Go to the settings for the extension and find the experimental regex support section. Edit the setting.json file to look like this:\n\n````json\n\"tailwindCSS.experimental.classRegex\": [\"class\\\\s*:\\\\s*\\\"([^\\\"]*)\"],\n\"tailwindCSS.includeLanguages\": {\n \"rust\": \"html\"\n},\n````\n\n## Development\n\n* Run the following command in the root of the project to start the tailwind css compiler:\n\n````bash\nnpx tailwindcss -i ./input.css -o ./public/tailwind.css --watch\n````\n\n### Web\n\n* Run the following command in the root of the project to start the dioxus dev server:\n\n````bash\ndx serve\n````\n\n* Open the browser to [http://localhost:8080](http://localhost:8080).\n\n### Desktop\n\n* Launch the dioxus desktop app:\n\n````bash\ndx serve --platform desktop\n````" + } + 43usize => { + "# Nested Routes\n\nWhen developing bigger applications we often want to nest routes within each\nother. As an example, we might want to organize a settings menu using this\npattern:\n\n````plain\n└ Settings\n ├ General Settings (displayed when opening the settings)\n ├ Change Password\n └ Privacy Settings\n````\n\nWe might want to map this structure to these paths and components:\n\n````plain\n/settings\t\t -> Settings { GeneralSettings }\n/settings/password -> Settings { PWSettings }\n/settings/privacy -> Settings { PrivacySettings }\n````\n\nNested routes allow us to do this without repeating /settings in every route.\n\n## Nesting\n\nTo nest routes, we use the `#[nest(\"path\")]` and `#[end_nest]` attributes.\n\nThe path in nest must not:\n\n1. Contain a [Catch All Segment](index.md#catch-all-segments)\n1. Contain a [Query Segment](index.md#query-segments)\n\nIf you define a dynamic segment in a nest, it will be available to all child routes and layouts.\n\nTo finish a nest, we use the `#[end_nest]` attribute or the end of the enum.\n\n````rust@nest.rs\n#[derive(Routable, Clone)]\n// Skipping formatting allows you to indent nests\n#[rustfmt::skip]\nenum Route {\n // Start the /blog nest\n #[nest(\"/blog\")]\n // You can nest as many times as you want\n #[nest(\"/:id\")]\n #[route(\"/post\")]\n PostId {\n // You must include parent dynamic segments in child variants\n id: usize,\n },\n // End nests manually with #[end_nest]\n #[end_nest]\n #[route(\"/:id\")]\n // The absolute route of BlogPost is /blog/:name\n BlogPost {\n id: usize,\n },\n // Or nests are ended automatically at the end of the enum\n}\n\n#[component]\nfn BlogPost(id: usize) -> Element {\n todo!()\n}\n\n#[component]\nfn PostId(id: usize) -> Element {\n todo!()\n}\n````" + } + 47usize => { + "# History Providers\n\n\\[`HistoryProvider`\\]s are used by the router to keep track of the navigation history\nand update any external state (e.g. the browser's URL).\n\nThe router provides two \\[`HistoryProvider`\\]s, but you can also create your own.\nThe two default implementations are:\n\n* The \\[`MemoryHistory`\\] is a custom implementation that works in memory.\n* The \\[`LiveviewHistory`\\] is a custom implementation that works with the liveview renderer.\n* The \\[`WebHistory`\\] integrates with the browser's URL.\n\nBy default, the router uses the \\[`MemoryHistory`\\]. It might be changed to use\n\\[`WebHistory`\\] when the `web` feature is active, but that is not guaranteed.\n\nYou can override the default history:\n\n````rust@history_provider.rs\n#[component]\nfn App() -> Element {\n rsx! {Router:: { config: || RouterConfig::default().history(WebHistory::default()) }}\n}\n````" + } + 70usize => { + "# Overall Goals\n\nThis document outlines some of the overall goals for Dioxus. These goals are not set in stone, but they represent general guidelines for the project.\n\nThe goal of Dioxus is to make it easy to build **cross-platform applications that scale**.\n\n## Cross-Platform\n\nDioxus is designed to be cross-platform by default. This means that it should be easy to build applications that run on the web, desktop, and mobile. However, Dioxus should also be flexible enough to allow users to opt into platform-specific features when needed. The `use_eval` is one example of this. By default, Dioxus does not assume that the platform supports JavaScript, but it does provide a hook that allows users to opt into JavaScript when needed.\n\n## Performance\n\nAs Dioxus applications grow, they should remain relatively performant without the need for manual optimizations. There will be cases where manual optimizations are needed, but Dioxus should try to make these cases as rare as possible.\n\nOne of the benefits of the core architecture of Dioxus is that it delivers reasonable performance even when components are rerendered often. It is based on a Virtual Dom which performs diffing which should prevent unnecessary re-renders even when large parts of the component tree are rerun. On top of this, Dioxus groups static parts of the RSX tree together to skip diffing them entirely.\n\n## Type Safety\n\nAs teams grow, the Type safety of Rust is a huge advantage. Dioxus should leverage this advantage to make it easy to build applications with large teams.\n\nTo take full advantage of Rust's type system, Dioxus should try to avoid exposing public `Any` types and string-ly typed APIs where possible.\n\n## Developer Experience\n\nDioxus should be easy to learn and ergonomic to use.\n\n* The API of Dioxus attempts to remain close to React's API where possible. This makes it easier for people to learn Dioxus if they already know React\n\n* We can avoid the tradeoff between simplicity and flexibility by providing multiple layers of API: One for the very common use case, one for low-level control\n \n * Hooks: the hooks crate has the most common use cases, but `use_hook` provides a way to access the underlying persistent value if needed.\n * The builder pattern in platform Configs: The builder pattern is used to default to the most common use case, but users can change the defaults if needed.\n* Documentation:\n \n * All public APIs should have rust documentation\n * Examples should be provided for all public features. These examples both serve as documentation and testing. They are checked by CI to ensure that they continue to compile\n * The most common workflows should be documented in the guide" + } + 35usize => { + "# Overview\n\nIn this guide, you'll learn to effectively use Dioxus Router whether you're\nbuilding a small todo app or the next FAANG company. We will create a small\nwebsite with a blog, homepage, and more!\n\n > \n > To follow along with the router example, you'll need a working Dioxus app.\n > Check out the [Dioxus book](https://dioxuslabs.com/learn/0.5/getting_started) to get started.\n\n > \n > Make sure to add Dioxus Router as a dependency, as explained in the\n > [introduction](../index.md).\n\n## You'll learn how to\n\n* Create routes and render \"pages\".\n* Utilize nested routes, create a navigation bar, and render content for a\n set of routes.\n* Parse URL parameters to dynamically display content.\n* Redirect visitors to different routes.\n\n > \n > **Disclaimer**\n > \n > The example will only display the features of Dioxus Router. It will not\n > include any actual functionality. To keep things simple we will only be using\n > a single file, this is not the recommended way of doing things with a real\n > application.\n\nYou can find the complete application in the [full code](full-code.md) chapter." + } + 69usize => { + "# Project Structure\n\nThere are many packages in the Dioxus organization. This document will help you understand the purpose of each package and how they fit together.\n\n## Renderers\n\n* [Desktop](https://github.com/DioxusLabs/dioxus/tree/main/packages/desktop): A Render that Runs Dioxus applications natively, but renders them with the system webview\n* [Mobile](https://github.com/DioxusLabs/dioxus/tree/main/packages/mobile): A Render that Runs Dioxus applications natively, but renders them with the system webview. This is currently a copy of the desktop render\n* [Web](https://github.com/DioxusLabs/dioxus/tree/main/packages/web): Renders Dioxus applications in the browser by compiling to WASM and manipulating the DOM\n* [Liveview](https://github.com/DioxusLabs/dioxus/tree/main/packages/liveview): A Render that Runs on the server, and renders using a websocket proxy in the browser\n* [Plasmo](https://github.com/DioxusLabs/blitz/tree/master/packages/plasmo): A Renderer that renders a HTML-like tree into a terminal\n* [TUI](https://github.com/DioxusLabs/blitz/tree/master/packages/dioxus-tui): A Renderer that uses Plasmo to render a Dioxus application in a terminal\n* [Blitz-Core](https://github.com/DioxusLabs/blitz/tree/master/packages/blitz-core): An experimental native renderer that renders a HTML-like tree using WGPU.\n* [Blitz](https://github.com/DioxusLabs/blitz): An experimental native renderer that uses Blitz-Core to render a Dioxus application using WGPU.\n* [SSR](https://github.com/DioxusLabs/dioxus/tree/main/packages/ssr): A Render that Runs Dioxus applications on the server, and renders them to HTML\n\n## State Management/Hooks\n\n* [Hooks](https://github.com/DioxusLabs/dioxus/tree/main/packages/hooks): A collection of common hooks for Dioxus applications\n* [Signals](https://github.com/DioxusLabs/dioxus/tree/main/packages/signals): A experimental state management library for Dioxus applications. This currently contains a `Copy` version of Signal\n* [SDK](https://github.com/DioxusLabs/sdk): A collection of platform agnostic hooks to interact with system interfaces (The clipboard, camera, etc.).\n* [Fermi](https://github.com/DioxusLabs/dioxus/tree/main/packages/fermi): A global state management library for Dioxus applications.\n* [Router](https://github.com/DioxusLabs/dioxus/tree/main/packages/router): A client-side router for Dioxus applications\n\n## Core utilities\n\n* [core](https://github.com/DioxusLabs/dioxus/tree/main/packages/core): The core virtual dom implementation every Dioxus application uses\n * You can read more about the architecture of the core [in this blog post](https://dioxuslabs.com/blog/templates-diffing/) and the [custom renderer section of the guide](../cookbook/custom_renderer.md)\n* [RSX](https://github.com/DioxusLabs/dioxus/tree/main/packages/rsx): The core parsing for RSX used for hot reloading, autoformatting, and the macro\n* [core-macro](https://github.com/DioxusLabs/dioxus/tree/main/packages/core-macro): The rsx! macro used to write Dioxus applications. (This is a wrapper over the RSX crate)\n* [HTML macro](https://github.com/DioxusLabs/dioxus-html-macro): A html-like alternative to the RSX macro\n\n## Native Renderer Utilities\n\n* [native-core](https://github.com/DioxusLabs/blitz/tree/main/packages/native-core): Incrementally computed tree of states (mostly styles)\n * You can read more about how native-core can help you build native renderers in the [custom renderer section of the guide](../cookbook/custom_renderer.md#native-core)\n* [native-core-macro](https://github.com/DioxusLabs/blitz/tree/main/packages/native-core-macro): A helper macro for native core\n* [Taffy](https://github.com/DioxusLabs/taffy): Layout engine powering Blitz-Core, Plasmo, and Bevy UI\n\n## Web renderer tooling\n\n* [HTML](https://github.com/DioxusLabs/dioxus/tree/main/packages/html): defines html specific elements, events, and attributes\n* [Interpreter](https://github.com/DioxusLabs/dioxus/tree/main/packages/interpreter): defines browser bindings used by the web and desktop renderers\n\n## Developer tooling\n\n* [hot-reload](https://github.com/DioxusLabs/dioxus/tree/main/packages/hot-reload): Macro that uses the RSX crate to hot reload static parts of any rsx! macro. This macro works with any non-web renderer with an [integration](https://crates.io/crates/dioxus-hot-reload)\n* [autofmt](https://github.com/DioxusLabs/dioxus/tree/main/packages/autofmt): Formats RSX code\n* [rsx-rosetta](https://github.com/DioxusLabs/dioxus/tree/main/packages/rsx-rosetta): Handles conversion between HTML and RSX\n* [CLI](https://github.com/DioxusLabs/dioxus/tree/main/packages/cli): A Command Line Interface and VSCode extension to assist with Dioxus usage" + } + 65usize => { + "# Create a Project\n\nOnce you have the Dioxus CLI installed, you can use it to create your own project!\n\n## Initializing a project\n\nFirst, run the `dx new` command to create a new project.\n\n > \n > It clones this [template](https://github.com/DioxusLabs/dioxus-template), which is used to create dioxus apps.\n > \n > You can create your project from a different template by passing the `template` argument:\n > \n > ````\n > dx new --template gh:dioxuslabs/dioxus-template\n > ````\n\nNext, navigate into your new project using `cd project-name`, or simply opening it in an IDE.\n\n > \n > Make sure the WASM target is installed before running the projects.\n > You can install the WASM target for rust using rustup:\n > \n > ````\n > rustup target add wasm32-unknown-unknown\n > ````\n\nFinally, serve your project with `dx serve`! The CLI will tell you the address it is serving on, along with additional\ninfo such as code warnings." + } + 23usize => { + "# Mobile App\n\nBuild a mobile app with Dioxus!\n\nExample: [Mobile Demo](https://github.com/DioxusLabs/dioxus/tree/v0.5/examples/mobile_demo)\n\n## Support\n\nMobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with either the platform's WebView or experimentally with [WGPU](https://github.com/DioxusLabs/blitz). WebView doesn't support animations, transparency, and native widgets.\n\nMobile 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.\n\n## Getting Set up\n\nGetting set up with mobile can be quite challenging. The tooling here isn't great (yet) and might take some hacking around to get things working.\n\n### Setting up dependencies\n\n#### Android Dependencies\n\nFirst, install the rust Android targets:\n\n````sh\nrustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android\n````\n\nTo develop on Android, you will need to [install Android Studio](https://developer.android.com/studio).\n\nOnce you have installed Android Studio, you will need to install the Android SDK and NDK:\n\n1. Create a blank Android project\n1. Select `Tools > SDK manager`\n1. Navigate to the `SDK tools` window:\n\n![NDK install window](/assets/static/android_ndk_install.png)\n\nThen select:\n\n* The SDK\n* The SDK Command line tools\n* The NDK (side by side)\n* CMAKE\n\n4. Select `apply` and follow the prompts\n\n > \n > More details that could be useful for debugging any errors you encounter are available [in the official android docs](https://developer.android.com/studio/intro/update#sdk-manager)\n\nNext set the Java, Android and NDK home variables:\n\nMac:\n\n````sh\nexport JAVA_HOME=\"/Applications/Android Studio.app/Contents/jbr/Contents/Home\"\nexport ANDROID_HOME=\"$HOME/Library/Android/sdk\"\nexport NDK_HOME=\"$ANDROID_HOME/ndk/25.2.9519653\"\n````\n\nWindows:\n\n````powershell\n[System.Environment]::SetEnvironmentVariable(\"JAVA_HOME\", \"C:\\Program Files\\Android\\Android Studio\\jbr\", \"User\")\n[System.Environment]::SetEnvironmentVariable(\"ANDROID_HOME\", \"$env:LocalAppData\\Android\\Sdk\", \"User\")\n[System.Environment]::SetEnvironmentVariable(\"NDK_HOME\", \"$env:LocalAppData\\Android\\Sdk\\ndk\\25.2.9519653\", \"User\")\n````\n\n > \n > The NDK version in the paths should match the version you installed in the last step\n\n#### IOS Dependencies\n\nFirst, install the rust IOS targets:\n\n````sh\nrustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim\n````\n\nTo develop on IOS, you will need to [install XCode](https://apps.apple.com/us/app/xcode/id497799835).\n\n > \n > Note: On Apple silicon you must run Xcode on rosetta. Goto Application > Right Click Xcode > Get Info > Open in Rosetta.\n > If you are using M1, you will have to run `cargo build --target x86_64-apple-ios` instead of `cargo apple build` if you want to run in simulator.\n\n### Setting up your project\n\nFirst, we need to create a rust project:\n\n````sh\ncargo new dioxus-mobile-test --lib\ncd dioxus-mobile-test\n````\n\nNext, we can use `cargo-mobile2` to create a project for mobile:\n\n````shell\ncargo install --git https://github.com/tauri-apps/cargo-mobile2\ncargo mobile init\n````\n\nWhen you run `cargo mobile init`, you will be asked a series of questions about your project. One of those questions is what template you should use. Dioxus currently doesn't have a template in Tauri mobile, instead you can use the `wry` template.\n\n > \n > You may also be asked to input your team ID for IOS. You can find your team id [here](https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/) or create a team id by creating a developer account [here](https://developer.apple.com/help/account/get-started/about-your-developer-account)\n\nNext, we need to modify our dependencies to include dioxus and ensure the right version of wry is installed. Change the `[dependencies]` section of your `Cargo.toml`:\n\n````toml\n[dependencies]\nanyhow = \"1.0.56\"\nlog = \"0.4.11\"\ndioxus = { version = \"0.5\", features = [\"mobile\"] }\nwry = \"0.35.0\"\ntao = \"0.25.0\"\n````\n\nFinally, we need to add a component to renderer. Replace the wry template in your `lib.rs` file with this code:\n\n````rust\nuse anyhow::Result;\nuse dioxus::prelude::*;\n\n#[cfg(target_os = \"android\")]\nfn init_logging() {\n android_logger::init_once(\n android_logger::Config::default()\n .with_max_level(log::LevelFilter::Trace)\n );\n}\n\n#[cfg(not(target_os = \"android\"))]\nfn init_logging() {\n env_logger::init();\n}\n\n#[cfg(any(target_os = \"android\", target_os = \"ios\"))]\nfn stop_unwind T, T>(f: F) -> T {\n match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {\n Ok(t) => t,\n Err(err) => {\n eprintln!(\"attempt to unwind out of `rust` with err: {:?}\", err);\n std::process::abort()\n }\n }\n}\n\n#[no_mangle]\n#[inline(never)]\n#[cfg(any(target_os = \"android\", target_os = \"ios\"))]\npub extern \"C\" fn start_app() {\n fn _start_app() {\n stop_unwind(|| main().unwrap());\n }\n\n #[cfg(target_os = \"android\")]\n {\n tao::android_binding!(\n com_example,\n dioxus_mobile_test,\n WryActivity,\n wry::android_setup, // pass the wry::android_setup function to tao which will invoke when the event loop is created\n _start_app\n );\n wry::android_binding!(com_example, dioxus_mobile_test);\n }\n #[cfg(target_os = \"ios\")]\n _start_app()\n}\n\npub fn main() -> Result<()> {\n init_logging();\n\n launch(app);\n\n Ok(())\n}\n\nfn app() -> Element {\n let mut items = use_signal(|| vec![1, 2, 3]);\n\n log::debug!(\"Hello from the app\");\n\n rsx! {\n div {\n h1 { \"Hello, Mobile\"}\n div { margin_left: \"auto\", margin_right: \"auto\", width: \"200px\", padding: \"10px\", border: \"1px solid black\",\n button {\n onclick: move|_| {\n println!(\"Clicked!\");\n let mut items_mut = items.write();\n let new_item = items_mut.len() + 1;\n items_mut.push(new_item);\n println!(\"Requested update\");\n },\n \"Add item\"\n }\n for item in items.read().iter() {\n div { \"- {item}\" }\n }\n }\n }\n }\n}\n````\n\n## Running\n\nFrom there, you'll want to get a build of the crate using whichever platform you're targeting (simulator or actual hardware). For now, we'll just stick with the simulator.\n\nFirst, you need to make sure that the build variant is correct in Android Studio:\n\n1. Click \"Build\" in the top menu bar.\n1. Click \"Select Build Variant...\" in the dropdown.\n1. Find the \"Build Variants\" panel and use the dropdown to change the selected build variant.\n\n![android studio build dropdown](/assets/static/as-build-dropdown.png)\n![android studio build variants](/assets/static/as-build-variant-menu.png)\n\n### Android\n\nTo build your project on Android you can run:\n\n````sh\ncargo android build\n````\n\nNext, open Android studio:\n\n````sh\ncargo android open\n````\n\nThis will open an android studio project for this application.\n\nNext we need to create a simulator in Android studio to run our app in. To create a simulator click on the phone icon in the top right of Android studio:\n\n![android studio manage devices](/assets/static/android-studio-simulator.png)\n\nThen click the `create a virtual device` button and follow the prompts:\n\n![android studio devices](/assets/static/android-studio-devices.png)\n\nFinally, launch your device by clicking the play button on the device you created:\n\n![android studio device](/assets/static/android-studio-device.png)\n\nNow you can start your application from your terminal by running:\n\n````sh\ncargo android run\n````\n\n![android_demo](/assets/static/Android-Dioxus-demo.png)\n\n > \n > More information is available in the Android docs:\n > \n > * https://developer.android.com/ndk/guides\n > * https://developer.android.com/studio/projects/install-ndk\n > * https://source.android.com/docs/setup/build/rust/building-rust-modules/overview\n\n### IOS\n\nTo build your project for IOS, you can run:\n\n````sh\ncargo build --target aarch64-apple-ios-sim\n````\n\nNext, open XCode (this might take awhile if you've never opened XCode before):\n\n````sh\ncargo apple open\n````\n\nThis will open XCode with this particular project.\n\nFrom there, just click the \"play\" button with the right target and the app should be running!\n\n![ios_demo](/assets/static/IOS-dioxus-demo.png)\n\nNote that clicking play doesn't cause a new build, so you'll need to keep rebuilding the app between changes. The tooling here is very young, so please be patient. If you want to contribute to make things easier, please do! We'll be happy to help." + } + 64usize => { + "# Introduction\n\nThe ✨**Dioxus CLI**✨ is a tool to get Dioxus projects off the ground.\n\nThere's no documentation for commands here, but you can see all commands using `dx --help` once you've installed the CLI! Furthermore, you can run `dx --help` to get help with a specific command.\n\n## Features\n\n* Build and pack a Dioxus project.\n* Format `rsx` code.\n* Hot Reload.\n* Create a Dioxus project from a template repository.\n* And more!" + } + 30usize => { + "# Extractors\n\nServer functions are an ergonomic way to call a function on the server. Server function work by registering an endpoint on the server and using requests on the client. Most of the time, you shouldn't need to worry about how server functions operate, but there are some times when you need to get some value from the request other than the data passed in the server function.\n\nFor example, requests contain information about the user's browser (called the [user agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent)). We can use an extractor to retrieve that information.\n\nYou can use the `extract` method within a server function to extract something from the request. You can extract any type that implements `FromServerContext` (or when axum is enabled, you can use axum extractors directly):\n\n````rust@server_function_extract.rs\n#[server]\npub async fn log_headers() -> Result<(), ServerFnError> {\n let headers: http::HeaderMap = extract().await?;\n log::info!(\"{:?}\", headers[http::header::USER_AGENT]);\n Ok(())\n}\n````" + } + 46usize => { + "# Programmatic Navigation\n\nSometimes we want our application to navigate to another page without having the\nuser click on a link. This is called programmatic navigation.\n\n## Using a Navigator\n\nWe can get a navigator with the [`navigator`] function which returns a [`Navigator`].\n\nWe can use the [`Navigator`] to trigger four different kinds of navigation:\n\n* `push` will navigate to the target. It works like a regular anchor tag.\n* `replace` works like `push`, except that it replaces the current history entry\n instead of adding a new one. This means the prior page cannot be restored with the browser's back button.\n* `Go back` works like the browser's back button.\n* `Go forward` works like the browser's forward button.\n\n````rust@navigator.rs\n#[component]\nfn Home() -> Element {\n let nav = navigator();\n\n // push\n nav.push(Route::PageNotFound { route: vec![] });\n\n // replace\n nav.replace(Route::Home {});\n\n // go back\n nav.go_back();\n\n // go forward\n nav.go_forward();\n\n rsx! { h1 { \"Welcome to the Dioxus Blog!\" } }\n}\n````\n\nYou might have noticed that, like [`Link`], the [`Navigator`]s `push` and\n`replace` functions take a [`NavigationTarget`]. This means we can use either\n`Internal`, or `External` targets.\n\n## External Navigation Targets\n\nUnlike a [`Link`], the [`Navigator`] cannot rely on the browser (or webview) to\nhandle navigation to external targets via a generated anchor element.\n\nThis means, that under certain conditions, navigation to external targets can\nfail." + } + 74usize => { + "# State Migration\n\nThe `use_state` and `use_ref` hooks have been replaced with the `use_signal` hook. The `use_signal` hook is a more flexible and powerful version of the `use_ref` hook with smarter scopes that only subscribe to a signal if that signal is read within the scope.\n\nWith `use_state`, if you had this code:\n\n````rust\nfn Parent(cx: Scope) -> Element {\n\tlet state = use_state(cx, || 0);\n\n\trender! {\n\t\tChild {\n\t\t\tstate: state.clone()\n\t\t}\n\t}\n}\n\n#[component]\nfn Child(cx: Scope, state: UseState) -> Element {\n\trender! {\n\t\t\"{state}\"\n\t}\n}\n````\n\nParent would re-render every time the state changed even though only the child component was using the state. With the new `use_signal` hook, the parent would only re-render if the state was changed within the parent component:\n\n````rust@migration_state.rs\nfn Parent() -> Element {\n let state = use_signal(|| 0);\n\n rsx! { Child { state } }\n}\n\n#[component]\nfn Child(state: Signal) -> Element {\n rsx! {\"{state}\"}\n}\n````\n\nOnly the child component will re-render when the state changes because only the child component is reading the state.\n\n## Context Based State\n\nThe `use_shared_state_provider` and `use_shared_state` hooks have been replaced with using the `use_context_provider` and `use_context` hooks with a `Signal`:\n\n````rust@migration_state.rs\nfn Parent() -> Element {\n // Create a new signal and provide it to the context API\n let state = use_context_provider(|| Signal::new(0));\n\n rsx! { Child {} }\n}\n\nfn Child() -> Element {\n // Get the state from the context API\n let state = use_context::>();\n\n rsx! {\"{state}\"}\n}\n````\n\nSignals are smart enough to handle subscribing to the right scopes without a special shared state hook.\n\n## Opting Out of Subscriptions\n\nSome state hooks including `use_shared_state` and `use_ref` hooks had a function called `write_silent` in `0.4`. This function allowed you to update the state without triggering a re-render any subscribers. This function has been removed in `0.5`.\n\nInstead, you can use the `peek` function to read the current value of a signal without subscribing to it. This inverts the subscription model so that you can opt out of subscribing to a signal instead of opting all subscribers out of updates:\n\n````rust@migration_state.rs\nfn Parent() -> Element {\n let state = use_signal(|| 0);\n\n // Even though we are reading the state, we don't need to subscribe to it\n let read_without_subscribing = state.peek();\n println!(\"{}\", state.peek());\n\n rsx! { Child { state } }\n}\n\n#[component]\nfn Child(state: Signal) -> Element {\n rsx! {\n button { onclick: move |_| {\n state += 1;\n }, \"count is {state}\" }\n }\n}\n````\n\n`peek` gives you more fine-grained control over when you want to subscribe to a signal. This can be useful for performance optimizations and for updating state without re-rendering components.\n\n## Global State\n\nIn `0.4`, the fermi crate provided a separate global state API called atoms. In `0.5`, the `Signal` type has been extended to provide a global state API. You can use the `Signal::global` function to create a global signal:\n\n````rust@migration_state.rs\nstatic COUNT: GlobalSignal = Signal::global(|| 0);\n\nfn Parent() -> Element {\n rsx! {\n div { \"{COUNT}\" }\n button {\n onclick: move |_| {\n *COUNT.write() += 1;\n },\n \"Increment\"\n }\n }\n}\n````\n\nYou can read more about global signals in the [Fermi migration guide](fermi.md)." + } + 4usize => { + "# Interactivity\n\nIn this chapter, we will add a preview for articles you hover over or links you focus on.\n\n## Creating a Preview\n\nFirst, let's split our app into a Stories component on the left side of the screen, and a preview component on the right side of the screen:\n\n````rust@hackernews_state.rs\npub fn App() -> Element {\n rsx! {\n div { display: \"flex\", flex_direction: \"row\", width: \"100%\",\n div { width: \"50%\", Stories {} }\n div { width: \"50%\", Preview {} }\n }\n }\n}\n\n// New\nfn Stories() -> Element {\n rsx! {\n StoryListing {\n story: StoryItem {\n id: 0,\n title: \"hello hackernews\".to_string(),\n url: None,\n text: None,\n by: \"Author\".to_string(),\n score: 0,\n descendants: 0,\n time: chrono::Utc::now(),\n kids: vec![],\n r#type: \"\".to_string(),\n }\n }\n }\n}\n\n// New\n#[derive(Clone, Debug)]\nenum PreviewState {\n Unset,\n Loading,\n Loaded(StoryPageData),\n}\n\n// New\nfn Preview() -> Element {\n let preview_state = PreviewState::Unset;\n match preview_state {\n PreviewState::Unset => rsx! {\"Hover over a story to preview it here\"},\n PreviewState::Loading => rsx! {\"Loading...\"},\n PreviewState::Loaded(story) => {\n rsx! {\n div { padding: \"0.5rem\",\n div { font_size: \"1.5rem\", a { href: story.item.url, \"{story.item.title}\" } }\n div { dangerous_inner_html: story.item.text }\n for comment in &story.comments {\n Comment { comment: comment.clone() }\n }\n }\n }\n }\n }\n}\n\n// NEW\n#[component]\nfn Comment(comment: CommentData) -> Element {\n rsx! {\n div { padding: \"0.5rem\",\n div { color: \"gray\", \"by {comment.by}\" }\n div { dangerous_inner_html: \"{comment.text}\" }\n for kid in &comment.sub_comments {\n Comment { comment: kid.clone() }\n }\n }\n }\n}\n\n````\n\n````inject-dioxus\nDemoFrame {\n hackernews_state::app_v1::App {}\n}\n````\n\n## Event Handlers\n\nNext, we need to detect when the user hovers over a section or focuses a link. We can use an [event listener](../reference/event_handlers.md) to listen for the hover and focus events.\n\nEvent 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 it listens for is triggered. When an event is triggered, information about the event is passed to the closure through the [Event](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Event.html) structure.\n\nLet's create a [`onmouseenter`](https://docs.rs/dioxus/latest/dioxus/events/fn.onmouseenter.html) event listener in the `StoryListing` component:\n\n````rust@hackernews_state.rs\nrsx! {\n div {\n padding: \"0.5rem\",\n position: \"relative\",\n onmouseenter: move |_| {},\n div { font_size: \"1.5rem\",\n a { href: url, onfocus: move |_event| {}, \"{title}\" }\n a {\n color: \"gray\",\n href: \"https://news.ycombinator.com/from?site={hostname}\",\n text_decoration: \"none\",\n \" ({hostname})\"\n }\n }\n div { display: \"flex\", flex_direction: \"row\", color: \"gray\",\n div { \"{score}\" }\n div { padding_left: \"0.5rem\", \"by {by}\" }\n div { padding_left: \"0.5rem\", \"{time}\" }\n div { padding_left: \"0.5rem\", \"{comments}\" }\n }\n }\n}\n````\n\n > \n > You can read more about Event Handlers in the [Event Handler reference](../reference/event_handlers.md)\n\n## State\n\nSo far our components have had no state like normal rust functions. To make our application change when we hover over a link we need state to store the currently hovered link in the root of the application.\n\nYou can create state in dioxus using hooks. Hooks are Rust functions you call in a constant order in a component that add additional functionality to the component.\n\nIn this case, we will use the `use_context_provider` and `use_context` hooks:\n\n* You can provide a closure to `use_context_provider` that determines the initial value of the shared state and provides the value to all child components\n* You can then use the `use_context` hook to read and modify that state in the `Preview` and `StoryListing` components\n* When the value updates, the `Signal` will cause the component to re-render, and provides you with the new value\n\n > \n > Note: You should prefer local state hooks like use_signal or use_signal_sync when you only use state in one component. Because we use state in multiple components, we can use a [global state pattern](../reference/context.md)\n\n````rust@hackernews_state.rs\npub fn App() -> Element {\n use_context_provider(|| Signal::new(PreviewState::Unset));\n````\n\n````rust@hackernews_state.rs\n#[component]\nfn StoryListing(story: ReadOnlySignal) -> Element {\n let mut preview_state = consume_context::>();\n let StoryItem {\n title,\n url,\n by,\n score,\n time,\n kids,\n ..\n } = &*story.read();\n\n let url = url.as_deref().unwrap_or_default();\n let hostname = url\n .trim_start_matches(\"https://\")\n .trim_start_matches(\"http://\")\n .trim_start_matches(\"www.\");\n let score = format!(\"{score} point{}\", if *score > 1 { \"s\" } else { \"\" });\n let comments = format!(\n \"{} {}\",\n kids.len(),\n if kids.len() == 1 {\n \" comment\"\n } else {\n \" comments\"\n }\n );\n let time = time.format(\"%D %l:%M %p\");\n\n rsx! {\n div {\n padding: \"0.5rem\",\n position: \"relative\",\n onmouseenter: move |_event| {\n *preview_state\n .write() = PreviewState::Loaded(StoryPageData {\n item: story(),\n comments: vec![],\n });\n },\n div { font_size: \"1.5rem\",\n a {\n href: url,\n onfocus: move |_event| {\n *preview_state\n .write() = PreviewState::Loaded(StoryPageData {\n item: story(),\n comments: vec![],\n });\n },\n````\n\n````rust@hackernews_state.rs\nfn Preview() -> Element {\n // New\n let preview_state = consume_context::>();\n\n // New\n match preview_state() {\n````\n\n````inject-dioxus\nDemoFrame {\n hackernews_state::App {}\n}\n````\n\n > \n > You can read more about Hooks in the [Hooks reference](../reference/hooks.md)\n\n### The Rules of Hooks\n\nHooks are a powerful way to manage state in Dioxus, but there are some rules you need to follow to insure they work as expected. Dioxus uses the order you call hooks to differentiate between hooks. Because the order you call hooks matters, you must follow these rules:\n\n1. Hooks may be only used in components or other hooks (we'll get to that later)\n1. On every call to the component function\n 1. The same hooks must be called\n 1. In the same order\n1. Hooks name's should start with `use_` so you don't accidentally confuse them with regular functions\n\nThese rules mean that there are certain things you can't do with hooks:\n\n#### No Hooks in Conditionals\n\n````rust@hooks_bad.rs\n// ❌ don't call hooks in conditionals!\n// We must ensure that the same hooks will be called every time\n// But `if` statements only run if the conditional is true!\n// So we might violate rule 2.\nif you_are_happy && you_know_it {\n let something = use_signal(|| \"hands\");\n println!(\"clap your {something}\")\n}\n\n// ✅ instead, *always* call use_signal\n// You can put other stuff in the conditional though\nlet something = use_signal(|| \"hands\");\nif you_are_happy && you_know_it {\n println!(\"clap your {something}\")\n}\n````\n\n#### No Hooks in Closures\n\n````rust@hooks_bad.rs\n// ❌ don't call hooks inside closures!\n// We can't guarantee that the closure, if used, will be called in the same order every time\nlet _a = || {\n let b = use_signal(|| 0);\n b()\n};\n\n// ✅ instead, move hook `b` outside\nlet b = use_signal(|| 0);\nlet _a = || b();\n````\n\n#### No Hooks in Loops\n\n````rust@hooks_bad.rs\n// `names` is a Vec<&str>\n\n// ❌ Do not use hooks in loops!\n// In this case, if the length of the Vec changes, we break rule 2\nfor _name in &names {\n let is_selected = use_signal(|| false);\n println!(\"selected: {is_selected}\");\n}\n\n// ✅ Instead, use a hashmap with use_signal\nlet selection_map = use_signal(HashMap::<&str, bool>::new);\n\nfor name in &names {\n let is_selected = selection_map.read()[name];\n println!(\"selected: {is_selected}\");\n}\n````" + } + 37usize => { + "# Building a Nest\n\nIn this chapter, we will begin to build the blog portion of our site which will\ninclude links, nested routes, and route parameters.\n\n## Site Navigation\n\nOur site visitors won't know all the available pages and blogs on our site so we\nshould provide a navigation bar for them. Our navbar will be a list of links going between our pages.\n\nWe want our navbar component to be rendered on several different pages on our site. Instead of duplicating the code, we can create a component that wraps all children routes. This is called a layout component. To tell the router where to render the child routes, we use the [`Outlet`](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html) component.\n\nLet's create a new `NavBar` component:\n\n````rust@nested_routes.rs\n#[component]\nfn NavBar() -> Element {\n rsx! {\n nav {\n ul { li { \"links\" } }\n }\n // The Outlet component will render child routes (In this case just the Home component) inside the Outlet component\n Outlet:: {}\n }\n}\n````\n\nNext, let's add our `NavBar` component as a layout to our Route enum:\n\n````rust@nested_routes.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // All routes under the NavBar layout will be rendered inside of the NavBar Outlet\n #[layout(NavBar)]\n #[route(\"/\")]\n Home {},\n #[end_layout]\n #[route(\"/:..route\")]\n PageNotFound { route: Vec },\n}\n````\n\nTo add links to our `NavBar`, we could always use an HTML anchor element but that has two issues:\n\n1. It causes a full-page reload\n1. We can accidentally link to a page that doesn't exist\n\nInstead, we want to use the [`Link`] component provided by Dioxus Router.\n\nThe [`Link`] is similar to a regular `` tag. It takes a target and children.\n\nUnlike a regular `` tag, we can pass in our Route enum as the target. Because we annotated our routes with the `#[route(path)]` attribute, the [`Link`] will know how to generate the correct URL. If we use the Route enum, the rust compiler will prevent us from linking to a page that doesn't exist.\n\nLet's add our links:\n\n````rust@links.rs\n#[component]\nfn NavBar() -> Element {\n rsx! {\n nav {\n ul {\n li {\n Link { to: Route::Home {}, \"Home\" }\n }\n }\n }\n Outlet:: {}\n }\n}\n````\n\n > \n > Using this method, the [`Link`] component only works for links within our\n > application. To learn more about navigation targets see\n > [here](./navigation-targets.md).\n\nNow you should see a list of links near the top of your page. Click on one and\nyou should seamlessly travel between pages.\n\n## URL Parameters and Nested Routes\n\nMany websites such as GitHub put parameters in their URL. For example,\n`https://github.com/DioxusLabs` utilizes the text after the domain to\ndynamically search and display content about an organization.\n\nWe want to store our blogs in a database and load them as needed. We also\nwant our users to be able to send people a link to a specific blog post.\nInstead of listing all of the blog titles at compile time, we can make a dynamic route.\n\nWe could utilize a search page that loads a blog when clicked but then our users\nwon't be able to share our blogs easily. This is where URL parameters come in.\n\nThe path to our blog will look like `/blog/myBlogPage`, `myBlogPage` being the\nURL parameter.\n\nFirst, let's create a layout component (similar to the navbar) that wraps the blog content. This allows us to add a heading that tells the user they are on the blog.\n\n````rust@dynamic_route.rs\n#[component]\nfn Blog() -> Element {\n rsx! {\n h1 { \"Blog\" }\n Outlet:: {}\n }\n}\n````\n\nNow we'll create another index component, that'll be displayed when no blog post\nis selected:\n\n````rust@dynamic_route.rs\n#[component]\nfn BlogList() -> Element {\n rsx! {\n h2 { \"Choose a post\" }\n ul {\n li {\n Link {\n to: Route::BlogPost {\n name: \"Blog post 1\".into(),\n },\n \"Read the first blog post\"\n }\n }\n li {\n Link {\n to: Route::BlogPost {\n name: \"Blog post 2\".into(),\n },\n \"Read the second blog post\"\n }\n }\n }\n }\n}\n````\n\nWe also need to create a component that displays an actual blog post. This component will accept the URL parameters as props:\n\n````rust@dynamic_route.rs\n// The name prop comes from the /:name route segment\n#[component]\nfn BlogPost(name: String) -> Element {\n rsx! { h2 { \"Blog Post: {name}\" } }\n}\n````\n\nFinally, let's tell our router about those components:\n\n````rust@dynamic_route.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(NavBar)]\n #[route(\"/\")]\n Home {},\n #[nest(\"/blog\")]\n #[layout(Blog)]\n #[route(\"/\")]\n BlogList {},\n #[route(\"/post/:name\")]\n BlogPost { name: String },\n #[end_layout]\n #[end_nest]\n #[end_layout]\n #[route(\"/:..route\")]\n PageNotFound {\n route: Vec,\n },\n}\n````\n\nThat's it! If you head to `/blog/1` you should see our sample post.\n\n## Conclusion\n\nIn this chapter, we utilized Dioxus Router's Link, and Route Parameter\nfunctionality to build the blog portion of our application. In the next chapter,\nwe will go over how navigation targets (like the one we passed to our links)\nwork." + } + 58usize => { + "# Working with External State\n\nThis guide will help you integrate your Dioxus application with some external state like a different thread or a websocket connection.\n\n## Working with non-reactive State\n\n[Coroutines](../../../reference/use_coroutine.md) are great tool for dealing with non-reactive (state you don't render directly) state within your application.\n\nYou can store your state inside the coroutine async block and communicate with the coroutine with messages from any child components.\n\n````rust@use_coroutine.rs\n// import futures::StreamExt to use the next() method\nuse futures::StreamExt;\nlet mut response_state = use_signal(|| None);\nlet tx = use_coroutine(move |mut rx| async move {\n // Define your state before the loop\n let mut state = reqwest::Client::new();\n let mut cache: HashMap = HashMap::new();\n loop {\n // Loop and wait for the next message\n if let Some(request) = rx.next().await {\n // Resolve the message\n let response = if let Some(response) = cache.get(&request) {\n response.clone()\n } else {\n let response = state\n .get(&request)\n .send()\n .await\n .unwrap()\n .text()\n .await\n .unwrap();\n cache.insert(request, response.clone());\n response\n };\n response_state.set(Some(response));\n } else {\n break;\n }\n }\n});\n// Send a message to the coroutine\ntx.send(\"https://example.com\".to_string());\n// Get the current state of the coroutine\nlet response = response_state.read();\n````\n\n## Making Reactive State External\n\nIf you have some reactive state (state that is rendered), that you want to modify from another thread, you can use a signal that is sync. Signals take an optional second generic value with information about syncness. Sync signals have a slightly higher overhead than thread local signals, but they can be used in a multithreaded environment.\n\n````rust@sync_signal.rs\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app);\n}\n\nfn app() -> Element {\n let mut signal = use_signal_sync(|| 0);\n\n use_hook(|| {\n std::thread::spawn(move || loop {\n std::thread::sleep(std::time::Duration::from_secs(1));\n // You can easily update the signal from a different thread\n signal += 1;\n });\n });\n\n rsx! {\n button { onclick: move |_| signal += 1, \"Increase\" }\n \"{signal}\"\n }\n}\n\n````" + } + 66usize => { + "# Configure Project\n\nThis chapter will teach you how to configure the CLI with the `Dioxus.toml` file. There's an [example](#config-example) which has comments to describe individual keys. You can copy that or view this documentation for a more complete learning experience.\n\n\"🔒\" indicates a mandatory item. Some headers are mandatory, but none of the keys inside them are. In that case, you only need to include the header, but no keys. It might look weird, but it's normal.\n\n## Structure\n\nEach header has its TOML form directly under it.\n\n### Application 🔒\n\n````toml\n[application]\n````\n\nApplication-wide configuration. Applies to both web and desktop.\n\n* **name** 🔒 - Project name & title.\n ````toml\n name = \"my_project\"\n ````\n\n* **default_platform** 🔒 - The platform this project targets\n ````toml\n # Currently supported platforms: web, desktop\n default_platform = \"web\"\n ````\n\n* **out_dir** - The directory to place the build artifacts from `dx build` or `dx serve` into. This is also where the `assets` directory will be copied into.\n ````toml\n out_dir = \"dist\"\n ````\n\n* **asset_dir** - The directory with your static assets. The CLI will automatically copy these assets into the **out_dir** after a build/serve.\n ````toml\n asset_dir = \"public\"\n ````\n\n* **sub_package** - The sub package in the workspace to build by default.\n ````toml\n sub_package = \"my-crate\"\n ````\n\n### Web.App 🔒\n\n````toml\n[web.app]\n````\n\nWeb-specific configuration.\n\n* **title** - The title of the web page.\n ````toml\n # HTML title tag content\n title = \"project_name\"\n ````\n\n* **base_path** - The base path to build the application for serving at. This can be useful when serving your application in a subdirectory under a domain. For example, when building a site to be served on GitHub Pages.\n ````toml\n # The application will be served at domain.com/my_application/, so we need to modify the base_path to the path where the application will be served\n base_path = \"my_application\"\n ````\n\n### Web.Watcher 🔒\n\n````toml\n[web.watcher]\n````\n\nDevelopment server configuration.\n\n* **reload_html** - If this is true, the cli will rebuild the index.html file every time the application is rebuilt\n \n ````toml\n reload_html = true\n ````\n\n* **watch_path** - The files & directories to monitor for changes\n \n ````toml\n watch_path = [\"src\", \"public\"]\n ````\n\n* **index_on_404** - If enabled, Dioxus will serve the root page when a route is not found.\n *This is needed when serving an application that uses the router*. However, when serving your app using something else than Dioxus (e.g. GitHub Pages), you will have to check how to configure it on that platform. In GitHub Pages, you can make a copy of `index.html` named `404.html` in the same directory.\n \n ````toml\n index_on_404 = true\n ````\n\n### Web.Resource 🔒\n\n````toml\n[web.resource]\n````\n\nStatic resource configuration.\n\n* **style** - CSS files to include in your application.\n \n ````toml\n style = [\n # Include from public_dir.\n \"./assets/style.css\",\n # Or some asset from online cdn.\n \"https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css\"\n ]\n ````\n\n* **script** - JavaScript files to include in your application.\n \n ````toml\n script = [\n # Include from asset_dir.\n \"./public/index.js\",\n # Or from an online CDN.\n \"https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js\"\n ]\n ````\n\n### Web.Resource.Dev 🔒\n\n````toml\n[web.resource.dev]\n````\n\nThis is the same as [`[web.resource]`](#webresource-), but it only works in development servers. For example, if you want to include a file in a `dx serve` server, but not a `dx serve --release` server, put it here.\n\n### Web.Proxy\n\n````toml\n[[web.proxy]]\n````\n\nConfiguration related to any proxies your application requires during development. Proxies will forward requests to a new service.\n\n* **backend** - The URL to the server to proxy. The CLI will forward any requests under the backend relative route to the backend instead of returning 404\n ````toml\n backend = \"http://localhost:8000/api/\"\n ````\n \n This will cause any requests made to the dev server with prefix /api/ to be redirected to the backend server at http://localhost:8000. The path and query parameters will be passed on as-is (path rewriting is currently not supported).\n\n## Config example\n\nThis includes all fields, mandatory or not.\n\n````toml\n[application]\n\n# App name\nname = \"project_name\"\n\n# The Dioxus platform to default to\ndefault_platform = \"web\"\n\n# `build` & `serve` output path\nout_dir = \"dist\"\n\n# The static resource path\nasset_dir = \"public\"\n\n[web.app]\n\n# HTML title tag content\ntitle = \"project_name\"\n\n[web.watcher]\n\n# When watcher is triggered, regenerate the `index.html`\nreload_html = true\n\n# Which files or dirs will be monitored\nwatch_path = [\"src\", \"public\"]\n\n# Include style or script assets\n[web.resource]\n\n# CSS style file\nstyle = []\n\n# Javascript code file\nscript = []\n\n[web.resource.dev]\n\n# Same as [web.resource], but for development servers\n\n# CSS style file\nstyle = []\n\n# JavaScript files\nscript = []\n\n[[web.proxy]]\nbackend = \"http://localhost:8000/api/\"\n````" + } + 28usize => { + "# Fullstack development\n\nDioxus Fullstack contains helpers for:\n\n* Incremental, static, and server side rendering\n* Hydrating your application on the Client\n* Communicating between a server and a client\n\nThis guide will teach you everything you need to know about how to use the utilities in Dioxus fullstack to create amazing fullstack applications.\n\n > \n > In addition to this guide, you can find more examples of full-stack apps and information about how to integrate with other frameworks and desktop renderers in the [dioxus-fullstack examples directory](https://github.com/DioxusLabs/dioxus/tree/v0.5/packages/fullstack/examples)." + } + 12usize => { + "# Hooks and component state\n\nSo far, our components have had no state like a normal Rust function. 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 opened a drop-down and render different things accordingly.\n\nHooks allow us to create state in our components. Hooks are Rust functions you call in a constant order in a component that add additional functionality to the component.\n\nDioxus provides many built-in hooks, but if those hooks don't fit your specific use case, you also can [create your own hook](../cookbook/state/custom_hooks/index.md)\n\n## use_signal hook\n\n[`use_signal`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_signal.html) is one of the simplest hooks.\n\n* You provide a closure that determines the initial value: `let mut count = use_signal(|| 0);`\n* `use_signal` gives you the current value, and a way to write to the value\n* When the value updates, `use_signal` makes the component re-render (along with any other component that references it), and then provides you with the new value.\n\nFor example, you might have seen the counter example, in which state (a number) is tracked using the `use_signal` hook:\n\n````rust, no_run@hooks_counter.rs\npub fn App() -> Element {\n // count will be initialized to 0 the first time the component is rendered\n let mut count = use_signal(|| 0);\n\n rsx! {\n h1 { \"High-Five counter: {count}\" }\n button { onclick: move |_| count += 1, \"Up high!\" }\n button { onclick: move |_| count -= 1, \"Down low!\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n hooks_counter::App {}\n}\n````\n\nEvery 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 – describe what you want in terms of the state, and Dioxus will take care of the rest!\n\n > \n > `use_signal` returns your value wrapped in a smart pointer of type [`Signal`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Signal.html) that is `Copy`. This is why you can both read the value and update it, even within an event handler.\n\nYou can use multiple hooks in the same component if you want:\n\n````rust, no_run@hooks_counter_two_state.rs\npub fn App() -> Element {\n let mut count_a = use_signal(|| 0);\n let mut count_b = use_signal(|| 0);\n\n rsx! {\n h1 { \"Counter_a: {count_a}\" }\n button { onclick: move |_| count_a += 1, \"a++\" }\n button { onclick: move |_| count_a -= 1, \"a--\" }\n h1 { \"Counter_b: {count_b}\" }\n button { onclick: move |_| count_b += 1, \"b++\" }\n button { onclick: move |_| count_b -= 1, \"b--\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n hooks_counter_two_state::App {}\n}\n````\n\nYou can also use `use_signal` to store more complex state, like a Vec. You can read and write to the state with the `read` and `write` methods:\n\n````rust, no_run@hooks_use_signal.rs\npub fn App() -> Element {\n let mut list = use_signal(Vec::new);\n\n rsx! {\n p { \"Current list: {list:?}\" }\n button {\n onclick: move |event| {\n let list_len = list.len();\n list.push(list_len);\n list.push(list_len);\n },\n \"Add two elements!\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n hooks_use_signal::App {}\n}\n````\n\n## Rules of hooks\n\nThe 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 hidden scope that is associated with the component.\n\nBut how can Dioxus differentiate between multiple hooks in the same component? As you saw in the second example, both `use_signal` functions were called with the same parameters, so how come they can return different things when the counters are different?\n\n````rust, no_run@hooks_counter_two_state.rs\nlet mut count_a = use_signal(|| 0);\nlet mut count_b = use_signal(|| 0);\n````\n\nThis is only possible because the two hooks are always called in the same order, so Dioxus knows which is which. Because the order you call hooks matters, you must follow certain rules when using hooks:\n\n1. Hooks may be only used in components or other hooks (we'll get to that later).\n1. On every call to a component function.\n1. The same hooks must be called (except in the case of early returns, as explained later in the [Error Handling chapter](../cookbook/error_handling.md)).\n1. In the same order.\n1. Hook names should start with `use_` so you don't accidentally confuse them with regular\n functions (`use_signal()`, `use_effect()`, `use_resource()`, etc...).\n\nThese rules mean that there are certain things you can't do with hooks:\n\n### No hooks in conditionals\n\n````rust, no_run@hooks_bad.rs\n// ❌ don't call hooks in conditionals!\n// We must ensure that the same hooks will be called every time\n// But `if` statements only run if the conditional is true!\n// So we might violate rule 2.\nif you_are_happy && you_know_it {\n let something = use_signal(|| \"hands\");\n println!(\"clap your {something}\")\n}\n\n// ✅ instead, *always* call use_signal\n// You can put other stuff in the conditional though\nlet something = use_signal(|| \"hands\");\nif you_are_happy && you_know_it {\n println!(\"clap your {something}\")\n}\n````\n\n### No hooks in closures\n\n````rust, no_run@hooks_bad.rs\n// ❌ don't call hooks inside closures!\n// We can't guarantee that the closure, if used, will be called in the same order every time\nlet _a = || {\n let b = use_signal(|| 0);\n b()\n};\n\n// ✅ instead, move hook `b` outside\nlet b = use_signal(|| 0);\nlet _a = || b();\n````\n\n### No hooks in loops\n\n````rust, no_run@hooks_bad.rs\n// `names` is a Vec<&str>\n\n// ❌ Do not use hooks in loops!\n// In this case, if the length of the Vec changes, we break rule 2\nfor _name in &names {\n let is_selected = use_signal(|| false);\n println!(\"selected: {is_selected}\");\n}\n\n// ✅ Instead, use a hashmap with use_signal\nlet selection_map = use_signal(HashMap::<&str, bool>::new);\n\nfor name in &names {\n let is_selected = selection_map.read()[name];\n println!(\"selected: {is_selected}\");\n}\n````\n\n## Additional resources\n\n* [dioxus_hooks API docs](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/)\n* [dioxus_hooks source code](https://github.com/DioxusLabs/dioxus/tree/v0.5/packages/hooks)" + } + 27usize => { + "# Liveview\n\nLiveview allows apps to *run* on the server and *render* in the browser. It uses WebSockets to communicate between the server and the browser.\n\nExamples:\n\n* [Simple Example](https://github.com/DioxusLabs/dioxus/tree/v0.5/packages/liveview/examples/axum.rs)\n\n## Support\n\nDioxus liveview will be migrated to [dioxus-fullstack](./fullstack/index.md) in a future release. Once this migration occurs, you may need to update your code. We plan for this migration to be minimal.\n\nLiveview is currently limited in capability when compared to the Web platform. Liveview apps run on the server in 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.\n\n## Router Integration\n\nCurrently, the Dioxus router does not integrate with the browser history in the liveview renderer. If you are interested in contributing this feature to Dioxus this issue is tracked [here](https://github.com/DioxusLabs/dioxus/issues/1038).\n\n## Managing Latency\n\nLiveview makes it incredibly convenient to talk to your server from the client, but there are some downsides. Mainly in Dioxus Liveview every interaction goes through the server by default.\n\nBecause of this, with the liveview renderer you need to be very deliberate about managing latency. Events that would be fast enough on other renderers like [controlled inputs](../reference/user_input.md), can be frustrating to use in the liveview renderer.\n\nTo get around this issue you can inject bits of javascript in your liveview application. If you use a raw attribute as a listener, you can inject some javascript that will be run when the event is triggered:\n\n````rust\nrsx! {\n div {\n input {\n \"oninput\": \"console.log('input changed!')\"\n }\n }\n}\n````" + } + 44usize => { + "# Layouts\n\nLayouts allow you to wrap all child routes in a component. This can be useful when creating something like a header that will be used in many different routes.\n\n[`Outlet`] tells the router where to render content in layouts. In the following example,\nthe Index will be rendered within the [`Outlet`].\n\nThis page is built with the Dioxus. It uses Layouts in several different places. Here is an outline of how layouts are used on the current page. Hover over different layouts to see what elements they are on the page.\n\n````inject-dioxus\nLayoutsExplanation {}\n````\n\nHere is a more complete example of a layout wrapping the body of a page.\n\n````rust@outlet.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(Wrapper)]\n #[route(\"/\")]\n Index {},\n}\n\n#[component]\nfn Wrapper() -> Element {\n rsx! {\n header { \"header\" }\n // The index route will be rendered here\n Outlet:: {}\n footer { \"footer\" }\n }\n}\n\n#[component]\nfn Index() -> Element {\n rsx! { h1 { \"Index\" } }\n}\n````\n\nThe example above will output the following HTML (line breaks added for\nreadability):\n\n````html\n
header
\n

Index

\n
footer
\n````\n\n## Layouts with dynamic segments\n\nYou can combine layouts with [nested routes](./routes/nested.md) to create dynamic layouts with content that changes based on the current route.\n\nJust like routes, layouts components must accept a prop for each dynamic segment in the route. For example, if you have a route with a dynamic segment like `/:name`, your layout component must accept a `name` prop:\n\n````rust@outlet.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[nest(\"/:name\")]\n #[layout(Wrapper)]\n #[route(\"/\")]\n Index {\n name: String,\n },\n}\n\n#[component]\nfn Wrapper(name: String) -> Element {\n rsx! {\n header { \"Welcome {name}!\" }\n // The index route will be rendered here\n Outlet:: {}\n footer { \"footer\" }\n }\n}\n\n#[component]\nfn Index(name: String) -> Element {\n rsx! { h1 { \"This is a homepage for {name}\" } }\n}\n````\n\nOr to get the full route, you can use the `use_route` hook.\n\n````rust@outlet.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(Wrapper)]\n #[route(\"/:name\")]\n Index {\n name: String,\n },\n}\n\n#[component]\nfn Wrapper() -> Element {\n let full_route = use_route::();\n rsx! {\n header { \"Welcome to {full_route}!\" }\n // The index route will be rendered here\n Outlet:: {}\n footer { \"footer\" }\n }\n}\n\n#[component]\nfn Index(name: String) -> Element {\n rsx! { h1 { \"This is a homepage for {name}\" } }\n}\n````" + } + 2usize => { + "# Dioxus Guide\n\n## Introduction\n\nIn this guide, you'll learn to use Dioxus to build user interfaces that run anywhere. We will recreate the hackernews homepage in Dioxus:\n\n````inject-dioxus\nDemoFrame {\n hackernews_complete::App {}\n}\n````\n\nThis guide serves a very brief overview of Dioxus. Throughout the guide, there will be links to the [reference](../reference/index.md) with more details about specific concepts." + } + _ => panic!("Invalid page ID:"), + } + } + pub fn sections(&self) -> &'static [use_mdbook::mdbook_shared::Section] { + &self.page().sections + } + pub fn page(&self) -> &'static use_mdbook::mdbook_shared::Page { + LAZY_BOOK.get_page(self) + } + pub fn page_id(&self) -> use_mdbook::mdbook_shared::PageId { + match self { + BookRoute::Index { .. } => use_mdbook::mdbook_shared::PageId(0usize), + BookRoute::GettingStartedIndex { .. } => use_mdbook::mdbook_shared::PageId(1usize), + BookRoute::GuideIndex { .. } => use_mdbook::mdbook_shared::PageId(2usize), + BookRoute::GuideYourFirstComponent { .. } => use_mdbook::mdbook_shared::PageId(3usize), + BookRoute::GuideState { .. } => use_mdbook::mdbook_shared::PageId(4usize), + BookRoute::GuideDataFetching { .. } => use_mdbook::mdbook_shared::PageId(5usize), + BookRoute::GuideFullCode { .. } => use_mdbook::mdbook_shared::PageId(6usize), + BookRoute::ReferenceIndex { .. } => use_mdbook::mdbook_shared::PageId(7usize), + BookRoute::ReferenceRsx { .. } => use_mdbook::mdbook_shared::PageId(8usize), + BookRoute::ReferenceComponents { .. } => use_mdbook::mdbook_shared::PageId(9usize), + BookRoute::ReferenceComponentProps { .. } => use_mdbook::mdbook_shared::PageId(10usize), + BookRoute::ReferenceEventHandlers { .. } => use_mdbook::mdbook_shared::PageId(11usize), + BookRoute::ReferenceHooks { .. } => use_mdbook::mdbook_shared::PageId(12usize), + BookRoute::ReferenceUserInput { .. } => use_mdbook::mdbook_shared::PageId(13usize), + BookRoute::ReferenceContext { .. } => use_mdbook::mdbook_shared::PageId(14usize), + BookRoute::ReferenceDynamicRendering { .. } => { + use_mdbook::mdbook_shared::PageId(15usize) + } + BookRoute::ReferenceRouter { .. } => use_mdbook::mdbook_shared::PageId(16usize), + BookRoute::ReferenceUseResource { .. } => use_mdbook::mdbook_shared::PageId(17usize), + BookRoute::ReferenceUseCoroutine { .. } => use_mdbook::mdbook_shared::PageId(18usize), + BookRoute::ReferenceSpawn { .. } => use_mdbook::mdbook_shared::PageId(19usize), + BookRoute::ReferenceAssets { .. } => use_mdbook::mdbook_shared::PageId(20usize), + BookRoute::ReferenceChoosingAWebRenderer { .. } => { + use_mdbook::mdbook_shared::PageId(21usize) + } + BookRoute::ReferenceDesktopIndex { .. } => use_mdbook::mdbook_shared::PageId(22usize), + BookRoute::ReferenceMobileIndex { .. } => use_mdbook::mdbook_shared::PageId(23usize), + BookRoute::ReferenceMobileApis { .. } => use_mdbook::mdbook_shared::PageId(24usize), + BookRoute::ReferenceWebIndex { .. } => use_mdbook::mdbook_shared::PageId(25usize), + BookRoute::ReferenceSsr { .. } => use_mdbook::mdbook_shared::PageId(26usize), + BookRoute::ReferenceLiveview { .. } => use_mdbook::mdbook_shared::PageId(27usize), + BookRoute::ReferenceFullstackIndex { .. } => use_mdbook::mdbook_shared::PageId(28usize), + BookRoute::ReferenceFullstackServerFunctions { .. } => { + use_mdbook::mdbook_shared::PageId(29usize) + } + BookRoute::ReferenceFullstackExtractors { .. } => { + use_mdbook::mdbook_shared::PageId(30usize) + } + BookRoute::ReferenceFullstackMiddleware { .. } => { + use_mdbook::mdbook_shared::PageId(31usize) + } + BookRoute::ReferenceFullstackAuthentication { .. } => { + use_mdbook::mdbook_shared::PageId(32usize) + } + BookRoute::ReferenceFullstackRouting { .. } => { + use_mdbook::mdbook_shared::PageId(33usize) + } + BookRoute::RouterIndex { .. } => use_mdbook::mdbook_shared::PageId(34usize), + BookRoute::RouterExampleIndex { .. } => use_mdbook::mdbook_shared::PageId(35usize), + BookRoute::RouterExampleFirstRoute { .. } => use_mdbook::mdbook_shared::PageId(36usize), + BookRoute::RouterExampleBuildingANest { .. } => { + use_mdbook::mdbook_shared::PageId(37usize) + } + BookRoute::RouterExampleNavigationTargets { .. } => { + use_mdbook::mdbook_shared::PageId(38usize) + } + BookRoute::RouterExampleRedirectionPerfection { .. } => { + use_mdbook::mdbook_shared::PageId(39usize) + } + BookRoute::RouterExampleFullCode { .. } => use_mdbook::mdbook_shared::PageId(40usize), + BookRoute::RouterReferenceIndex { .. } => use_mdbook::mdbook_shared::PageId(41usize), + BookRoute::RouterReferenceRoutesIndex { .. } => { + use_mdbook::mdbook_shared::PageId(42usize) + } + BookRoute::RouterReferenceRoutesNested { .. } => { + use_mdbook::mdbook_shared::PageId(43usize) + } + BookRoute::RouterReferenceLayouts { .. } => use_mdbook::mdbook_shared::PageId(44usize), + BookRoute::RouterReferenceNavigationIndex { .. } => { + use_mdbook::mdbook_shared::PageId(45usize) + } + BookRoute::RouterReferenceNavigationProgrammatic { .. } => { + use_mdbook::mdbook_shared::PageId(46usize) + } + BookRoute::RouterReferenceHistoryProviders { .. } => { + use_mdbook::mdbook_shared::PageId(47usize) + } + BookRoute::RouterReferenceHistoryButtons { .. } => { + use_mdbook::mdbook_shared::PageId(48usize) + } + BookRoute::RouterReferenceRoutingUpdateCallback { .. } => { + use_mdbook::mdbook_shared::PageId(49usize) + } + BookRoute::CookbookIndex { .. } => use_mdbook::mdbook_shared::PageId(50usize), + BookRoute::CookbookPublishing { .. } => use_mdbook::mdbook_shared::PageId(51usize), + BookRoute::CookbookAntipatterns { .. } => use_mdbook::mdbook_shared::PageId(52usize), + BookRoute::CookbookErrorHandling { .. } => use_mdbook::mdbook_shared::PageId(53usize), + BookRoute::CookbookIntegrationsIndex { .. } => { + use_mdbook::mdbook_shared::PageId(54usize) + } + BookRoute::CookbookIntegrationsLogging { .. } => { + use_mdbook::mdbook_shared::PageId(55usize) + } + BookRoute::CookbookIntegrationsInternationalization { .. } => { + use_mdbook::mdbook_shared::PageId(56usize) + } + BookRoute::CookbookStateIndex { .. } => use_mdbook::mdbook_shared::PageId(57usize), + BookRoute::CookbookStateExternalIndex { .. } => { + use_mdbook::mdbook_shared::PageId(58usize) + } + BookRoute::CookbookStateCustomHooksIndex { .. } => { + use_mdbook::mdbook_shared::PageId(59usize) + } + BookRoute::CookbookTesting { .. } => use_mdbook::mdbook_shared::PageId(60usize), + BookRoute::CookbookTailwind { .. } => use_mdbook::mdbook_shared::PageId(61usize), + BookRoute::CookbookCustomRenderer { .. } => use_mdbook::mdbook_shared::PageId(62usize), + BookRoute::CookbookOptimizing { .. } => use_mdbook::mdbook_shared::PageId(63usize), + BookRoute::CliIndex { .. } => use_mdbook::mdbook_shared::PageId(64usize), + BookRoute::CliCreating { .. } => use_mdbook::mdbook_shared::PageId(65usize), + BookRoute::CliConfigure { .. } => use_mdbook::mdbook_shared::PageId(66usize), + BookRoute::CliTranslate { .. } => use_mdbook::mdbook_shared::PageId(67usize), + BookRoute::ContributingIndex { .. } => use_mdbook::mdbook_shared::PageId(68usize), + BookRoute::ContributingProjectStructure { .. } => { + use_mdbook::mdbook_shared::PageId(69usize) + } + BookRoute::ContributingGuidingPrinciples { .. } => { + use_mdbook::mdbook_shared::PageId(70usize) + } + BookRoute::ContributingRoadmap { .. } => use_mdbook::mdbook_shared::PageId(71usize), + BookRoute::MigrationIndex { .. } => use_mdbook::mdbook_shared::PageId(72usize), + BookRoute::MigrationHooks { .. } => use_mdbook::mdbook_shared::PageId(73usize), + BookRoute::MigrationState { .. } => use_mdbook::mdbook_shared::PageId(74usize), + BookRoute::MigrationFermi { .. } => use_mdbook::mdbook_shared::PageId(75usize), + BookRoute::MigrationProps { .. } => use_mdbook::mdbook_shared::PageId(76usize), + } + } +} +impl Default for BookRoute { + fn default() -> Self { + BookRoute::Index { + section: IndexSection::Empty, + } + } +} +pub static LAZY_BOOK: use_mdbook::Lazy> = + use_mdbook::Lazy::new(|| { + let mut page_id_mapping = ::std::collections::HashMap::new(); + let mut pages = Vec::new(); + pages.push((0usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Introduction".to_string(), + url: BookRoute::Index { + section: IndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Introduction".to_string(), + id: "introduction".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Features".to_string(), + id: "features".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Multiplatform".to_string(), + id: "multiplatform".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Stability".to_string(), + id: "stability".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(0usize), + } + })); + page_id_mapping.insert( + BookRoute::Index { + section: IndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(0usize), + ); + pages.push((1usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Getting Started".to_string(), + url: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Getting Started".to_string(), + id: "getting-started".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Prerequisites".to_string(), + id: "prerequisites".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "An Editor".to_string(), + id: "an-editor".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Rust".to_string(), + id: "rust".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Platform-specific dependencies".to_string(), + id: "platform-specific-dependencies".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dioxus CLI".to_string(), + id: "dioxus-cli".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Create a new project".to_string(), + id: "create-a-new-project".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Running the project".to_string(), + id: "running-the-project".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conclusion".to_string(), + id: "conclusion".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(1usize), + } + })); + page_id_mapping.insert( + BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(1usize), + ); + pages.push((2usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Guide".to_string(), + url: BookRoute::GuideIndex { + section: GuideIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Dioxus Guide".to_string(), + id: "dioxus-guide".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Introduction".to_string(), + id: "introduction".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(2usize), + } + })); + page_id_mapping.insert( + BookRoute::GuideIndex { + section: GuideIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(2usize), + ); + pages.push((3usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Your First Component".to_string(), + url: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Your First Component".to_string(), + id: "your-first-component".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup".to_string(), + id: "setup".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Describing the UI".to_string(), + id: "describing-the-ui".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dynamic Text".to_string(), + id: "dynamic-text".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating Elements".to_string(), + id: "creating-elements".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setting Attributes".to_string(), + id: "setting-attributes".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating a Component".to_string(), + id: "creating-a-component".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating Props".to_string(), + id: "creating-props".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Cleaning Up Our Interface".to_string(), + id: "cleaning-up-our-interface".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(3usize), + } + })); + page_id_mapping.insert( + BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(3usize), + ); + pages.push((4usize, { + ::use_mdbook::mdbook_shared::Page { + title: "State".to_string(), + url: BookRoute::GuideState { + section: GuideStateSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Interactivity".to_string(), + id: "interactivity".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating a Preview".to_string(), + id: "creating-a-preview".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Event Handlers".to_string(), + id: "event-handlers".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "State".to_string(), + id: "state".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The Rules of Hooks".to_string(), + id: "the-rules-of-hooks".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No Hooks in Conditionals".to_string(), + id: "no-hooks-in-conditionals".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No Hooks in Closures".to_string(), + id: "no-hooks-in-closures".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No Hooks in Loops".to_string(), + id: "no-hooks-in-loops".to_string(), + level: 4usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(4usize), + } + })); + page_id_mapping.insert( + BookRoute::GuideState { + section: GuideStateSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(4usize), + ); + pages.push((5usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Data Fetching".to_string(), + url: BookRoute::GuideDataFetching { + section: GuideDataFetchingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Fetching Data".to_string(), + id: "fetching-data".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Defining the API".to_string(), + id: "defining-the-api".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Working with Async".to_string(), + id: "working-with-async".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Lazily Fetching Data".to_string(), + id: "lazily-fetching-data".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(5usize), + } + })); + page_id_mapping.insert( + BookRoute::GuideDataFetching { + section: GuideDataFetchingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(5usize), + ); + pages.push((6usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Full Code".to_string(), + url: BookRoute::GuideFullCode { + section: GuideFullCodeSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Conclusion".to_string(), + id: "conclusion".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Challenges".to_string(), + id: "challenges".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The full code for the hacker news project".to_string(), + id: "the-full-code-for-the-hacker-news-project".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(6usize), + } + })); + page_id_mapping.insert( + BookRoute::GuideFullCode { + section: GuideFullCodeSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(6usize), + ); + pages.push((7usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Reference".to_string(), + url: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Dioxus Reference".to_string(), + id: "dioxus-reference".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Rendering".to_string(), + id: "rendering".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "State".to_string(), + id: "state".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Platforms".to_string(), + id: "platforms".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(7usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(7usize), + ); + pages.push((8usize, { + ::use_mdbook::mdbook_shared::Page { + title: "RSX".to_string(), + url: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Describing the UI".to_string(), + id: "describing-the-ui".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "RSX Features".to_string(), + id: "rsx-features".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Attributes".to_string(), + id: "attributes".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conditional Attributes".to_string(), + id: "conditional-attributes".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom Attributes".to_string(), + id: "custom-attributes".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Special Attributes".to_string(), + id: "special-attributes".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The HTML Escape Hatch".to_string(), + id: "the-html-escape-hatch".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Boolean Attributes".to_string(), + id: "boolean-attributes".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Interpolation".to_string(), + id: "interpolation".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Children".to_string(), + id: "children".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Fragments".to_string(), + id: "fragments".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Expressions".to_string(), + id: "expressions".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Loops".to_string(), + id: "loops".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "If statements".to_string(), + id: "if-statements".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(8usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(8usize), + ); + pages.push((9usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Components".to_string(), + url: BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Components".to_string(), + id: "components".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(9usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(9usize), + ); + pages.push((10usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Props".to_string(), + url: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Component Props".to_string(), + id: "component-props".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "derive(Props)".to_string(), + id: "deriveprops".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Prop Options".to_string(), + id: "prop-options".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Optional Props".to_string(), + id: "optional-props".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Explicitly Required Option".to_string(), + id: "explicitly-required-option".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Default Props".to_string(), + id: "default-props".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Automatic Conversion with into".to_string(), + id: "automatic-conversion-with-into".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The component macro".to_string(), + id: "the-component-macro".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Component Children".to_string(), + id: "component-children".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The children field".to_string(), + id: "the-children-field".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(10usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(10usize), + ); + pages.push((11usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Event Handlers".to_string(), + url: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Event Handlers".to_string(), + id: "event-handlers".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The Event object".to_string(), + id: "the-event-object".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Event propagation".to_string(), + id: "event-propagation".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Prevent Default".to_string(), + id: "prevent-default".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Handler Props".to_string(), + id: "handler-props".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Async Event Handlers".to_string(), + id: "async-event-handlers".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom Data".to_string(), + id: "custom-data".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(11usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(11usize), + ); + pages.push((12usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Hooks".to_string(), + url: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Hooks and component state".to_string(), + id: "hooks-and-component-state".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "use_signal hook".to_string(), + id: "use-signal-hook".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Rules of hooks".to_string(), + id: "rules-of-hooks".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No hooks in conditionals".to_string(), + id: "no-hooks-in-conditionals".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No hooks in closures".to_string(), + id: "no-hooks-in-closures".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "No hooks in loops".to_string(), + id: "no-hooks-in-loops".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Additional resources".to_string(), + id: "additional-resources".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(12usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceHooks { + section: ReferenceHooksSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(12usize), + ); + pages.push((13usize, { + ::use_mdbook::mdbook_shared::Page { + title: "User Input".to_string(), + url: BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "User Input".to_string(), + id: "user-input".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Controlled Inputs".to_string(), + id: "controlled-inputs".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Uncontrolled Inputs".to_string(), + id: "uncontrolled-inputs".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Handling files".to_string(), + id: "handling-files".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(13usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(13usize), + ); + pages.push((14usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Context".to_string(), + url: BookRoute::ReferenceContext { + section: ReferenceContextSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Sharing State".to_string(), + id: "sharing-state".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Lifting State".to_string(), + id: "lifting-state".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Using Shared State".to_string(), + id: "using-shared-state".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(14usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceContext { + section: ReferenceContextSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(14usize), + ); + pages.push((15usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Dynamic Rendering".to_string(), + url: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Dynamic Rendering".to_string(), + id: "dynamic-rendering".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conditional Rendering".to_string(), + id: "conditional-rendering".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Improving the if-else Example".to_string(), + id: "improving-the-if-else-example".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Inspecting Element props".to_string(), + id: "inspecting-element-props".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Rendering Nothing".to_string(), + id: "rendering-nothing".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Rendering Lists".to_string(), + id: "rendering-lists".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Inline for loops".to_string(), + id: "inline-for-loops".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The key Attribute".to_string(), + id: "the-key-attribute".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(15usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(15usize), + ); + pages.push((16usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Routing".to_string(), + url: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Router".to_string(), + id: "router".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "What is it?".to_string(), + id: "what-is-it".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Using the router".to_string(), + id: "using-the-router".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Links".to_string(), + id: "links".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "More reading".to_string(), + id: "more-reading".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(16usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceRouter { + section: ReferenceRouterSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(16usize), + ); + pages.push((17usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Resource".to_string(), + url: BookRoute::ReferenceUseResource { + section: ReferenceUseResourceSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Resource".to_string(), + id: "resource".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Restarting the Future".to_string(), + id: "restarting-the-future".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dependencies".to_string(), + id: "dependencies".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(17usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceUseResource { + section: ReferenceUseResourceSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(17usize), + ); + pages.push((18usize, { + ::use_mdbook::mdbook_shared::Page { + title: "UseCoroutine".to_string(), + url: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Coroutines".to_string(), + id: "coroutines".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "use_coroutine".to_string(), + id: "use-coroutine".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Yielding Values".to_string(), + id: "yielding-values".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Sending Values".to_string(), + id: "sending-values".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Automatic injection into the Context API".to_string(), + id: "automatic-injection-into-the-context-api".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(18usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(18usize), + ); + pages.push((19usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Spawn".to_string(), + url: BookRoute::ReferenceSpawn { + section: ReferenceSpawnSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Spawning Futures".to_string(), + id: "spawning-futures".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Spawning Tokio Tasks".to_string(), + id: "spawning-tokio-tasks".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(19usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceSpawn { + section: ReferenceSpawnSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(19usize), + ); + pages.push((20usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Assets".to_string(), + url: BookRoute::ReferenceAssets { + section: ReferenceAssetsSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Assets".to_string(), + id: "assets".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Including images".to_string(), + id: "including-images".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Including arbitrary files".to_string(), + id: "including-arbitrary-files".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Including stylesheets".to_string(), + id: "including-stylesheets".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conclusion".to_string(), + id: "conclusion".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(20usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceAssets { + section: ReferenceAssetsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(20usize), + ); + pages.push((21usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Choosing A Web Renderer".to_string(), + url: BookRoute::ReferenceChoosingAWebRenderer { + section: ReferenceChoosingAWebRendererSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Choosing a web renderer".to_string(), + id: "choosing-a-web-renderer".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dioxus Liveview".to_string(), + id: "dioxus-liveview".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dioxus Web".to_string(), + id: "dioxus-web".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dioxus Fullstack".to_string(), + id: "dioxus-fullstack".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(21usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceChoosingAWebRenderer { + section: ReferenceChoosingAWebRendererSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(21usize), + ); + pages.push((22usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Desktop".to_string(), + url: BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Desktop".to_string(), + id: "desktop".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Examples".to_string(), + id: "examples".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Running Javascript".to_string(), + id: "running-javascript".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom Assets".to_string(), + id: "custom-assets".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Integrating with Wry".to_string(), + id: "integrating-with-wry".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(22usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(22usize), + ); + pages.push((23usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Mobile".to_string(), + url: BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Mobile App".to_string(), + id: "mobile-app".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Support".to_string(), + id: "support".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Getting Set up".to_string(), + id: "getting-set-up".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setting up dependencies".to_string(), + id: "setting-up-dependencies".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Android Dependencies".to_string(), + id: "android-dependencies".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "IOS Dependencies".to_string(), + id: "ios-dependencies".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setting up your project".to_string(), + id: "setting-up-your-project".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Running".to_string(), + id: "running".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Android".to_string(), + id: "android".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "IOS".to_string(), + id: "ios".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(23usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(23usize), + ); + pages.push((24usize, { + ::use_mdbook::mdbook_shared::Page { + title: "APIs".to_string(), + url: BookRoute::ReferenceMobileApis { + section: ReferenceMobileApisSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Mobile".to_string(), + id: "mobile".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Running Javascript".to_string(), + id: "running-javascript".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom Assets".to_string(), + id: "custom-assets".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Integrating with Wry".to_string(), + id: "integrating-with-wry".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(24usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceMobileApis { + section: ReferenceMobileApisSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(24usize), + ); + pages.push((25usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Web".to_string(), + url: BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Web".to_string(), + id: "web".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Support".to_string(), + id: "support".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Running Javascript".to_string(), + id: "running-javascript".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Customizing Index Template".to_string(), + id: "customizing-index-template".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(25usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(25usize), + ); + pages.push((26usize, { + ::use_mdbook::mdbook_shared::Page { + title: "SSR".to_string(), + url: BookRoute::ReferenceSsr { + section: ReferenceSsrSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Server-Side Rendering".to_string(), + id: "server-side-rendering".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup".to_string(), + id: "setup".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Multithreaded Support".to_string(), + id: "multithreaded-support".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(26usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceSsr { + section: ReferenceSsrSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(26usize), + ); + pages.push((27usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Liveview".to_string(), + url: BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Liveview".to_string(), + id: "liveview".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Support".to_string(), + id: "support".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Router Integration".to_string(), + id: "router-integration".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Managing Latency".to_string(), + id: "managing-latency".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(27usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(27usize), + ); + pages.push((28usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Fullstack".to_string(), + url: BookRoute::ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Fullstack development".to_string(), + id: "fullstack-development".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(28usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(28usize), + ); + pages.push((29usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Server Functions".to_string(), + url: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Communicating with the server".to_string(), + id: "communicating-with-the-server".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Cached data fetching".to_string(), + id: "cached-data-fetching".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Running the client with dioxus-desktop".to_string(), + id: "running-the-client-with-dioxus-desktop".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Client code".to_string(), + id: "client-code".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Server code".to_string(), + id: "server-code".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(29usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(29usize), + ); + pages.push((30usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Extractors".to_string(), + url: BookRoute::ReferenceFullstackExtractors { + section: ReferenceFullstackExtractorsSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Extractors".to_string(), + id: "extractors".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(30usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceFullstackExtractors { + section: ReferenceFullstackExtractorsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(30usize), + ); + pages.push((31usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Middleware".to_string(), + url: BookRoute::ReferenceFullstackMiddleware { + section: ReferenceFullstackMiddlewareSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Middleware".to_string(), + id: "middleware".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(31usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceFullstackMiddleware { + section: ReferenceFullstackMiddlewareSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(31usize), + ); + pages.push((32usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Authentication".to_string(), + url: BookRoute::ReferenceFullstackAuthentication { + section: ReferenceFullstackAuthenticationSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Authentication".to_string(), + id: "authentication".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(32usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceFullstackAuthentication { + section: ReferenceFullstackAuthenticationSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(32usize), + ); + pages.push((33usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Routing".to_string(), + url: BookRoute::ReferenceFullstackRouting { + section: ReferenceFullstackRoutingSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Routing".to_string(), + id: "routing".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(33usize), + } + })); + page_id_mapping.insert( + BookRoute::ReferenceFullstackRouting { + section: ReferenceFullstackRoutingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(33usize), + ); + pages.push((34usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Router".to_string(), + url: BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Introduction".to_string(), + id: "introduction".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(34usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(34usize), + ); + pages.push((35usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Example Project".to_string(), + url: BookRoute::RouterExampleIndex { + section: RouterExampleIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Overview".to_string(), + id: "overview".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "You'll learn how to".to_string(), + id: "youll-learn-how-to".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(35usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterExampleIndex { + section: RouterExampleIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(35usize), + ); + pages.push((36usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Creating Our First Route".to_string(), + url: BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Creating Our First Route".to_string(), + id: "creating-our-first-route".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Fundamentals".to_string(), + id: "fundamentals".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating Routes".to_string(), + id: "creating-routes".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Fallback Route".to_string(), + id: "fallback-route".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conclusion".to_string(), + id: "conclusion".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(36usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(36usize), + ); + pages.push((37usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Building a Nest".to_string(), + url: BookRoute::RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Building a Nest".to_string(), + id: "building-a-nest".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Site Navigation".to_string(), + id: "site-navigation".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "URL Parameters and Nested Routes".to_string(), + id: "url-parameters-and-nested-routes".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conclusion".to_string(), + id: "conclusion".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(37usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(37usize), + ); + pages.push((38usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Navigation Targets".to_string(), + url: BookRoute::RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Navigation Targets".to_string(), + id: "navigation-targets".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "What is a navigation target?".to_string(), + id: "what-is-a-navigation-target".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "External navigation".to_string(), + id: "external-navigation".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(38usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(38usize), + ); + pages.push((39usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Redirection Perfection".to_string(), + url: BookRoute::RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Redirection Perfection".to_string(), + id: "redirection-perfection".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Creating Redirects".to_string(), + id: "creating-redirects".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conclusion".to_string(), + id: "conclusion".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Challenges".to_string(), + id: "challenges".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(39usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(39usize), + ); + pages.push((40usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Full Code".to_string(), + url: BookRoute::RouterExampleFullCode { + section: RouterExampleFullCodeSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Full Code".to_string(), + id: "full-code".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(40usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterExampleFullCode { + section: RouterExampleFullCodeSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(40usize), + ); + pages.push((41usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Reference".to_string(), + url: BookRoute::RouterReferenceIndex { + section: RouterReferenceIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Adding the router to your application".to_string(), + id: "adding-the-router-to-your-application".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(41usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceIndex { + section: RouterReferenceIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(41usize), + ); + pages.push((42usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Defining Routes".to_string(), + url: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Defining Routes".to_string(), + id: "defining-routes".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Route Segments".to_string(), + id: "route-segments".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Static segments".to_string(), + id: "static-segments".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dynamic Segments".to_string(), + id: "dynamic-segments".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Catch All Segments".to_string(), + id: "catch-all-segments".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Query Segments".to_string(), + id: "query-segments".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(42usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(42usize), + ); + pages.push((43usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Nested Routes".to_string(), + url: BookRoute::RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Nested Routes".to_string(), + id: "nested-routes".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Nesting".to_string(), + id: "nesting".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(43usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(43usize), + ); + pages.push((44usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Layouts".to_string(), + url: BookRoute::RouterReferenceLayouts { + section: RouterReferenceLayoutsSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Layouts".to_string(), + id: "layouts".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Layouts with dynamic segments".to_string(), + id: "layouts-with-dynamic-segments".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(44usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceLayouts { + section: RouterReferenceLayoutsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(44usize), + ); + pages.push((45usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Navigation".to_string(), + url: BookRoute::RouterReferenceNavigationIndex { + section: RouterReferenceNavigationIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Links & Navigation".to_string(), + id: "links--navigation".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(45usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceNavigationIndex { + section: RouterReferenceNavigationIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(45usize), + ); + pages.push((46usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Programmatic Navigation".to_string(), + url: BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Programmatic Navigation".to_string(), + id: "programmatic-navigation".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Using a Navigator".to_string(), + id: "using-a-navigator".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "External Navigation Targets".to_string(), + id: "external-navigation-targets".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(46usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(46usize), + ); + pages.push((47usize, { + ::use_mdbook::mdbook_shared::Page { + title: "History Providers".to_string(), + url: BookRoute::RouterReferenceHistoryProviders { + section: RouterReferenceHistoryProvidersSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "History Providers".to_string(), + id: "history-providers".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(47usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceHistoryProviders { + section: RouterReferenceHistoryProvidersSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(47usize), + ); + pages.push((48usize, { + ::use_mdbook::mdbook_shared::Page { + title: "History Buttons".to_string(), + url: BookRoute::RouterReferenceHistoryButtons { + section: RouterReferenceHistoryButtonsSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "History Buttons".to_string(), + id: "history-buttons".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(48usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceHistoryButtons { + section: RouterReferenceHistoryButtonsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(48usize), + ); + pages.push((49usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Routing Update Callback".to_string(), + url: BookRoute::RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Routing Update Callback".to_string(), + id: "routing-update-callback".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "How does the callback behave?".to_string(), + id: "how-does-the-callback-behave".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Code Example".to_string(), + id: "code-example".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(49usize), + } + })); + page_id_mapping.insert( + BookRoute::RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(49usize), + ); + pages.push((50usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Cookbook".to_string(), + url: BookRoute::CookbookIndex { + section: CookbookIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "Cookbook".to_string(), + id: "cookbook".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(50usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookIndex { + section: CookbookIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(50usize), + ); + pages.push((51usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Publishing".to_string(), + url: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Publishing".to_string(), + id: "publishing".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web: Publishing with GitHub Pages".to_string(), + id: "web-publishing-with-github-pages".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Desktop: Creating an installer".to_string(), + id: "desktop-creating-an-installer".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Preparing your application for bundling".to_string(), + id: "preparing-your-application-for-bundling".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Adding assets to your application".to_string(), + id: "adding-assets-to-your-application".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Install dioxus CLI".to_string(), + id: "install-dioxus-cli".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Building".to_string(), + id: "building".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(51usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookPublishing { + section: CookbookPublishingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(51usize), + ); + pages.push((52usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Anti-patterns".to_string(), + url: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Antipatterns".to_string(), + id: "antipatterns".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Unnecessarily Nested Fragments".to_string(), + id: "unnecessarily-nested-fragments".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Incorrect Iterator Keys".to_string(), + id: "incorrect-iterator-keys".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Avoid Interior Mutability in Props".to_string(), + id: "avoid-interior-mutability-in-props".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Avoid Updating State During Render".to_string(), + id: "avoid-updating-state-during-render".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Avoid Large Groups of State".to_string(), + id: "avoid-large-groups-of-state".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Running Non-Deterministic Code in the Body of a Component" + .to_string(), + id: "running-non-deterministic-code-in-the-body-of-a-component".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Overly Permissive PartialEq for Props".to_string(), + id: "overly-permissive-partialeq-for-props".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(52usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(52usize), + ); + pages.push((53usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Error Handling".to_string(), + url: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Error handling".to_string(), + id: "error-handling".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The simplest – returning None".to_string(), + id: "the-simplest--returning-none".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Early return on result".to_string(), + id: "early-return-on-result".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Match results".to_string(), + id: "match-results".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Passing error states through components".to_string(), + id: "passing-error-states-through-components".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Throwing errors".to_string(), + id: "throwing-errors".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(53usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(53usize), + ); + pages.push((54usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Integrations".to_string(), + url: BookRoute::CookbookIntegrationsIndex { + section: CookbookIntegrationsIndexSection::Empty, + }, + segments: vec![], + sections: vec![], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(54usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookIntegrationsIndex { + section: CookbookIntegrationsIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(54usize), + ); + pages.push((55usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Logging".to_string(), + url: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Logging".to_string(), + id: "logging".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The Tracing Crate".to_string(), + id: "the-tracing-crate".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dioxus Logger".to_string(), + id: "dioxus-logger".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Platform Intricacies".to_string(), + id: "platform-intricacies".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Final Notes".to_string(), + id: "final-notes".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Desktop and Server".to_string(), + id: "desktop-and-server".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web".to_string(), + id: "web".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Mobile".to_string(), + id: "mobile".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Android".to_string(), + id: "android".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Viewing Android Logs".to_string(), + id: "viewing-android-logs".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "iOS".to_string(), + id: "ios".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Viewing IOS Logs".to_string(), + id: "viewing-ios-logs".to_string(), + level: 4usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(55usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(55usize), + ); + pages.push((56usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Internationalization".to_string(), + url: BookRoute::CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Internationalization".to_string(), + id: "internationalization".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The full code for internationalization".to_string(), + id: "the-full-code-for-internationalization".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(56usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(56usize), + ); + pages.push((57usize, { + ::use_mdbook::mdbook_shared::Page { + title: "State Management".to_string(), + url: BookRoute::CookbookStateIndex { + section: CookbookStateIndexSection::Empty, + }, + segments: vec![], + sections: vec![::use_mdbook::mdbook_shared::Section { + title: "State Cookbook".to_string(), + id: "state-cookbook".to_string(), + level: 1usize, + }], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(57usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookStateIndex { + section: CookbookStateIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(57usize), + ); + pages.push((58usize, { + ::use_mdbook::mdbook_shared::Page { + title: "External State".to_string(), + url: BookRoute::CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Working with External State".to_string(), + id: "working-with-external-state".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Working with non-reactive State".to_string(), + id: "working-with-non-reactive-state".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Making Reactive State External".to_string(), + id: "making-reactive-state-external".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(58usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(58usize), + ); + pages.push((59usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Custom Hooks".to_string(), + url: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Custom Hooks".to_string(), + id: "custom-hooks".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Composing Hooks".to_string(), + id: "composing-hooks".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom Hook Logic".to_string(), + id: "custom-hook-logic".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(59usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(59usize), + ); + pages.push((60usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Testing".to_string(), + url: BookRoute::CookbookTesting { + section: CookbookTestingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Testing".to_string(), + id: "testing".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Component Testing".to_string(), + id: "component-testing".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Hook Testing".to_string(), + id: "hook-testing".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "End to End Testing".to_string(), + id: "end-to-end-testing".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(60usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookTesting { + section: CookbookTestingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(60usize), + ); + pages.push((61usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Tailwind".to_string(), + url: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Tailwind".to_string(), + id: "tailwind".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Setup".to_string(), + id: "setup".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Bonus Steps".to_string(), + id: "bonus-steps".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Development".to_string(), + id: "development".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web".to_string(), + id: "web".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Desktop".to_string(), + id: "desktop".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(61usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(61usize), + ); + pages.push((62usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Custom Renderer".to_string(), + url: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Custom Renderer".to_string(), + id: "custom-renderer".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The specifics:".to_string(), + id: "the-specifics".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Templates".to_string(), + id: "templates".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Mutations".to_string(), + id: "mutations".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Node storage".to_string(), + id: "node-storage".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "An Example".to_string(), + id: "an-example".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Building Templates".to_string(), + id: "building-templates".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Applying Mutations".to_string(), + id: "applying-mutations".to_string(), + level: 4usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Event loop".to_string(), + id: "event-loop".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Custom raw elements".to_string(), + id: "custom-raw-elements".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Native Core".to_string(), + id: "native-core".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "The RealDom".to_string(), + id: "the-realdom".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Example".to_string(), + id: "example".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Layout".to_string(), + id: "layout".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Text Editing".to_string(), + id: "text-editing".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Conclusion".to_string(), + id: "conclusion".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(62usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(62usize), + ); + pages.push((63usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Optimizing".to_string(), + url: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Optimizing".to_string(), + id: "optimizing".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Building in release mode".to_string(), + id: "building-in-release-mode".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "UPX".to_string(), + id: "upx".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Build configuration".to_string(), + id: "build-configuration".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Stable".to_string(), + id: "stable".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Unstable".to_string(), + id: "unstable".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "wasm-opt".to_string(), + id: "wasm-opt".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Improving Dioxus code".to_string(), + id: "improving-dioxus-code".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Optimizing the size of assets".to_string(), + id: "optimizing-the-size-of-assets".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(63usize), + } + })); + page_id_mapping.insert( + BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(63usize), + ); + pages.push((64usize, { + ::use_mdbook::mdbook_shared::Page { + title: "CLI".to_string(), + url: BookRoute::CliIndex { + section: CliIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Introduction".to_string(), + id: "introduction".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Features".to_string(), + id: "features".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(64usize), + } + })); + page_id_mapping.insert( + BookRoute::CliIndex { + section: CliIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(64usize), + ); + pages.push((65usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Create a Project".to_string(), + url: BookRoute::CliCreating { + section: CliCreatingSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Create a Project".to_string(), + id: "create-a-project".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Initializing a project".to_string(), + id: "initializing-a-project".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(65usize), + } + })); + page_id_mapping.insert( + BookRoute::CliCreating { + section: CliCreatingSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(65usize), + ); + pages.push((66usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Configure Project".to_string(), + url: BookRoute::CliConfigure { + section: CliConfigureSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Configure Project".to_string(), + id: "configure-project".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Structure".to_string(), + id: "structure".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Application 🔒".to_string(), + id: "application-".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web.App 🔒".to_string(), + id: "webapp-".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web.Watcher 🔒".to_string(), + id: "webwatcher-".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web.Resource 🔒".to_string(), + id: "webresource-".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web.Resource.Dev 🔒".to_string(), + id: "webresourcedev-".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web.Proxy".to_string(), + id: "webproxy".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Config example".to_string(), + id: "config-example".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(66usize), + } + })); + page_id_mapping.insert( + BookRoute::CliConfigure { + section: CliConfigureSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(66usize), + ); + pages.push((67usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Translate HTML".to_string(), + url: BookRoute::CliTranslate { + section: CliTranslateSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Translating existing HTML".to_string(), + id: "translating-existing-html".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Usage".to_string(), + id: "usage".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(67usize), + } + })); + page_id_mapping.insert( + BookRoute::CliTranslate { + section: CliTranslateSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(67usize), + ); + pages.push((68usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Contributing".to_string(), + url: BookRoute::ContributingIndex { + section: ContributingIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Contributing".to_string(), + id: "contributing".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Improving Docs".to_string(), + id: "improving-docs".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Working on the Ecosystem".to_string(), + id: "working-on-the-ecosystem".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Bugs & Features".to_string(), + id: "bugs--features".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Before you contribute".to_string(), + id: "before-you-contribute".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "How to test dioxus with local crate".to_string(), + id: "how-to-test-dioxus-with-local-crate".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(68usize), + } + })); + page_id_mapping.insert( + BookRoute::ContributingIndex { + section: ContributingIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(68usize), + ); + pages.push((69usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Project Structure".to_string(), + url: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Project Structure".to_string(), + id: "project-structure".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Renderers".to_string(), + id: "renderers".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "State Management/Hooks".to_string(), + id: "state-managementhooks".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Core utilities".to_string(), + id: "core-utilities".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Native Renderer Utilities".to_string(), + id: "native-renderer-utilities".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Web renderer tooling".to_string(), + id: "web-renderer-tooling".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Developer tooling".to_string(), + id: "developer-tooling".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(69usize), + } + })); + page_id_mapping.insert( + BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(69usize), + ); + pages.push((70usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Guiding Principles".to_string(), + url: BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Overall Goals".to_string(), + id: "overall-goals".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Cross-Platform".to_string(), + id: "cross-platform".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Performance".to_string(), + id: "performance".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Type Safety".to_string(), + id: "type-safety".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Developer Experience".to_string(), + id: "developer-experience".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(70usize), + } + })); + page_id_mapping.insert( + BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(70usize), + ); + pages.push((71usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Roadmap".to_string(), + url: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Roadmap & Feature-set".to_string(), + id: "roadmap--feature-set".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Features".to_string(), + id: "features".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Roadmap".to_string(), + id: "roadmap".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Core".to_string(), + id: "core".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "SSR".to_string(), + id: "ssr".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Desktop".to_string(), + id: "desktop".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Mobile".to_string(), + id: "mobile".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Bundling (CLI)".to_string(), + id: "bundling-cli".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Essential hooks".to_string(), + id: "essential-hooks".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Work in Progress".to_string(), + id: "work-in-progress".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Build Tool".to_string(), + id: "build-tool".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Server Component Support".to_string(), + id: "server-component-support".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Native rendering".to_string(), + id: "native-rendering".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(71usize), + } + })); + page_id_mapping.insert( + BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(71usize), + ); + pages.push((72usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Migration".to_string(), + url: BookRoute::MigrationIndex { + section: MigrationIndexSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "How to Upgrade to Dioxus 0.5".to_string(), + id: "how-to-upgrade-to-dioxus-05".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Cheat Sheet".to_string(), + id: "cheat-sheet".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Scope".to_string(), + id: "scope".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Props".to_string(), + id: "props".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Futures".to_string(), + id: "futures".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "State Hooks".to_string(), + id: "state-hooks".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Fermi".to_string(), + id: "fermi".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(72usize), + } + })); + page_id_mapping.insert( + BookRoute::MigrationIndex { + section: MigrationIndexSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(72usize), + ); + pages.push((73usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Hooks".to_string(), + url: BookRoute::MigrationHooks { + section: MigrationHooksSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Hooks".to_string(), + id: "hooks".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "State Hooks".to_string(), + id: "state-hooks".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Async Hooks".to_string(), + id: "async-hooks".to_string(), + level: 3usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Dependencies".to_string(), + id: "dependencies".to_string(), + level: 3usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(73usize), + } + })); + page_id_mapping.insert( + BookRoute::MigrationHooks { + section: MigrationHooksSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(73usize), + ); + pages.push((74usize, { + ::use_mdbook::mdbook_shared::Page { + title: "State".to_string(), + url: BookRoute::MigrationState { + section: MigrationStateSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "State Migration".to_string(), + id: "state-migration".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Context Based State".to_string(), + id: "context-based-state".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Opting Out of Subscriptions".to_string(), + id: "opting-out-of-subscriptions".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Global State".to_string(), + id: "global-state".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(74usize), + } + })); + page_id_mapping.insert( + BookRoute::MigrationState { + section: MigrationStateSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(74usize), + ); + pages.push((75usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Fermi".to_string(), + url: BookRoute::MigrationFermi { + section: MigrationFermiSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Fermi".to_string(), + id: "fermi".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Memos".to_string(), + id: "memos".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(75usize), + } + })); + page_id_mapping.insert( + BookRoute::MigrationFermi { + section: MigrationFermiSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(75usize), + ); + pages.push((76usize, { + ::use_mdbook::mdbook_shared::Page { + title: "Props".to_string(), + url: BookRoute::MigrationProps { + section: MigrationPropsSection::Empty, + }, + segments: vec![], + sections: vec![ + ::use_mdbook::mdbook_shared::Section { + title: "Props Migration".to_string(), + id: "props-migration".to_string(), + level: 1usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Owned Props".to_string(), + id: "owned-props".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Borrowed Props".to_string(), + id: "borrowed-props".to_string(), + level: 2usize, + }, + ::use_mdbook::mdbook_shared::Section { + title: "Manual Props".to_string(), + id: "manual-props".to_string(), + level: 2usize, + }, + ], + raw: String::new(), + id: ::use_mdbook::mdbook_shared::PageId(76usize), + } + })); + page_id_mapping.insert( + BookRoute::MigrationProps { + section: MigrationPropsSection::Empty, + }, + ::use_mdbook::mdbook_shared::PageId(76usize), + ); + ::use_mdbook::mdbook_shared::MdBook { + summary: ::use_mdbook::mdbook_shared::Summary { + title: Some("Summary".to_string()), + prefix_chapters: vec![], + numbered_chapters: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Introduction".to_string(), + location: Some(BookRoute::Index { + section: IndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Getting Started".to_string(), + location: Some(BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![1u32, 1u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Guide".to_string(), + location: Some(BookRoute::GuideIndex { + section: GuideIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Your First Component".to_string(), + location: Some(BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "State".to_string(), + location: Some(BookRoute::GuideState { + section: GuideStateSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Data Fetching".to_string(), + location: Some(BookRoute::GuideDataFetching { + section: GuideDataFetchingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32, 3u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Full Code".to_string(), + location: Some(BookRoute::GuideFullCode { + section: GuideFullCodeSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![2u32, 4u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Separator, + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Reference".to_string(), + location: Some(BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "RSX".to_string(), + location: Some(BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Components".to_string(), + location: Some(BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Props".to_string(), + location: Some(BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 3u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Event Handlers".to_string(), + location: Some(BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 4u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Hooks".to_string(), + location: Some(BookRoute::ReferenceHooks { + section: ReferenceHooksSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 5u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "User Input".to_string(), + location: Some(BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 6u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Context".to_string(), + location: Some(BookRoute::ReferenceContext { + section: ReferenceContextSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 7u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Dynamic Rendering".to_string(), + location: Some(BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 8u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Routing".to_string(), + location: Some(BookRoute::ReferenceRouter { + section: ReferenceRouterSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![3u32, 9u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Resource".to_string(), + location: Some(BookRoute::ReferenceUseResource { + section: ReferenceUseResourceSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 10u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "UseCoroutine".to_string(), + location: Some(BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 11u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Spawn".to_string(), + location: Some(BookRoute::ReferenceSpawn { + section: ReferenceSpawnSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 12u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Assets".to_string(), + location: Some(BookRoute::ReferenceAssets { + section: ReferenceAssetsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 13u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Choosing A Web Renderer".to_string(), + location: Some(BookRoute::ReferenceChoosingAWebRenderer { + section: ReferenceChoosingAWebRendererSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 14u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Desktop".to_string(), + location: Some(BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 15u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Mobile".to_string(), + location: Some(BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 16u32], + ), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "APIs".to_string(), + location: Some(BookRoute::ReferenceMobileApis { + section: ReferenceMobileApisSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 16u32, 1u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Web".to_string(), + location: Some(BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 17u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "SSR".to_string(), + location: Some(BookRoute::ReferenceSsr { + section: ReferenceSsrSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 18u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Liveview".to_string(), + location: Some(BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 19u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Fullstack".to_string(), + location: Some(BookRoute::ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 20u32], + ), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Server Functions".to_string(), + location: Some(BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 20u32, 1u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Extractors".to_string(), + location: Some(BookRoute::ReferenceFullstackExtractors { + section: ReferenceFullstackExtractorsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 20u32, 2u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Middleware".to_string(), + location: Some(BookRoute::ReferenceFullstackMiddleware { + section: ReferenceFullstackMiddlewareSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 20u32, 3u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Authentication".to_string(), + location: Some(BookRoute::ReferenceFullstackAuthentication { + section: ReferenceFullstackAuthenticationSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 20u32, 4u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Routing".to_string(), + location: Some(BookRoute::ReferenceFullstackRouting { + section: ReferenceFullstackRoutingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![3u32, 20u32, 5u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Separator, + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Router".to_string(), + location: Some(BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![4u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Example Project".to_string(), + location: Some(BookRoute::RouterExampleIndex { + section: RouterExampleIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![4u32, 1u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Creating Our First Route".to_string(), + location: Some(BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 1u32, 1u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Building a Nest".to_string(), + location: Some(BookRoute::RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 1u32, 2u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Navigation Targets".to_string(), + location: Some(BookRoute::RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 1u32, 3u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Redirection Perfection".to_string(), + location: Some(BookRoute::RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 1u32, 4u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Full Code".to_string(), + location: Some(BookRoute::RouterExampleFullCode { + section: RouterExampleFullCodeSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 1u32, 5u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Reference".to_string(), + location: Some(BookRoute::RouterReferenceIndex { + section: RouterReferenceIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![4u32, 2u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Defining Routes".to_string(), + location: Some(BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 1u32], + ), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Nested Routes".to_string(), + location: Some(BookRoute::RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 1u32, 1u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Layouts".to_string(), + location: Some(BookRoute::RouterReferenceLayouts { + section: RouterReferenceLayoutsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 2u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Navigation".to_string(), + location: Some(BookRoute::RouterReferenceNavigationIndex { + section: RouterReferenceNavigationIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 3u32], + ), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Programmatic Navigation".to_string(), + location: Some(BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 3u32, 1u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "History Providers".to_string(), + location: Some(BookRoute::RouterReferenceHistoryProviders { + section: RouterReferenceHistoryProvidersSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 4u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "History Buttons".to_string(), + location: Some(BookRoute::RouterReferenceHistoryButtons { + section: RouterReferenceHistoryButtonsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 5u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Routing Update Callback".to_string(), + location: Some(BookRoute::RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![4u32, 2u32, 6u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Separator, + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Cookbook".to_string(), + location: Some(BookRoute::CookbookIndex { + section: CookbookIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Publishing".to_string(), + location: Some(BookRoute::CookbookPublishing { + section: CookbookPublishingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Anti-patterns".to_string(), + location: Some(BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Error Handling".to_string(), + location: Some(BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 3u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Integrations".to_string(), + location: Some(BookRoute::CookbookIntegrationsIndex { + section: CookbookIntegrationsIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 4u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Logging".to_string(), + location: Some(BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![5u32, 4u32, 1u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Internationalization".to_string(), + location: Some(BookRoute::CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![5u32, 4u32, 2u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "State Management".to_string(), + location: Some(BookRoute::CookbookStateIndex { + section: CookbookStateIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 5u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "External State".to_string(), + location: Some(BookRoute::CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![5u32, 5u32, 1u32], + ), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Custom Hooks".to_string(), + location: Some(BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![5u32, 5u32, 2u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Testing".to_string(), + location: Some(BookRoute::CookbookTesting { + section: CookbookTestingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 6u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Tailwind".to_string(), + location: Some(BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 7u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Custom Renderer".to_string(), + location: Some(BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 8u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Optimizing".to_string(), + location: Some(BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![5u32, 9u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Separator, + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "CLI".to_string(), + location: Some(BookRoute::CliIndex { + section: CliIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![6u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Create a Project".to_string(), + location: Some(BookRoute::CliCreating { + section: CliCreatingSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![6u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Configure Project".to_string(), + location: Some(BookRoute::CliConfigure { + section: CliConfigureSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![6u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Translate HTML".to_string(), + location: Some(BookRoute::CliTranslate { + section: CliTranslateSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![6u32, 3u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Separator, + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Contributing".to_string(), + location: Some(BookRoute::ContributingIndex { + section: ContributingIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![7u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Project Structure".to_string(), + location: Some(BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![7u32, 1u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Guiding Principles".to_string(), + location: Some(BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![7u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Roadmap".to_string(), + location: Some(BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![7u32, 3u32]), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Separator, + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Migration".to_string(), + location: Some(BookRoute::MigrationIndex { + section: MigrationIndexSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![8u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Hooks".to_string(), + location: Some(BookRoute::MigrationHooks { + section: MigrationHooksSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![8u32, 1u32]), + ), + nested_items: vec![ + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "State".to_string(), + location: Some(BookRoute::MigrationState { + section: MigrationStateSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber( + vec![8u32, 1u32, 1u32], + ), + ), + nested_items: vec![], + }), + ], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Fermi".to_string(), + location: Some(BookRoute::MigrationFermi { + section: MigrationFermiSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![8u32, 2u32]), + ), + nested_items: vec![], + }), + ::use_mdbook::mdbook_shared::SummaryItem::Link(::use_mdbook::mdbook_shared::Link { + name: "Props".to_string(), + location: Some(BookRoute::MigrationProps { + section: MigrationPropsSection::Empty, + }), + number: Some( + ::use_mdbook::mdbook_shared::SectionNumber(vec![8u32, 3u32]), + ), + nested_items: vec![], + }), + ], + }), + ], + suffix_chapters: vec![], + }, + pages: pages.into_iter().collect(), + page_id_mapping, + } + }); +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum IndexSection { + #[default] + Empty, + Introduction, + Features, + Multiplatform, + Stability, +} +impl std::str::FromStr for IndexSection { + type Err = IndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "introduction" => Ok(Self::Introduction), + "features" => Ok(Self::Features), + "multiplatform" => Ok(Self::Multiplatform), + "stability" => Ok(Self::Stability), + _ => Err(IndexSectionParseError), + } + } +} +impl std::fmt::Display for IndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Introduction => f.write_str("introduction"), + Self::Features => f.write_str("features"), + Self::Multiplatform => f.write_str("multiplatform"), + Self::Stability => f.write_str("stability"), + } + } +} +#[derive(Debug)] +pub struct IndexSectionParseError; +impl std::fmt::Display for IndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of IndexSectionintroduction, features, multiplatform, stability", + )?; + Ok(()) + } +} +impl std::error::Error for IndexSectionParseError {} +#[component(no_case_check)] +pub fn Index(section: IndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "introduction", + Link { + to: BookRoute::Index { + section: IndexSection::Introduction, + }, + class: "header", + "Introduction" + } + } + p { + "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." + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\npub fn App() -> Element {{\n    let mut count = use_signal(|| 0);\n\n    rsx! {{\n        h1 {{ "High-Five counter: {{count}}" }}\n        button {{ onclick: move |_| count += 1, "Up high!" }}\n        button {{ onclick: move |_| count -= 1, "Down low!" }}\n    }}\n}}
\n", + name: "readme.rs".to_string(), + } + DemoFrame { readme::App {} } + p { + "Dioxus is heavily inspired by React. If you know React, getting started with Dioxus will be a breeze." + } + blockquote { + p { + "This guide assumes you already know some " + Link { to: "https://www.rust-lang.org/", "Rust" } + "! If not, we recommend reading " + Link { to: "https://doc.rust-lang.org/book/ch01-00-getting-started.html", + em { "the book" } + } + " to learn Rust first." + } + } + h2 { id: "features", + Link { + to: BookRoute::Index { + section: IndexSection::Features, + }, + class: "header", + "Features" + } + } + ul { + li { "Cross platform apps in three lines of code. (Web, Desktop, Server, Mobile, and more)" } + li { + "Incredibly ergonomic and powerful state management that combines the best parts of react, solid and svelte." + } + li { + "Comprehensive inline documentation – hover and guides for all HTML elements, listeners, and events." + } + li { + "High performance applications " + Link { to: "https://dioxuslabs.com/blog/templates-diffing", + "approaching the fastest web frameworks on the web" + } + " and native speeds on desktop." + } + li { "First-class async support." } + } + h3 { id: "multiplatform", + Link { + to: BookRoute::Index { + section: IndexSection::Multiplatform, + }, + class: "header", + "Multiplatform" + } + } + p { + "Dioxus is a " + em { "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 " + code { "html" } + " feature enabled, but this can be disabled depending on your target renderer." + } + p { "Right now, we have several 1st-party renderers:" } + ul { + li { "WebSys/Sledgehammer (for WASM): Great support" } + li { "Tao/Tokio (for Desktop apps): Good support" } + li { "Tao/Tokio (for Mobile apps): Poor support" } + li { "Fullstack (for SSR and server functions): Good support" } + li { "TUI/Plasmo (for terminal-based apps): Experimental" } + } + h2 { id: "stability", + Link { + to: BookRoute::Index { + section: IndexSection::Stability, + }, + class: "header", + "Stability" + } + } + p { "Dioxus has not reached a stable release yet." } + p { + "Web: Since the web is a fairly mature platform, we expect there to be very little API churn for web-based features." + } + p { + "Desktop: APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart." + } + p { + "Fullstack: APIs will likely be in flux as we figure out the best API for server communication." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GettingStartedIndexSection { + #[default] + Empty, + GettingStarted, + Prerequisites, + AnEditor, + Rust, + PlatformSpecificDependencies, + DioxusCli, + CreateANewProject, + RunningTheProject, + Conclusion, +} +impl std::str::FromStr for GettingStartedIndexSection { + type Err = GettingStartedIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "getting-started" => Ok(Self::GettingStarted), + "prerequisites" => Ok(Self::Prerequisites), + "an-editor" => Ok(Self::AnEditor), + "rust" => Ok(Self::Rust), + "platform-specific-dependencies" => Ok(Self::PlatformSpecificDependencies), + "dioxus-cli" => Ok(Self::DioxusCli), + "create-a-new-project" => Ok(Self::CreateANewProject), + "running-the-project" => Ok(Self::RunningTheProject), + "conclusion" => Ok(Self::Conclusion), + _ => Err(GettingStartedIndexSectionParseError), + } + } +} +impl std::fmt::Display for GettingStartedIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::GettingStarted => f.write_str("getting-started"), + Self::Prerequisites => f.write_str("prerequisites"), + Self::AnEditor => f.write_str("an-editor"), + Self::Rust => f.write_str("rust"), + Self::PlatformSpecificDependencies => f.write_str("platform-specific-dependencies"), + Self::DioxusCli => f.write_str("dioxus-cli"), + Self::CreateANewProject => f.write_str("create-a-new-project"), + Self::RunningTheProject => f.write_str("running-the-project"), + Self::Conclusion => f.write_str("conclusion"), + } + } +} +#[derive(Debug)] +pub struct GettingStartedIndexSectionParseError; +impl std::fmt::Display for GettingStartedIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GettingStartedIndexSectiongetting-started, prerequisites, an-editor, rust, platform-specific-dependencies, dioxus-cli, create-a-new-project, running-the-project, conclusion", + )?; + Ok(()) + } +} +impl std::error::Error for GettingStartedIndexSectionParseError {} +#[component(no_case_check)] +pub fn GettingStartedIndex(section: GettingStartedIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "getting-started", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::GettingStarted, + }, + class: "header", + "Getting Started" + } + } + p { "This section will help you set up your Dioxus project!" } + h2 { id: "prerequisites", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Prerequisites, + }, + class: "header", + "Prerequisites" + } + } + h3 { id: "an-editor", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::AnEditor, + }, + class: "header", + "An Editor" + } + } + p { + "Dioxus integrates very well with the " + Link { to: "https://rust-analyzer.github.io", "Rust-Analyzer LSP plugin" } + " which will provide appropriate syntax highlighting, code navigation, folding, and more." + } + h3 { id: "rust", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Rust, + }, + class: "header", + "Rust" + } + } + p { + "Head over to " + Link { to: "http://rust-lang.org", "https://rust-lang.org" } + " and install the Rust compiler." + } + p { + "We strongly recommend going through the " + Link { to: "https://doc.rust-lang.org/book/ch01-00-getting-started.html", + "official Rust book" + } + " " + em { "completely" } + ". However, we hope that a Dioxus app can serve as a great first Rust project. With Dioxus, you'll learn about:" + } + ul { + li { "Error handling" } + li { "Structs, Functions, Enums" } + li { "Closures" } + li { "Macros" } + } + p { + "We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need deep knowledge of async, lifetimes, or smart pointers until you start building complex Dioxus apps." + } + h3 { id: "platform-specific-dependencies", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::PlatformSpecificDependencies, + }, + class: "header", + "Platform-specific dependencies" + } + } + p { + "Most platforms don't require any additional dependencies, but if you are targeting desktop, you can install the following dependencies:" + } + DesktopDependencies {} + h3 { id: "dioxus-cli", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::DioxusCli, + }, + class: "header", + "Dioxus CLI" + } + } + p { "Next, lets install the Dioxus CLI:" } + CodeBlock { contents: "
\ncargo install dioxus-cli
\n" } + p { + "If you get an OpenSSL error on installation, ensure the dependencies listed " + Link { to: "https://docs.rs/openssl/latest/openssl/#automatic", "here" } + " are installed." + } + h2 { id: "create-a-new-project", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::CreateANewProject, + }, + class: "header", + "Create a new project" + } + } + p { + "You can create a new Dioxus project by running the following command and following the prompts:" + } + CodeBlock { contents: "
\ndx new
\n" } + p { + "First you will need to select a platform. Each platform has its own reference with more information on how to set up a project for that platform. Here are the platforms we recommend starting with:" + } + ul { + li { + "Web" + ul { + li { + Link { + to: BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::Empty, + }, + "Client Side" + } + ": runs in the browser through WebAssembly" + } + li { + Link { + to: BookRoute::ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection::Empty, + }, + "Fullstack" + } + ": renders to HTML text on the server and hydrates it on the client" + } + } + } + } + blockquote { + p { + "If you are not sure which web platform you want to use, check out the " + Link { + to: BookRoute::ReferenceChoosingAWebRenderer { + section: ReferenceChoosingAWebRendererSection::Empty, + }, + "choosing a web renderer" + } + " chapter." + } + } + ul { + li { + "WebView" + ul { + li { + Link { + to: BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::Empty, + }, + "Desktop" + } + ": runs in a web view on desktop" + } + li { + Link { + to: BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::Empty, + }, + "Mobile" + } + ": runs in a web view on mobile. Mobile is currently not supported by the dioxus CLI. The " + Link { + to: BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::Empty, + }, + "mobile reference" + } + " has more information about setting up a mobile project" + } + } + } + } + p { "Next, you can choose a styling library. For this project, we will use vanilla CSS." } + p { + "Finally, you can choose to start the project with the router enabled. The router is covered in the " + Link { + to: BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }, + "router guide" + } + "." + } + h2 { id: "running-the-project", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::RunningTheProject, + }, + class: "header", + "Running the project" + } + } + p { "Once you have created your project, you can start it with the following command:" } + CodeBlock { contents: "
\ncd my_project\ndx serve
\n" } + p { + "For projects using the liveview template, run " + code { "dx serve --desktop" } + "." + } + p { + "For Web targets the application will be served at " + Link { to: "http://localhost:8080", "http://localhost:8080" } + } + h2 { id: "conclusion", + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Conclusion, + }, + class: "header", + "Conclusion" + } + } + p { + "That's it! You now have a working Dioxus project. You can continue learning about dioxus by " + Link { + to: BookRoute::GuideIndex { + section: GuideIndexSection::Empty, + }, + "making a hackernews clone in the guide" + } + ", or learning about specific topics/platforms in the " + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Empty, + }, + "reference" + } + ". If you have any questions, feel free to ask in the " + Link { to: "https://discord.gg/XgGxMSkvUM", "discord" } + " or " + Link { to: "https://github.com/DioxusLabs/dioxus/discussions", "open a discussion" } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GuideIndexSection { + #[default] + Empty, + DioxusGuide, + Introduction, +} +impl std::str::FromStr for GuideIndexSection { + type Err = GuideIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "dioxus-guide" => Ok(Self::DioxusGuide), + "introduction" => Ok(Self::Introduction), + _ => Err(GuideIndexSectionParseError), + } + } +} +impl std::fmt::Display for GuideIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::DioxusGuide => f.write_str("dioxus-guide"), + Self::Introduction => f.write_str("introduction"), + } + } +} +#[derive(Debug)] +pub struct GuideIndexSectionParseError; +impl std::fmt::Display for GuideIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GuideIndexSectiondioxus-guide, introduction", + )?; + Ok(()) + } +} +impl std::error::Error for GuideIndexSectionParseError {} +#[component(no_case_check)] +pub fn GuideIndex(section: GuideIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "dioxus-guide", + Link { + to: BookRoute::GuideIndex { + section: GuideIndexSection::DioxusGuide, + }, + class: "header", + "Dioxus Guide" + } + } + h2 { id: "introduction", + Link { + to: BookRoute::GuideIndex { + section: GuideIndexSection::Introduction, + }, + class: "header", + "Introduction" + } + } + p { + "In this guide, you'll learn to use Dioxus to build user interfaces that run anywhere. We will recreate the hackernews homepage in Dioxus:" + } + DemoFrame { hackernews_complete::App {} } + p { + "This guide serves a very brief overview of Dioxus. Throughout the guide, there will be links to the " + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Empty, + }, + "reference" + } + " with more details about specific concepts." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GuideYourFirstComponentSection { + #[default] + Empty, + YourFirstComponent, + Setup, + DescribingTheUi, + DynamicText, + CreatingElements, + SettingAttributes, + CreatingAComponent, + CreatingProps, + CleaningUpOurInterface, +} +impl std::str::FromStr for GuideYourFirstComponentSection { + type Err = GuideYourFirstComponentSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "your-first-component" => Ok(Self::YourFirstComponent), + "setup" => Ok(Self::Setup), + "describing-the-ui" => Ok(Self::DescribingTheUi), + "dynamic-text" => Ok(Self::DynamicText), + "creating-elements" => Ok(Self::CreatingElements), + "setting-attributes" => Ok(Self::SettingAttributes), + "creating-a-component" => Ok(Self::CreatingAComponent), + "creating-props" => Ok(Self::CreatingProps), + "cleaning-up-our-interface" => Ok(Self::CleaningUpOurInterface), + _ => Err(GuideYourFirstComponentSectionParseError), + } + } +} +impl std::fmt::Display for GuideYourFirstComponentSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::YourFirstComponent => f.write_str("your-first-component"), + Self::Setup => f.write_str("setup"), + Self::DescribingTheUi => f.write_str("describing-the-ui"), + Self::DynamicText => f.write_str("dynamic-text"), + Self::CreatingElements => f.write_str("creating-elements"), + Self::SettingAttributes => f.write_str("setting-attributes"), + Self::CreatingAComponent => f.write_str("creating-a-component"), + Self::CreatingProps => f.write_str("creating-props"), + Self::CleaningUpOurInterface => f.write_str("cleaning-up-our-interface"), + } + } +} +#[derive(Debug)] +pub struct GuideYourFirstComponentSectionParseError; +impl std::fmt::Display for GuideYourFirstComponentSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GuideYourFirstComponentSectionyour-first-component, setup, describing-the-ui, dynamic-text, creating-elements, setting-attributes, creating-a-component, creating-props, cleaning-up-our-interface", + )?; + Ok(()) + } +} +impl std::error::Error for GuideYourFirstComponentSectionParseError {} +#[component(no_case_check)] +pub fn GuideYourFirstComponent( + section: GuideYourFirstComponentSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "your-first-component", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::YourFirstComponent, + }, + class: "header", + "Your First Component" + } + } + p { + "This chapter will teach you how to create a " + Link { + to: BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Empty, + }, + "Component" + } + " that displays a link to a post on hackernews." + } + h2 { id: "setup", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::Setup, + }, + class: "header", + "Setup" + } + } + blockquote { + p { + "Before you start the guide, make sure you have the dioxus CLI and any required dependencies for your platform as described in the " + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Empty, + }, + "getting started" + } + " guide." + } + } + p { + "First, let's create a new project for our hacker news app. We can use the CLI to create a new project. You can select a platform of your choice or view the getting started guide for more information on each option. If you aren't sure what platform to try out, we recommend getting started with web or desktop:" + } + CodeBlock { contents: "
\ndx new
\n" } + p { + "The template contains some boilerplate to help you get started. For this guide, we will be rebuilding some of the code from scratch for learning purposes. You can clear the " + code { "src/main.rs" } + " file. We will be adding new code in the next sections." + } + p { + "Next, let's setup our dependencies. We need to set up a few dependencies to work with the hacker news API:" + } + CodeBlock { contents: "
\ncargo add chrono --features serde\ncargo add futures\ncargo add reqwest --features json\ncargo add serde --features derive\ncargo add serde_json\ncargo add async_recursion
\n" } + h2 { id: "describing-the-ui", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::DescribingTheUi, + }, + class: "header", + "Describing the UI" + } + } + p { + "Now, we can define how to display a post. Dioxus is a " + em { "declarative" } + " framework. This means that instead of telling Dioxus what to do (e.g. to \"create an element\" or \"set the color to red\") we simply " + em { "declare" } + " how we want the UI to look." + } + p { + "To declare what you want your UI to look like, you will need to use the " + code { "rsx" } + " macro. Let's create a " + code { "main" } + " function and an " + code { "App" } + " component to show information about our story:" + } + CodeBlock { + contents: "
\nfn main() {{\n    launch(App);\n}}\n\npub fn App() -> Element {{\n    rsx! {{"story"}}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + p { "Now if you run your application you should see something like this:" } + DemoFrame { hackernews_post::story_v1::App {} } + blockquote { + p { "RSX mirrors HTML. Because of this you will need to know some html to use Dioxus." } + p { "Here are some resources to help get you started learning HTML:" } + ul { + li { + Link { to: "https://developer.mozilla.org/en-US/docs/Learn/HTML", + "MDN HTML Guide" + } + } + li { + Link { to: "https://www.w3schools.com/html/default.asp", "W3 Schools HTML Tutorial" } + } + } + p { + "In addition to HTML, Dioxus uses CSS to style applications. You can either use traditional CSS (what this guide uses) or use a tool like " + Link { to: "https://tailwindcss.com/docs/installation", "tailwind CSS" } + ":" + } + ul { + li { + Link { to: "https://developer.mozilla.org/en-US/docs/Learn/HTML", + "MDN Traditional CSS Guide" + } + } + li { + Link { to: "https://www.w3schools.com/css/default.asp", + "W3 Schools Traditional CSS Tutorial" + } + } + li { + Link { to: "https://tailwindcss.com/docs/installation", "Tailwind tutorial" } + " (used with the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/examples/tailwind", + "Tailwind setup example" + } + ")" + } + } + p { + "If you have existing html code, you can use the " + Link { + to: BookRoute::CliTranslate { + section: CliTranslateSection::Empty, + }, + "translate" + } + " command to convert it to RSX. Or if you prefer to write html, you can use the " + Link { to: "https://github.com/DioxusLabs/dioxus-html-macro", "html! macro" } + " to write html directly in your code." + } + } + h2 { id: "dynamic-text", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::DynamicText, + }, + class: "header", + "Dynamic Text" + } + } + p { + "Let's expand our " + code { "App" } + " component to include the story title, author, score, time posted, and number of comments. We can insert dynamic text in the render macro by inserting variables inside " + code { "{{}}" } + "s (this works similarly to the formatting in the " + Link { to: "https://doc.rust-lang.org/std/macro.println.html", "println!" } + " macro):" + } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    let title = "title";\n    let by = "author";\n    let score = 0;\n    let time = chrono::Utc::now();\n    let comments = "comments";\n\n    rsx! {{"{{title}} by {{by}} ({{score}}) {{time}} {{comments}}"}}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + DemoFrame { hackernews_post::story_v2::App {} } + h2 { id: "creating-elements", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::CreatingElements, + }, + class: "header", + "Creating Elements" + } + } + p { + "Next, let's wrap our post description in a " + Link { to: "https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div", + code { "div" } + } + ". You can create HTML elements in Dioxus by putting a " + code { "{{" } + " after the element name and a " + code { "}}" } + " after the last child of the element:" + } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    let title = "title";\n    let by = "author";\n    let score = 0;\n    let time = chrono::Utc::now();\n    let comments = "comments";\n\n    rsx! {{ div {{ "{{title}} by {{by}} ({{score}}) {{time}} {{comments}}" }} }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + DemoFrame { hackernews_post::story_v3::App {} } + blockquote { + p { + "You can read more about elements in the " + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Empty, + }, + "rsx reference" + } + "." + } + } + h2 { id: "setting-attributes", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::SettingAttributes, + }, + class: "header", + "Setting Attributes" + } + } + p { "Next, let's add some padding around our post listing with an attribute." } + p { + "Attributes (and " + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }, + "listeners" + } + ") modify the behavior or appearance of the element they are attached to. They are specified inside the " + code { "{{}}" } + " brackets before any children, using the " + code { "name: value" } + " syntax. You can format the text in the attribute as you would with a text node:" + } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    let title = "title";\n    let by = "author";\n    let score = 0;\n    let time = chrono::Utc::now();\n    let comments = "comments";\n\n    rsx! {{\n        div {{ padding: "0.5rem", position: "relative",\n            "{{title}} by {{by}} ({{score}}) {{time}} {{comments}}"\n        }}\n    }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + DemoFrame { hackernews_post::story_v4::App {} } + blockquote { + p { + "Note: All attributes defined in " + Link { to: "https://docs.rs/dioxus-html/latest/dioxus_html/", + code { "dioxus-html" } + } + " follow the snake_case naming convention. They transform their " + code { "snake_case" } + " names to HTML's " + code { "camelCase" } + " attributes." + } + } + blockquote { + p { + "Note: Styles can be used directly outside of the " + code { "style:" } + " attribute. In the above example, " + code { "padding: \"0.5rem\"" } + " is turned into " + code { "style=\"padding: 0.5rem\"" } + "." + } + } + blockquote { + p { + "You can read more about elements in the " + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Empty, + }, + "attribute reference" + } + } + } + h2 { id: "creating-a-component", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::CreatingAComponent, + }, + class: "header", + "Creating a Component" + } + } + p { + "Just like you wouldn't want to write a complex program in a single, long, " + code { "main" } + " function, you shouldn't build a complex UI in a single " + code { "App" } + " function. Instead, you should break down the functionality of an app in logical parts called components." + } + p { + "A component is a Rust function, named in UpperCamelCase, that takes a props parameter and returns an " + code { "Element" } + " describing the UI it wants to render. In fact, our " + code { "App" } + " function is a component!" + } + p { "Let's pull our story description into a new component:" } + CodeBlock { + contents: "
\nfn StoryListing() -> Element {{\n    let title = "title";\n    let by = "author";\n    let score = 0;\n    let time = chrono::Utc::now();\n    let comments = "comments";\n\n    rsx! {{\n        div {{ padding: "0.5rem", position: "relative",\n            "{{title}} by {{by}} ({{score}}) {{time}} {{comments}}"\n        }}\n    }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + p { + "We can render our component like we would an element by putting " + code { "{{}}" } + "s after the component name. Let's modify our " + code { "App" } + " component to render our new StoryListing component:" + } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    rsx! {{ StoryListing {{}} }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + DemoFrame { hackernews_post::story_v5::App {} } + blockquote { + p { + "You can read more about elements in the " + Link { + to: BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Empty, + }, + "component reference" + } + } + } + h2 { id: "creating-props", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::CreatingProps, + }, + class: "header", + "Creating Props" + } + } + p { + "Just like you can pass arguments to a function or attributes to an element, you can pass props to a component that customize its behavior!" + } + p { + "We can define arguments that components can take when they are rendered (called " + code { "Props" } + ") by adding the " + code { "#[component]" } + " macro before our function definition and adding extra function arguments." + } + p { + "Currently, our " + code { "StoryListing" } + " component always renders the same story. We can modify it to accept a story to render as a prop." + } + p { + "We will also define what a post is and include information for how to transform our post to and from a different format using " + Link { to: "https://serde.rs", "serde" } + ". This will be used with the hackernews API in a later chapter:" + } + CodeBlock { + contents: "
\nuse chrono::{{DateTime, Utc}};\nuse serde::{{Deserialize, Serialize}};\n\n// Define the Hackernews types\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryPageData {{\n    #[serde(flatten)]\n    pub item: StoryItem,\n    #[serde(default)]\n    pub comments: Vec<CommentData>,\n}}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct CommentData {{\n    pub id: i64,\n    /// there will be no by field if the comment was deleted\n    #[serde(default)]\n    pub by: String,\n    #[serde(default)]\n    pub text: String,\n    #[serde(with = "chrono::serde::ts_seconds")]\n    pub time: DateTime<Utc>,\n    #[serde(default)]\n    pub kids: Vec<i64>,\n    #[serde(default)]\n    pub sub_comments: Vec<CommentData>,\n    pub r#type: String,\n}}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryItem {{\n    pub id: i64,\n    pub title: String,\n    pub url: Option<String>,\n    pub text: Option<String>,\n    #[serde(default)]\n    pub by: String,\n    #[serde(default)]\n    pub score: i64,\n    #[serde(default)]\n    pub descendants: i64,\n    #[serde(with = "chrono::serde::ts_seconds")]\n    pub time: DateTime<Utc>,\n    #[serde(default)]\n    pub kids: Vec<i64>,\n    pub r#type: String,\n}}\n\n#[component]\nfn StoryListing(story: ReadOnlySignal<StoryItem>) -> Element {{\n    let StoryItem {{\n        title,\n        url,\n        by,\n        score,\n        time,\n        kids,\n        ..\n    }} = &*story.read();\n\n    let comments = kids.len();\n\n    rsx! {{\n        div {{ padding: "0.5rem", position: "relative",\n            "{{title}} by {{by}} ({{score}}) {{time}} {{comments}}"\n        }}\n    }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + p { + "Make sure to also add " + Link { to: "https://serde.rs", "serde" } + " as a dependency:" + } + CodeBlock { contents: "
\ncargo add serde --features derive\ncargo add serde_json
\n" } + p { + "We will also use the " + Link { to: "https://crates.io/crates/chrono", "chrono" } + " crate to provide utilities for handling time data from the hackernews API:" + } + CodeBlock { contents: "
\ncargo add chrono --features serde
\n" } + p { + "Now, let's modify the " + code { "App" } + " component to pass the story to our " + code { "StoryListing" } + " component like we would set an attribute on an element:" + } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    rsx! {{\n        StoryListing {{\n            story: StoryItem {{\n                id: 0,\n                title: "hello hackernews".to_string(),\n                url: None,\n                text: None,\n                by: "Author".to_string(),\n                score: 0,\n                descendants: 0,\n                time: chrono::Utc::now(),\n                kids: vec![],\n                r#type: "".to_string(),\n            }}\n        }}\n    }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + DemoFrame { hackernews_post::story_v6::App {} } + blockquote { + p { + "You can read more about Props in the " + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::Empty, + }, + "Props reference" + } + } + } + h2 { id: "cleaning-up-our-interface", + Link { + to: BookRoute::GuideYourFirstComponent { + section: GuideYourFirstComponentSection::CleaningUpOurInterface, + }, + class: "header", + "Cleaning Up Our Interface" + } + } + p { + "Finally, by combining elements and attributes, we can make our post listing much more appealing:" + } + p { "Full code up to this point:" } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\n// Define the Hackernews types\nuse chrono::{{DateTime, Utc}};\nuse serde::{{Deserialize, Serialize}};\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryPageData {{\n    #[serde(flatten)]\n    pub item: StoryItem,\n    #[serde(default)]\n    pub comments: Vec<CommentData>,\n}}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct CommentData {{\n    pub id: i64,\n    /// there will be no by field if the comment was deleted\n    #[serde(default)]\n    pub by: String,\n    #[serde(default)]\n    pub text: String,\n    #[serde(with = "chrono::serde::ts_seconds")]\n    pub time: DateTime<Utc>,\n    #[serde(default)]\n    pub kids: Vec<i64>,\n    #[serde(default)]\n    pub sub_comments: Vec<CommentData>,\n    pub r#type: String,\n}}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryItem {{\n    pub id: i64,\n    pub title: String,\n    pub url: Option<String>,\n    pub text: Option<String>,\n    #[serde(default)]\n    pub by: String,\n    #[serde(default)]\n    pub score: i64,\n    #[serde(default)]\n    pub descendants: i64,\n    #[serde(with = "chrono::serde::ts_seconds")]\n    pub time: DateTime<Utc>,\n    #[serde(default)]\n    pub kids: Vec<i64>,\n    pub r#type: String,\n}}\n\nfn main() {{\n    launch(App);\n}}\n\npub fn App() -> Element {{\n    rsx! {{\n        StoryListing {{\n            story: StoryItem {{\n                id: 0,\n                title: "hello hackernews".to_string(),\n                url: None,\n                text: None,\n                by: "Author".to_string(),\n                score: 0,\n                descendants: 0,\n                time: Utc::now(),\n                kids: vec![],\n                r#type: "".to_string(),\n            }}\n        }}\n    }}\n}}\n\n#[component]\nfn StoryListing(story: ReadOnlySignal<StoryItem>) -> Element {{\n    let StoryItem {{\n        title,\n        url,\n        by,\n        score,\n        time,\n        kids,\n        ..\n    }} = &*story.read();\n\n    let url = url.as_deref().unwrap_or_default();\n    let hostname = url\n        .trim_start_matches("https://")\n        .trim_start_matches("http://")\n        .trim_start_matches("www.");\n    let score = format!("{{score}} {{}}", if *score == 1 {{ " point" }} else {{ " points" }});\n    let comments = format!(\n        "{{}} {{}}",\n        kids.len(),\n        if kids.len() == 1 {{\n            " comment"\n        }} else {{\n            " comments"\n        }}\n    );\n    let time = time.format("%D %l:%M %p");\n\n    rsx! {{\n        div {{ padding: "0.5rem", position: "relative",\n            div {{ font_size: "1.5rem",\n                a {{ href: url, "{{title}}" }}\n                a {{\n                    color: "gray",\n                    href: "https://news.ycombinator.com/from?site={{hostname}}",\n                    text_decoration: "none",\n                    " ({{hostname}})"\n                }}\n            }}\n            div {{ display: "flex", flex_direction: "row", color: "gray",\n                div {{ "{{score}}" }}\n                div {{ padding_left: "0.5rem", "by {{by}}" }}\n                div {{ padding_left: "0.5rem", "{{time}}" }}\n                div {{ padding_left: "0.5rem", "{{comments}}" }}\n            }}\n        }}\n    }}\n}}
\n", + name: "hackernews_post.rs".to_string(), + } + DemoFrame { hackernews_post::story_final::App {} } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GuideStateSection { + #[default] + Empty, + Interactivity, + CreatingAPreview, + EventHandlers, + State, + TheRulesOfHooks, + NoHooksInConditionals, + NoHooksInClosures, + NoHooksInLoops, +} +impl std::str::FromStr for GuideStateSection { + type Err = GuideStateSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "interactivity" => Ok(Self::Interactivity), + "creating-a-preview" => Ok(Self::CreatingAPreview), + "event-handlers" => Ok(Self::EventHandlers), + "state" => Ok(Self::State), + "the-rules-of-hooks" => Ok(Self::TheRulesOfHooks), + "no-hooks-in-conditionals" => Ok(Self::NoHooksInConditionals), + "no-hooks-in-closures" => Ok(Self::NoHooksInClosures), + "no-hooks-in-loops" => Ok(Self::NoHooksInLoops), + _ => Err(GuideStateSectionParseError), + } + } +} +impl std::fmt::Display for GuideStateSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Interactivity => f.write_str("interactivity"), + Self::CreatingAPreview => f.write_str("creating-a-preview"), + Self::EventHandlers => f.write_str("event-handlers"), + Self::State => f.write_str("state"), + Self::TheRulesOfHooks => f.write_str("the-rules-of-hooks"), + Self::NoHooksInConditionals => f.write_str("no-hooks-in-conditionals"), + Self::NoHooksInClosures => f.write_str("no-hooks-in-closures"), + Self::NoHooksInLoops => f.write_str("no-hooks-in-loops"), + } + } +} +#[derive(Debug)] +pub struct GuideStateSectionParseError; +impl std::fmt::Display for GuideStateSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GuideStateSectioninteractivity, creating-a-preview, event-handlers, state, the-rules-of-hooks, no-hooks-in-conditionals, no-hooks-in-closures, no-hooks-in-loops", + )?; + Ok(()) + } +} +impl std::error::Error for GuideStateSectionParseError {} +#[component(no_case_check)] +pub fn GuideState(section: GuideStateSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "interactivity", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::Interactivity, + }, + class: "header", + "Interactivity" + } + } + p { "In this chapter, we will add a preview for articles you hover over or links you focus on." } + h2 { id: "creating-a-preview", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::CreatingAPreview, + }, + class: "header", + "Creating a Preview" + } + } + p { + "First, let's split our app into a Stories component on the left side of the screen, and a preview component on the right side of the screen:" + } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    rsx! {{\n        div {{ display: "flex", flex_direction: "row", width: "100%",\n            div {{ width: "50%", Stories {{}} }}\n            div {{ width: "50%", Preview {{}} }}\n        }}\n    }}\n}}\n\n// New\nfn Stories() -> Element {{\n    rsx! {{\n        StoryListing {{\n            story: StoryItem {{\n                id: 0,\n                title: "hello hackernews".to_string(),\n                url: None,\n                text: None,\n                by: "Author".to_string(),\n                score: 0,\n                descendants: 0,\n                time: chrono::Utc::now(),\n                kids: vec![],\n                r#type: "".to_string(),\n            }}\n        }}\n    }}\n}}\n\n// New\n#[derive(Clone, Debug)]\nenum PreviewState {{\n    Unset,\n    Loading,\n    Loaded(StoryPageData),\n}}\n\n// New\nfn Preview() -> Element {{\n    let preview_state = PreviewState::Unset;\n    match preview_state {{\n        PreviewState::Unset => rsx! {{"Hover over a story to preview it here"}},\n        PreviewState::Loading => rsx! {{"Loading..."}},\n        PreviewState::Loaded(story) => {{\n            rsx! {{\n                div {{ padding: "0.5rem",\n                    div {{ font_size: "1.5rem", a {{ href: story.item.url, "{{story.item.title}}" }} }}\n                    div {{ dangerous_inner_html: story.item.text }}\n                    for comment in &story.comments {{\n                        Comment {{ comment: comment.clone() }}\n                    }}\n                }}\n            }}\n        }}\n    }}\n}}\n\n// NEW\n#[component]\nfn Comment(comment: CommentData) -> Element {{\n    rsx! {{\n        div {{ padding: "0.5rem",\n            div {{ color: "gray", "by {{comment.by}}" }}\n            div {{ dangerous_inner_html: "{{comment.text}}" }}\n            for kid in &comment.sub_comments {{\n                Comment {{ comment: kid.clone() }}\n            }}\n        }}\n    }}\n}}
\n", + name: "hackernews_state.rs".to_string(), + } + DemoFrame { hackernews_state::app_v1::App {} } + h2 { id: "event-handlers", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::EventHandlers, + }, + class: "header", + "Event Handlers" + } + } + p { + "Next, we need to detect when the user hovers over a section or focuses a link. We can use an " + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }, + "event listener" + } + " to listen for the hover and focus events." + } + p { + "Event handlers are similar to regular attributes, but their name usually starts with " + code { "on" } + "- and they accept closures as values. The closure will be called whenever the event it listens for is triggered. When an event is triggered, information about the event is passed to the closure through the " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.Event.html", + "Event" + } + " structure." + } + p { + "Let's create a " + Link { to: "https://docs.rs/dioxus/latest/dioxus/events/fn.onmouseenter.html", + code { "onmouseenter" } + } + " event listener in the " + code { "StoryListing" } + " component:" + } + CodeBlock { + contents: "
\nrsx! {{\n    div {{\n        padding: "0.5rem",\n        position: "relative",\n        onmouseenter: move |_| {{}},\n        div {{ font_size: "1.5rem",\n            a {{ href: url, onfocus: move |_event| {{}}, "{{title}}" }}\n            a {{\n                color: "gray",\n                href: "https://news.ycombinator.com/from?site={{hostname}}",\n                text_decoration: "none",\n                " ({{hostname}})"\n            }}\n        }}\n        div {{ display: "flex", flex_direction: "row", color: "gray",\n            div {{ "{{score}}" }}\n            div {{ padding_left: "0.5rem", "by {{by}}" }}\n            div {{ padding_left: "0.5rem", "{{time}}" }}\n            div {{ padding_left: "0.5rem", "{{comments}}" }}\n        }}\n    }}\n}}
\n", + name: "hackernews_state.rs".to_string(), + } + blockquote { + p { + "You can read more about Event Handlers in the " + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }, + "Event Handler reference" + } + } + } + h2 { id: "state", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::State, + }, + class: "header", + "State" + } + } + p { + "So far our components have had no state like normal rust functions. To make our application change when we hover over a link we need state to store the currently hovered link in the root of the application." + } + p { + "You can create state in dioxus using hooks. Hooks are Rust functions you call in a constant order in a component that add additional functionality to the component." + } + p { + "In this case, we will use the " + code { "use_context_provider" } + " and " + code { "use_context" } + " hooks:" + } + ul { + li { + "You can provide a closure to " + code { "use_context_provider" } + " that determines the initial value of the shared state and provides the value to all child components" + } + li { + "You can then use the " + code { "use_context" } + " hook to read and modify that state in the " + code { "Preview" } + " and " + code { "StoryListing" } + " components" + } + li { + "When the value updates, the " + code { "Signal" } + " will cause the component to re-render, and provides you with the new value" + } + } + blockquote { + p { + "Note: You should prefer local state hooks like use_signal or use_signal_sync when you only use state in one component. Because we use state in multiple components, we can use a " + Link { + to: BookRoute::ReferenceContext { + section: ReferenceContextSection::Empty, + }, + "global state pattern" + } + } + } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    use_context_provider(|| Signal::new(PreviewState::Unset));
\n", + name: "hackernews_state.rs".to_string(), + } + CodeBlock { + contents: "
\n#[component]\nfn StoryListing(story: ReadOnlySignal<StoryItem>) -> Element {{\n    let mut preview_state = consume_context::<Signal<PreviewState>>();\n    let StoryItem {{\n        title,\n        url,\n        by,\n        score,\n        time,\n        kids,\n        ..\n    }} = &*story.read();\n\n    let url = url.as_deref().unwrap_or_default();\n    let hostname = url\n        .trim_start_matches("https://")\n        .trim_start_matches("http://")\n        .trim_start_matches("www.");\n    let score = format!("{{score}} point{{}}", if *score > 1 {{ "s" }} else {{ "" }});\n    let comments = format!(\n        "{{}} {{}}",\n        kids.len(),\n        if kids.len() == 1 {{\n            " comment"\n        }} else {{\n            " comments"\n        }}\n    );\n    let time = time.format("%D %l:%M %p");\n\n    rsx! {{\n        div {{\n            padding: "0.5rem",\n            position: "relative",\n            onmouseenter: move |_event| {{\n                *preview_state\n                    .write() = PreviewState::Loaded(StoryPageData {{\n                    item: story(),\n                    comments: vec![],\n                }});\n            }},\n            div {{ font_size: "1.5rem",\n                a {{\n                    href: url,\n                    onfocus: move |_event| {{\n                        *preview_state\n                            .write() = PreviewState::Loaded(StoryPageData {{\n                            item: story(),\n                            comments: vec![],\n                        }});\n                    }},
\n", + name: "hackernews_state.rs".to_string(), + } + CodeBlock { + contents: "
\nfn Preview() -> Element {{\n    // New\n    let preview_state = consume_context::<Signal<PreviewState>>();\n\n    // New\n    match preview_state() {{
\n", + name: "hackernews_state.rs".to_string(), + } + DemoFrame { hackernews_state::App {} } + blockquote { + p { + "You can read more about Hooks in the " + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::Empty, + }, + "Hooks reference" + } + } + } + h3 { id: "the-rules-of-hooks", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::TheRulesOfHooks, + }, + class: "header", + "The Rules of Hooks" + } + } + p { + "Hooks are a powerful way to manage state in Dioxus, but there are some rules you need to follow to insure they work as expected. Dioxus uses the order you call hooks to differentiate between hooks. Because the order you call hooks matters, you must follow these rules:" + } + ol { + li { "Hooks may be only used in components or other hooks (we'll get to that later)" } + li { + "On every call to the component function" + ol { + li { "The same hooks must be called" } + li { "In the same order" } + } + } + li { + "Hooks name's should start with " + code { "use_" } + " so you don't accidentally confuse them with regular functions" + } + } + p { "These rules mean that there are certain things you can't do with hooks:" } + h4 { id: "no-hooks-in-conditionals", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::NoHooksInConditionals, + }, + class: "header", + "No Hooks in Conditionals" + } + } + CodeBlock { + contents: "
\n// ❌ don't call hooks in conditionals!\n// We must ensure that the same hooks will be called every time\n// But `if` statements only run if the conditional is true!\n// So we might violate rule 2.\nif you_are_happy && you_know_it {{\n    let something = use_signal(|| "hands");\n    println!("clap your {{something}}")\n}}\n\n// ✅ instead, *always* call use_signal\n// You can put other stuff in the conditional though\nlet something = use_signal(|| "hands");\nif you_are_happy && you_know_it {{\n    println!("clap your {{something}}")\n}}
\n", + name: "hooks_bad.rs".to_string(), + } + h4 { id: "no-hooks-in-closures", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::NoHooksInClosures, + }, + class: "header", + "No Hooks in Closures" + } + } + CodeBlock { + contents: "
\n// ❌ don't call hooks inside closures!\n// We can't guarantee that the closure, if used, will be called in the same order every time\nlet _a = || {{\n    let b = use_signal(|| 0);\n    b()\n}};\n\n// ✅ instead, move hook `b` outside\nlet b = use_signal(|| 0);\nlet _a = || b();
\n", + name: "hooks_bad.rs".to_string(), + } + h4 { id: "no-hooks-in-loops", + Link { + to: BookRoute::GuideState { + section: GuideStateSection::NoHooksInLoops, + }, + class: "header", + "No Hooks in Loops" + } + } + CodeBlock { + contents: "
\n// `names` is a Vec<&str>\n\n// ❌ Do not use hooks in loops!\n// In this case, if the length of the Vec changes, we break rule 2\nfor _name in &names {{\n    let is_selected = use_signal(|| false);\n    println!("selected: {{is_selected}}");\n}}\n\n// ✅ Instead, use a hashmap with use_signal\nlet selection_map = use_signal(HashMap::<&str, bool>::new);\n\nfor name in &names {{\n    let is_selected = selection_map.read()[name];\n    println!("selected: {{is_selected}}");\n}}
\n", + name: "hooks_bad.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GuideDataFetchingSection { + #[default] + Empty, + FetchingData, + DefiningTheApi, + WorkingWithAsync, + LazilyFetchingData, +} +impl std::str::FromStr for GuideDataFetchingSection { + type Err = GuideDataFetchingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "fetching-data" => Ok(Self::FetchingData), + "defining-the-api" => Ok(Self::DefiningTheApi), + "working-with-async" => Ok(Self::WorkingWithAsync), + "lazily-fetching-data" => Ok(Self::LazilyFetchingData), + _ => Err(GuideDataFetchingSectionParseError), + } + } +} +impl std::fmt::Display for GuideDataFetchingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::FetchingData => f.write_str("fetching-data"), + Self::DefiningTheApi => f.write_str("defining-the-api"), + Self::WorkingWithAsync => f.write_str("working-with-async"), + Self::LazilyFetchingData => f.write_str("lazily-fetching-data"), + } + } +} +#[derive(Debug)] +pub struct GuideDataFetchingSectionParseError; +impl std::fmt::Display for GuideDataFetchingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GuideDataFetchingSectionfetching-data, defining-the-api, working-with-async, lazily-fetching-data", + )?; + Ok(()) + } +} +impl std::error::Error for GuideDataFetchingSectionParseError {} +#[component(no_case_check)] +pub fn GuideDataFetching(section: GuideDataFetchingSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "fetching-data", + Link { + to: BookRoute::GuideDataFetching { + section: GuideDataFetchingSection::FetchingData, + }, + class: "header", + "Fetching Data" + } + } + p { + "In this chapter, we will fetch data from the hacker news API and use it to render the list of top posts in our application." + } + h2 { id: "defining-the-api", + Link { + to: BookRoute::GuideDataFetching { + section: GuideDataFetchingSection::DefiningTheApi, + }, + class: "header", + "Defining the API" + } + } + p { + "First we need to create some utilities to fetch data from the hackernews API using " + Link { to: "https://docs.rs/reqwest/latest/reqwest/index.html", "reqwest" } + ":" + } + CodeBlock { + contents: "
\n// Define the Hackernews API\nuse futures::future::join_all;\n\npub static BASE_API_URL: &str = "https://hacker-news.firebaseio.com/v0/";\npub static ITEM_API: &str = "item/";\npub static USER_API: &str = "user/";\nconst COMMENT_DEPTH: i64 = 2;\n\npub async fn get_story_preview(id: i64) -> Result<StoryItem, reqwest::Error> {{\n    let url = format!("{{}}{{}}{{}}.json", BASE_API_URL, ITEM_API, id);\n    reqwest::get(&url).await?.json().await\n}}\n\npub async fn get_stories(count: usize) -> Result<Vec<StoryItem>, reqwest::Error> {{\n    let url = format!("{{}}topstories.json", BASE_API_URL);\n    let stories_ids = &reqwest::get(&url).await?.json::<Vec<i64>>().await?[..count];\n\n    let story_futures = stories_ids[..usize::min(stories_ids.len(), count)]\n        .iter()\n        .map(|&story_id| get_story_preview(story_id));\n    let stories = join_all(story_futures)\n        .await\n        .into_iter()\n        .filter_map(|story| story.ok())\n        .collect();\n    Ok(stories)\n}}\n\npub async fn get_story(id: i64) -> Result<StoryPageData, reqwest::Error> {{\n    let url = format!("{{}}{{}}{{}}.json", BASE_API_URL, ITEM_API, id);\n    let mut story = reqwest::get(&url).await?.json::<StoryPageData>().await?;\n    let comment_futures = story.item.kids.iter().map(|&id| get_comment(id));\n    let comments = join_all(comment_futures)\n        .await\n        .into_iter()\n        .filter_map(|c| c.ok())\n        .collect();\n\n    story.comments = comments;\n    Ok(story)\n}}\n\n#[async_recursion::async_recursion(?Send)]\npub async fn get_comment_with_depth(id: i64, depth: i64) -> Result<CommentData, reqwest::Error> {{\n    let url = format!("{{}}{{}}{{}}.json", BASE_API_URL, ITEM_API, id);\n    let mut comment = reqwest::get(&url).await?.json::<CommentData>().await?;\n    if depth > 0 {{\n        let sub_comments_futures = comment\n            .kids\n            .iter()\n            .map(|story_id| get_comment_with_depth(*story_id, depth - 1));\n        comment.sub_comments = join_all(sub_comments_futures)\n            .await\n            .into_iter()\n            .filter_map(|c| c.ok())\n            .collect();\n    }}\n    Ok(comment)\n}}\n\npub async fn get_comment(comment_id: i64) -> Result<CommentData, reqwest::Error> {{\n    let comment = get_comment_with_depth(comment_id, COMMENT_DEPTH).await?;\n    Ok(comment)\n}}
\n", + name: "hackernews_async.rs".to_string(), + } + p { + "The code above requires you to add the " + Link { to: "https://crates.io/crates/reqwest", "reqwest" } + ", " + Link { to: "https://crates.io/crates/async-recursion", "async_recursion" } + ", and " + Link { to: "https://crates.io/crates/futures", "futures" } + " crate:" + } + CodeBlock { contents: "
\ncargo add reqwest --features json\ncargo add async_recursion\ncargo add futures
\n" } + p { "A quick overview of the supporting crates:" } + ul { + li { + Link { to: "https://crates.io/crates/reqwest", "reqwest" } + " allows us to create HTTP calls to the hackernews API." + } + li { + Link { to: "https://crates.io/crates/async-recursion", "async_recursion" } + " provides a utility macro to allow us to recursively use an async function." + } + li { + Link { to: "https://crates.io/crates/futures", "futures" } + " provides us with utilities all around Rust's futures." + } + } + h2 { id: "working-with-async", + Link { + to: BookRoute::GuideDataFetching { + section: GuideDataFetchingSection::WorkingWithAsync, + }, + class: "header", + "Working with Async" + } + } + p { + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_resource.html", + code { "use_resource" } + } + " is a " + Link { + to: BookRoute::GuideState { + section: GuideStateSection::Empty, + }, + "hook" + } + " that lets you run an async closure, and provides you with its result." + } + p { + "For example, we can make an API request (using " + Link { to: "https://docs.rs/reqwest/latest/reqwest/index.html", "reqwest" } + ") inside " + code { "use_resource" } + ":" + } + CodeBlock { + contents: "
\nfn Stories() -> Element {{\n    // Fetch the top 10 stories on Hackernews\n    let stories = use_resource(move || get_stories(10));\n\n    // check if the future is resolved\n    match &*stories.read_unchecked() {{\n        Some(Ok(list)) => {{\n            // if it is, render the stories\n            rsx! {{\n                div {{\n                    // iterate over the stories with a for loop\n                    for story in list {{\n                        // render every story with the StoryListing component\n                        StoryListing {{ story: story.clone() }}\n                    }}\n                }}\n            }}\n        }}\n        Some(Err(err)) => {{\n            // if there was an error, render the error\n            rsx! {{"An error occurred while fetching stories {{err}}"}}\n        }}\n        None => {{\n            // if the future is not resolved yet, render a loading message\n            rsx! {{"Loading items"}}\n        }}\n    }}\n}}
\n", + name: "hackernews_async.rs".to_string(), + } + p { + "The code inside " + code { "use_resource" } + " will be submitted to the Dioxus scheduler once the component has rendered." + } + p { + "We can use " + code { ".read()" } + " 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 " + code { "None" } + ". However, once the future is finished, the component will be re-rendered and the value will now be " + code { "Some(...)" } + ", containing the return value of the closure." + } + p { + "We can then render the result by looping over each of the posts and rendering them with the " + code { "StoryListing" } + " component." + } + DemoFrame { hackernews_async::fetch::App {} } + blockquote { + p { + "You can read more about working with Async in Dioxus in the " + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Empty, + }, + "Async reference" + } + } + } + h2 { id: "lazily-fetching-data", + Link { + to: BookRoute::GuideDataFetching { + section: GuideDataFetchingSection::LazilyFetchingData, + }, + class: "header", + "Lazily Fetching Data" + } + } + p { "Finally, we will lazily fetch the comments on each post as the user hovers over the post." } + p { + "We need to revisit the code that handles hovering over an item. Instead of passing an empty list of comments, we can fetch all the related comments when the user hovers over the item." + } + p { + "We will cache the list of comments with a " + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_signal.html", + "use_signal" + } + " hook. This hook allows you to store some state in a single component. When the user triggers fetching the comments we will check if the response has already been cached before fetching the data from the hackernews API." + } + CodeBlock { + contents: "
\n// New\nasync fn resolve_story(\n    mut full_story: Signal<Option<StoryPageData>>,\n    mut preview_state: Signal<PreviewState>,\n    story_id: i64,\n) {{\n    if let Some(cached) = full_story.as_ref() {{\n        *preview_state.write() = PreviewState::Loaded(cached.clone());\n        return;\n    }}\n\n    *preview_state.write() = PreviewState::Loading;\n    if let Ok(story) = get_story(story_id).await {{\n        *preview_state.write() = PreviewState::Loaded(story.clone());\n        *full_story.write() = Some(story);\n    }}\n}}\n\n#[component]\nfn StoryListing(story: ReadOnlySignal<StoryItem>) -> Element {{\n    let mut preview_state = consume_context::<Signal<PreviewState>>();\n    let StoryItem {{\n        title,\n        url,\n        by,\n        score,\n        time,\n        kids,\n        id,\n        ..\n    }} = story();\n    // New\n    let full_story = use_signal(|| None);\n\n    let url = url.as_deref().unwrap_or_default();\n    let hostname = url\n        .trim_start_matches("https://")\n        .trim_start_matches("http://")\n        .trim_start_matches("www.");\n    let score = format!("{{score}} {{}}", if score == 1 {{ " point" }} else {{ " points" }});\n    let comments = format!(\n        "{{}} {{}}",\n        kids.len(),\n        if kids.len() == 1 {{\n            " comment"\n        }} else {{\n            " comments"\n        }}\n    );\n    let time = time.format("%D %l:%M %p");\n\n    rsx! {{\n        div {{\n            padding: "0.5rem",\n            position: "relative",\n            onmouseenter: move |_event| {{ resolve_story(full_story, preview_state, id) }},\n            div {{ font_size: "1.5rem",\n                a {{\n                    href: url,\n                    onfocus: move |_event| {{ resolve_story(full_story, preview_state, id) }},\n                    // ...
\n", + name: "hackernews_async.rs".to_string(), + } + DemoFrame { hackernews_async::App {} } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum GuideFullCodeSection { + #[default] + Empty, + Conclusion, + Challenges, + TheFullCodeForTheHackerNewsProject, +} +impl std::str::FromStr for GuideFullCodeSection { + type Err = GuideFullCodeSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "conclusion" => Ok(Self::Conclusion), + "challenges" => Ok(Self::Challenges), + "the-full-code-for-the-hacker-news-project" => { + Ok(Self::TheFullCodeForTheHackerNewsProject) + } + _ => Err(GuideFullCodeSectionParseError), + } + } +} +impl std::fmt::Display for GuideFullCodeSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Conclusion => f.write_str("conclusion"), + Self::Challenges => f.write_str("challenges"), + Self::TheFullCodeForTheHackerNewsProject => { + f.write_str("the-full-code-for-the-hacker-news-project") + } + } + } +} +#[derive(Debug)] +pub struct GuideFullCodeSectionParseError; +impl std::fmt::Display for GuideFullCodeSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of GuideFullCodeSectionconclusion, challenges, the-full-code-for-the-hacker-news-project", + )?; + Ok(()) + } +} +impl std::error::Error for GuideFullCodeSectionParseError {} +#[component(no_case_check)] +pub fn GuideFullCode(section: GuideFullCodeSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "conclusion", + Link { + to: BookRoute::GuideFullCode { + section: GuideFullCodeSection::Conclusion, + }, + class: "header", + "Conclusion" + } + } + p { + "Well done! You've completed the Dioxus guide and built a hackernews application in Dioxus." + } + p { + "To continue your journey, you can attempt a challenge listed below, or look at the " + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Empty, + }, + "Dioxus reference" + } + "." + } + h2 { id: "challenges", + Link { + to: BookRoute::GuideFullCode { + section: GuideFullCodeSection::Challenges, + }, + class: "header", + "Challenges" + } + } + ul { + li { "Organize your components into separate files for better maintainability." } + li { "Give your app some style if you haven't already." } + li { + "Integrate your application with the " + Link { + to: BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }, + "Dioxus router" + } + "." + } + } + h2 { id: "the-full-code-for-the-hacker-news-project", + Link { + to: BookRoute::GuideFullCode { + section: GuideFullCodeSection::TheFullCodeForTheHackerNewsProject, + }, + class: "header", + "The full code for the hacker news project" + } + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\n\nfn main() {{\n    launch(App);\n}}\n\npub fn App() -> Element {{\n    use_context_provider(|| Signal::new(PreviewState::Unset));\n\n    rsx! {{\n        div {{ display: "flex", flex_direction: "row", width: "100%",\n            div {{ width: "50%", Stories {{}} }}\n            div {{ width: "50%", Preview {{}} }}\n        }}\n    }}\n}}\n\nfn Stories() -> Element {{\n    let stories = use_resource(move || get_stories(10));\n\n    match &*stories.read_unchecked() {{\n        Some(Ok(list)) => rsx! {{\n            div {{\n                for story in list {{\n                    StoryListing {{ story: story.clone() }}\n                }}\n            }}\n        }},\n        Some(Err(err)) => rsx! {{"An error occurred while fetching stories {{err}}"}},\n        None => rsx! {{"Loading items"}},\n    }}\n}}\n\nasync fn resolve_story(\n    mut full_story: Signal<Option<StoryPageData>>,\n    mut preview_state: Signal<PreviewState>,\n    story_id: i64,\n) {{\n    if let Some(cached) = full_story.as_ref() {{\n        *preview_state.write() = PreviewState::Loaded(cached.clone());\n        return;\n    }}\n\n    *preview_state.write() = PreviewState::Loading;\n    if let Ok(story) = get_story(story_id).await {{\n        *preview_state.write() = PreviewState::Loaded(story.clone());\n        *full_story.write() = Some(story);\n    }}\n}}\n\n#[component]\nfn StoryListing(story: ReadOnlySignal<StoryItem>) -> Element {{\n    let preview_state = consume_context::<Signal<PreviewState>>();\n    let StoryItem {{\n        title,\n        url,\n        by,\n        score,\n        time,\n        kids,\n        id,\n        ..\n    }} = story();\n    let full_story = use_signal(|| None);\n\n    let url = url.as_deref().unwrap_or_default();\n    let hostname = url\n        .trim_start_matches("https://")\n        .trim_start_matches("http://")\n        .trim_start_matches("www.");\n    let score = format!("{{score}} {{}}", if score == 1 {{ " point" }} else {{ " points" }});\n    let comments = format!(\n        "{{}} {{}}",\n        kids.len(),\n        if kids.len() == 1 {{\n            " comment"\n        }} else {{\n            " comments"\n        }}\n    );\n    let time = time.format("%D %l:%M %p");\n\n    rsx! {{\n        div {{\n            padding: "0.5rem",\n            position: "relative",\n            onmouseenter: move |_event| {{ resolve_story(full_story, preview_state, id) }},\n            div {{ font_size: "1.5rem",\n                a {{\n                    href: url,\n                    onfocus: move |_event| {{ resolve_story(full_story, preview_state, id) }},\n                    "{{title}}"\n                }}\n                a {{\n                    color: "gray",\n                    href: "https://news.ycombinator.com/from?site={{hostname}}",\n                    text_decoration: "none",\n                    " ({{hostname}})"\n                }}\n            }}\n            div {{ display: "flex", flex_direction: "row", color: "gray",\n                div {{ "{{score}}" }}\n                div {{ padding_left: "0.5rem", "by {{by}}" }}\n                div {{ padding_left: "0.5rem", "{{time}}" }}\n                div {{ padding_left: "0.5rem", "{{comments}}" }}\n            }}\n        }}\n    }}\n}}\n\n#[derive(Clone, Debug)]\nenum PreviewState {{\n    Unset,\n    Loading,\n    Loaded(StoryPageData),\n}}\n\nfn Preview() -> Element {{\n    let preview_state = consume_context::<Signal<PreviewState>>();\n\n    match preview_state() {{\n        PreviewState::Unset => rsx! {{"Hover over a story to preview it here"}},\n        PreviewState::Loading => rsx! {{"Loading..."}},\n        PreviewState::Loaded(story) => {{\n            rsx! {{\n                div {{ padding: "0.5rem",\n                    div {{ font_size: "1.5rem", a {{ href: story.item.url, "{{story.item.title}}" }} }}\n                    div {{ dangerous_inner_html: story.item.text }}\n                    for comment in &story.comments {{\n                        Comment {{ comment: comment.clone() }}\n                    }}\n                }}\n            }}\n        }}\n    }}\n}}\n\n#[component]\nfn Comment(comment: CommentData) -> Element {{\n    rsx! {{\n        div {{ padding: "0.5rem",\n            div {{ color: "gray", "by {{comment.by}}" }}\n            div {{ dangerous_inner_html: "{{comment.text}}" }}\n            for kid in &comment.sub_comments {{\n                Comment {{ comment: kid.clone() }}\n            }}\n        }}\n    }}\n}}\n\n// Define the Hackernews API and types\nuse chrono::{{DateTime, Utc}};\nuse futures::future::join_all;\nuse serde::{{Deserialize, Serialize}};\n\npub static BASE_API_URL: &str = "https://hacker-news.firebaseio.com/v0/";\npub static ITEM_API: &str = "item/";\npub static USER_API: &str = "user/";\nconst COMMENT_DEPTH: i64 = 2;\n\npub async fn get_story_preview(id: i64) -> Result<StoryItem, reqwest::Error> {{\n    let url = format!("{{}}{{}}{{}}.json", BASE_API_URL, ITEM_API, id);\n    reqwest::get(&url).await?.json().await\n}}\n\npub async fn get_stories(count: usize) -> Result<Vec<StoryItem>, reqwest::Error> {{\n    let url = format!("{{}}topstories.json", BASE_API_URL);\n    let stories_ids = &reqwest::get(&url).await?.json::<Vec<i64>>().await?[..count];\n\n    let story_futures = stories_ids[..usize::min(stories_ids.len(), count)]\n        .iter()\n        .map(|&story_id| get_story_preview(story_id));\n    Ok(join_all(story_futures)\n        .await\n        .into_iter()\n        .filter_map(|story| story.ok())\n        .collect())\n}}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryPageData {{\n    #[serde(flatten)]\n    pub item: StoryItem,\n    #[serde(default)]\n    pub comments: Vec<CommentData>,\n}}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct CommentData {{\n    pub id: i64,\n    /// there will be no by field if the comment was deleted\n    #[serde(default)]\n    pub by: String,\n    #[serde(default)]\n    pub text: String,\n    #[serde(with = "chrono::serde::ts_seconds")]\n    pub time: DateTime<Utc>,\n    #[serde(default)]\n    pub kids: Vec<i64>,\n    #[serde(default)]\n    pub sub_comments: Vec<CommentData>,\n    pub r#type: String,\n}}\n\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]\npub struct StoryItem {{\n    pub id: i64,\n    pub title: String,\n    pub url: Option<String>,\n    pub text: Option<String>,\n    #[serde(default)]\n    pub by: String,\n    #[serde(default)]\n    pub score: i64,\n    #[serde(default)]\n    pub descendants: i64,\n    #[serde(with = "chrono::serde::ts_seconds")]\n    pub time: DateTime<Utc>,\n    #[serde(default)]\n    pub kids: Vec<i64>,\n    pub r#type: String,\n}}\n\npub async fn get_story(id: i64) -> Result<StoryPageData, reqwest::Error> {{\n    let url = format!("{{}}{{}}{{}}.json", BASE_API_URL, ITEM_API, id);\n    let mut story = reqwest::get(&url).await?.json::<StoryPageData>().await?;\n    let comment_futures = story.item.kids.iter().map(|&id| get_comment(id));\n    let comments = join_all(comment_futures)\n        .await\n        .into_iter()\n        .filter_map(|c| c.ok())\n        .collect();\n\n    story.comments = comments;\n    Ok(story)\n}}\n\n#[async_recursion::async_recursion(?Send)]\npub async fn get_comment_with_depth(id: i64, depth: i64) -> Result<CommentData, reqwest::Error> {{\n    let url = format!("{{}}{{}}{{}}.json", BASE_API_URL, ITEM_API, id);\n    let mut comment = reqwest::get(&url).await?.json::<CommentData>().await?;\n    if depth > 0 {{\n        let sub_comments_futures = comment\n            .kids\n            .iter()\n            .map(|story_id| get_comment_with_depth(*story_id, depth - 1));\n        comment.sub_comments = join_all(sub_comments_futures)\n            .await\n            .into_iter()\n            .filter_map(|c| c.ok())\n            .collect();\n    }}\n    Ok(comment)\n}}\n\npub async fn get_comment(comment_id: i64) -> Result<CommentData, reqwest::Error> {{\n    get_comment_with_depth(comment_id, COMMENT_DEPTH).await\n}}
\n", + name: "hackernews_complete.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceIndexSection { + #[default] + Empty, + DioxusReference, + Rendering, + State, + Platforms, +} +impl std::str::FromStr for ReferenceIndexSection { + type Err = ReferenceIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "dioxus-reference" => Ok(Self::DioxusReference), + "rendering" => Ok(Self::Rendering), + "state" => Ok(Self::State), + "platforms" => Ok(Self::Platforms), + _ => Err(ReferenceIndexSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::DioxusReference => f.write_str("dioxus-reference"), + Self::Rendering => f.write_str("rendering"), + Self::State => f.write_str("state"), + Self::Platforms => f.write_str("platforms"), + } + } +} +#[derive(Debug)] +pub struct ReferenceIndexSectionParseError; +impl std::fmt::Display for ReferenceIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceIndexSectiondioxus-reference, rendering, state, platforms", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceIndexSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceIndex(section: ReferenceIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "dioxus-reference", + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::DioxusReference, + }, + class: "header", + "Dioxus Reference" + } + } + p { + "This Reference contains more detailed explanations for all concepts covered in the " + Link { + to: BookRoute::GuideIndex { + section: GuideIndexSection::Empty, + }, + "guide" + } + " and more." + } + h2 { id: "rendering", + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Rendering, + }, + class: "header", + "Rendering" + } + } + ul { + li { + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Empty, + }, + code { "RSX" } + } + " Rsx is a HTML-like macro that allows you to declare UI" + } + li { + Link { + to: BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Empty, + }, + code { "Components" } + } + " Components are the building blocks of UI in Dioxus" + } + li { + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::Empty, + }, + code { "Props" } + } + " Props allow you pass information to Components" + } + li { + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }, + code { "Event Listeners" } + } + " Event listeners let you respond to user input" + } + li { + Link { + to: BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::Empty, + }, + code { "User Input" } + } + " How to handle User input in Dioxus" + } + li { + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::Empty, + }, + code { "Dynamic Rendering" } + } + " How to dynamically render data in Dioxus" + } + } + h2 { id: "state", + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::State, + }, + class: "header", + "State" + } + } + ul { + li { + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::Empty, + }, + code { "Hooks" } + } + " Hooks allow you to create components state" + } + li { + Link { + to: BookRoute::ReferenceContext { + section: ReferenceContextSection::Empty, + }, + code { "Context" } + } + " Context allows you to create state in a parent and consume it in children" + } + li { + Link { + to: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::Empty, + }, + code { "Routing" } + } + " The router helps you manage the URL state" + } + li { + Link { + to: BookRoute::ReferenceUseResource { + section: ReferenceUseResourceSection::Empty, + }, + code { "Resource" } + } + " Use future allows you to create an async task and monitor it's state" + } + li { + Link { + to: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::Empty, + }, + code { "UseCoroutine" } + } + " Use coroutine helps you manage external state" + } + li { + Link { + to: BookRoute::ReferenceSpawn { + section: ReferenceSpawnSection::Empty, + }, + code { "Spawn" } + } + " Spawn creates an async task" + } + } + h2 { id: "platforms", + Link { + to: BookRoute::ReferenceIndex { + section: ReferenceIndexSection::Platforms, + }, + class: "header", + "Platforms" + } + } + ul { + li { + Link { + to: BookRoute::ReferenceChoosingAWebRenderer { + section: ReferenceChoosingAWebRendererSection::Empty, + }, + code { "Choosing a Web Renderer" } + } + " Overview of the different web renderers" + } + li { + Link { + to: BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::Empty, + }, + code { "Desktop" } + } + " Overview of desktop specific APIS" + } + li { + Link { + to: BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::Empty, + }, + code { "Web" } + } + " Overview of web specific APIS" + } + li { + Link { + to: BookRoute::ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection::Empty, + }, + code { "Fullstack" } + } + " Overview of Fullstack specific APIS" + ul { + li { + Link { + to: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::Empty, + }, + code { "Server Functions" } + } + " Server functions make it easy to communicate between your server and client" + } + li { + Link { + to: BookRoute::ReferenceFullstackExtractors { + section: ReferenceFullstackExtractorsSection::Empty, + }, + code { "Extractors" } + } + " Extractors allow you to get extra information out of the headers of a request" + } + li { + Link { + to: BookRoute::ReferenceFullstackMiddleware { + section: ReferenceFullstackMiddlewareSection::Empty, + }, + code { "Middleware" } + } + " Middleware allows you to wrap a server function request or response" + } + li { + Link { + to: BookRoute::ReferenceFullstackAuthentication { + section: ReferenceFullstackAuthenticationSection::Empty, + }, + code { "Authentication" } + } + " An overview of how to handle authentication with server functions" + } + li { + Link { + to: BookRoute::ReferenceFullstackRouting { + section: ReferenceFullstackRoutingSection::Empty, + }, + code { "Routing" } + } + " An overview of how to work with the router in the fullstack renderer" + } + } + } + li { + Link { + to: BookRoute::ReferenceSsr { + section: ReferenceSsrSection::Empty, + }, + code { "SSR" } + } + " Overview of the SSR renderer" + } + li { + Link { + to: BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::Empty, + }, + code { "Liveview" } + } + " Overview of liveview specific APIS" + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceRsxSection { + #[default] + Empty, + DescribingTheUi, + RsxFeatures, + Attributes, + ConditionalAttributes, + CustomAttributes, + SpecialAttributes, + TheHtmlEscapeHatch, + BooleanAttributes, + Interpolation, + Children, + Fragments, + Expressions, + Loops, + IfStatements, +} +impl std::str::FromStr for ReferenceRsxSection { + type Err = ReferenceRsxSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "describing-the-ui" => Ok(Self::DescribingTheUi), + "rsx-features" => Ok(Self::RsxFeatures), + "attributes" => Ok(Self::Attributes), + "conditional-attributes" => Ok(Self::ConditionalAttributes), + "custom-attributes" => Ok(Self::CustomAttributes), + "special-attributes" => Ok(Self::SpecialAttributes), + "the-html-escape-hatch" => Ok(Self::TheHtmlEscapeHatch), + "boolean-attributes" => Ok(Self::BooleanAttributes), + "interpolation" => Ok(Self::Interpolation), + "children" => Ok(Self::Children), + "fragments" => Ok(Self::Fragments), + "expressions" => Ok(Self::Expressions), + "loops" => Ok(Self::Loops), + "if-statements" => Ok(Self::IfStatements), + _ => Err(ReferenceRsxSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceRsxSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::DescribingTheUi => f.write_str("describing-the-ui"), + Self::RsxFeatures => f.write_str("rsx-features"), + Self::Attributes => f.write_str("attributes"), + Self::ConditionalAttributes => f.write_str("conditional-attributes"), + Self::CustomAttributes => f.write_str("custom-attributes"), + Self::SpecialAttributes => f.write_str("special-attributes"), + Self::TheHtmlEscapeHatch => f.write_str("the-html-escape-hatch"), + Self::BooleanAttributes => f.write_str("boolean-attributes"), + Self::Interpolation => f.write_str("interpolation"), + Self::Children => f.write_str("children"), + Self::Fragments => f.write_str("fragments"), + Self::Expressions => f.write_str("expressions"), + Self::Loops => f.write_str("loops"), + Self::IfStatements => f.write_str("if-statements"), + } + } +} +#[derive(Debug)] +pub struct ReferenceRsxSectionParseError; +impl std::fmt::Display for ReferenceRsxSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceRsxSectiondescribing-the-ui, rsx-features, attributes, conditional-attributes, custom-attributes, special-attributes, the-html-escape-hatch, boolean-attributes, interpolation, children, fragments, expressions, loops, if-statements", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceRsxSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceRsx(section: ReferenceRsxSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "describing-the-ui", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::DescribingTheUi, + }, + class: "header", + "Describing the UI" + } + } + p { + "Dioxus is a " + em { "declarative" } + " framework. This means that instead of telling Dioxus what to do (e.g. to \"create an element\" or \"set the color to red\") we simply " + em { "declare" } + " what we want the UI to look like using RSX." + } + p { "You have already seen a simple example of RSX syntax in the \"hello world\" application:" } + CodeBlock { + contents: "
\n// define a component that renders a div with the text "Hello, world!"\nfn App() -> Element {{\n    rsx! {{\n        div {{ "Hello, world!" }}\n    }}\n}}
\n", + name: "hello_world_desktop.rs".to_string(), + } + p { + "Here, we use the " + code { "rsx!" } + " macro to " + em { "declare" } + " that we want a " + code { "div" } + " element, containing the text " + code { "\"Hello, world!\"" } + ". Dioxus takes the RSX and constructs a UI from it." + } + h2 { id: "rsx-features", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::RsxFeatures, + }, + class: "header", + "RSX Features" + } + } + p { + "RSX is very similar to HTML in that it describes elements with attributes and children. Here's an empty " + code { "button" } + " element in RSX, as well as the resulting HTML:" + } + CodeBlock { + contents: "
\nrsx! {{\n    button {{\n        // attributes / listeners\n        // children\n        "Hello, World!"\n    }}\n}}
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::Button {} } + h3 { id: "attributes", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Attributes, + }, + class: "header", + "Attributes" + } + } + p { + "Attributes (and " + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::Empty, + }, + "event handlers" + } + ") modify the behavior or appearance of the element they are attached to. They are specified inside the " + code { "{{}}" } + " brackets, using the " + code { "name: value" } + " syntax. You can provide the value as a literal in the RSX:" + } + CodeBlock { + contents: "
\nrsx! {{\n    img {{\n        src: "https://avatars.githubusercontent.com/u/79236386?s=200&v=4",\n        class: "primary_button",\n        width: "10px",\n    }}\n}}
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::Attributes {} } + p { + "Some attributes, such as the " + code { "type" } + " attribute for " + code { "input" } + " elements won't work on their own in Rust. This is because " + code { "type" } + " is a reserved Rust keyword. To get around this, Dioxus uses the " + code { "r#" } + " specifier:" + } + CodeBlock { + contents: "
\nrsx! {{\n    input {{ r#type: "text", color: "red" }}\n}}
\n", + name: "rsx_overview.rs".to_string(), + } + blockquote { + p { + "Note: All attributes defined in " + code { "dioxus-html" } + " follow the snake_case naming convention. They transform their " + code { "snake_case" } + " names to HTML's " + code { "camelCase" } + " attributes." + } + } + blockquote { + p { + "Note: Styles can be used directly outside of the " + code { "style:" } + " attribute. In the above example, " + code { "color: \"red\"" } + " is turned into " + code { "style=\"color: red\"" } + "." + } + } + h4 { id: "conditional-attributes", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::ConditionalAttributes, + }, + class: "header", + "Conditional Attributes" + } + } + p { + "You can also conditionally include attributes by using an if statement without an else branch. This is useful for adding an attribute only if a certain condition is met:" + } + CodeBlock { + contents: "
\nlet large_font = true;\nrsx! {{\n    div {{ class: if large_font {{ "text-xl" }}, "Hello, World!" }}\n}}
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::ConditionalAttributes {} } + p { + "Repeating an attribute joins the values with a space. This makes it easy to add values like classes conditionally:" + } + CodeBlock { + contents: "
\nlet large_font = true;\nrsx! {{\n    div {{\n        class: "base-class another-class",\n        class: if large_font {{ "text-xl" }},\n        "Hello, World!"\n    }}\n}}
\n", + name: "rsx_overview.rs".to_string(), + } + h4 { id: "custom-attributes", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::CustomAttributes, + }, + class: "header", + "Custom Attributes" + } + } + p { + "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:" + } + CodeBlock { + contents: "
\nrsx! {{\n    div {{ "style": "width: 20px; height: 20px; background-color: red;" }}\n}}
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::CustomAttributes {} } + h3 { id: "special-attributes", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::SpecialAttributes, + }, + class: "header", + "Special Attributes" + } + } + p { "While most attributes are simply passed on to the HTML, some have special behaviors." } + h4 { id: "the-html-escape-hatch", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::TheHtmlEscapeHatch, + }, + class: "header", + "The HTML Escape Hatch" + } + } + p { + "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 " + code { "dangerous_inner_html" } + "." + } + p { + "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 " + Link { to: "https://dioxuslabs.com", "Dioxus homepage" } + ":" + } + CodeBlock { + contents: "
\n// this should come from a trusted source\nlet contents = "live <b>dangerously</b>";\n\nrsx! {{\n    div {{ dangerous_inner_html: "{{contents}}" }}\n}}
\n", + name: "dangerous_inner_html.rs".to_string(), + } + DemoFrame { dangerous_inner_html::App {} } + blockquote { + p { + "Note! This attribute is called \"dangerous_inner_html\" because it is " + strong { "dangerous" } + " to pass it data you don't trust. If you're not careful, you can easily expose " + Link { to: "https://en.wikipedia.org/wiki/Cross-site_scripting", + "cross-site scripting (XSS)" + } + " attacks to your users." + } + p { + "If you're handling untrusted input, make sure to sanitize your HTML before passing it into " + code { "dangerous_inner_html" } + " – or just pass it to a Text Element to escape any HTML tags." + } + } + h4 { id: "boolean-attributes", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::BooleanAttributes, + }, + class: "header", + "Boolean Attributes" + } + } + p { + "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 " + code { "\"false\"" } + " will cause them to be removed from the target element." + } + p { + "So this RSX wouldn't actually render the " + code { "hidden" } + " attribute:" + } + CodeBlock { + contents: "
\nrsx! {{\n    div {{ hidden: false, "hello" }}\n}}
\n", + name: "boolean_attribute.rs".to_string(), + } + DemoFrame { boolean_attribute::App {} } + p { + "Not all attributes work like this however. " + em { "Only the following attributes" } + " have this behavior:" + } + ul { + li { + code { "allowfullscreen" } + } + li { + code { "allowpaymentrequest" } + } + li { + code { "async" } + } + li { + code { "autofocus" } + } + li { + code { "autoplay" } + } + li { + code { "checked" } + } + li { + code { "controls" } + } + li { + code { "default" } + } + li { + code { "defer" } + } + li { + code { "disabled" } + } + li { + code { "formnovalidate" } + } + li { + code { "hidden" } + } + li { + code { "ismap" } + } + li { + code { "itemscope" } + } + li { + code { "loop" } + } + li { + code { "multiple" } + } + li { + code { "muted" } + } + li { + code { "nomodule" } + } + li { + code { "novalidate" } + } + li { + code { "open" } + } + li { + code { "playsinline" } + } + li { + code { "readonly" } + } + li { + code { "required" } + } + li { + code { "reversed" } + } + li { + code { "selected" } + } + li { + code { "truespeed" } + } + } + p { + "For any other attributes, a value of " + code { "\"false\"" } + " will be sent directly to the DOM." + } + h3 { id: "interpolation", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Interpolation, + }, + class: "header", + "Interpolation" + } + } + p { + "Similarly to how you can " + Link { to: "https://doc.rust-lang.org/rust-by-example/hello/print/fmt.html", + "format" + } + " Rust strings, you can also interpolate in RSX text. Use " + code { "{{variable}}" } + " to Display the value of a variable in a string, or " + code { "{{variable:?}}" } + " to use the Debug representation:" + } + CodeBlock { + contents: "
\nlet coordinates = (42, 0);\nlet country = "es";\nrsx! {{\n    div {{\n        class: "country-{{country}}",\n        left: "{{coordinates.0:?}}",\n        top: "{{coordinates.1:?}}",\n        // arbitrary expressions are allowed,\n        // as long as they don't contain `{{}}`\n        div {{ "{{country.to_uppercase()}}" }}\n        div {{ "{{7*6}}" }}\n        // {{}} can be escaped with {{{{}}}}\n        div {{ "{{{{}}}}" }}\n    }}\n}}
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::Formatting {} } + h3 { id: "children", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Children, + }, + class: "header", + "Children" + } + } + p { + "To add children to an element, put them inside the " + code { "{{}}" } + " brackets after all attributes and listeners in the element. They can be other elements, text, or " + Link { + to: BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Empty, + }, + "components" + } + ". For example, you could have an " + code { "ol" } + " (ordered list) element, containing 3 " + code { "li" } + " (list item) elements, each of which contains some text:" + } + CodeBlock { + contents: "
\nrsx! {{\n    ol {{\n        li {{ "First Item" }}\n        li {{ "Second Item" }}\n        li {{ "Third Item" }}\n    }}\n}}
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::Children {} } + h3 { id: "fragments", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Fragments, + }, + class: "header", + "Fragments" + } + } + p { + "You can render multiple elements at the top level of " + code { "rsx!" } + " and they will be automatically grouped." + } + CodeBlock { + contents: "
\nrsx! {{\n    p {{ "First Item" }}\n    p {{ "Second Item" }}\n}}
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::ManyRoots {} } + h3 { id: "expressions", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Expressions, + }, + class: "header", + "Expressions" + } + } + p { + "You can include arbitrary Rust expressions as children within RSX by surrounding your expression with " + code { "{{}}" } + "s. Any expression that implements " + Link { to: "https://docs.rs/dioxus-core/0.3/dioxus_core/trait.IntoDynNode.html", + "IntoDynNode" + } + " can be used within rsx. This is useful for displaying data from an " + Link { to: "https://doc.rust-lang.org/stable/book/ch13-02-iterators.html#processing-a-series-of-items-with-iterators", + "iterator" + } + ":" + } + CodeBlock { + contents: "
\nlet text = "Dioxus";\nrsx! {{\n    span {{\n        {{text.to_uppercase()}}\n        // create a list of text from 0 to 9\n        {{(0..10).map(|i| rsx! {{\n        "{{i}}"\n        }})}}\n    }}\n}}
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::Expression {} } + h3 { id: "loops", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::Loops, + }, + class: "header", + "Loops" + } + } + p { "In addition to iterators you can also use for loops directly within RSX:" } + CodeBlock { + contents: "
\nrsx! {{\n    // use a for loop where the body itself is RSX\n    div {{\n        // create a list of text from 0 to 9\n        for i in 0..3 {{\n            // NOTE: the body of the loop is RSX not a rust statement\n            div {{ "{{i}}" }}\n        }}\n    }}\n    // iterator equivalent\n    div {{\n        {{(0..3).map(|i| rsx! {{\n            div {{ "{{i}}" }}\n        }})}}\n    }}\n}}
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::Loops {} } + h3 { id: "if-statements", + Link { + to: BookRoute::ReferenceRsx { + section: ReferenceRsxSection::IfStatements, + }, + class: "header", + "If statements" + } + } + p { "You can also use if statements without an else branch within RSX:" } + CodeBlock { + contents: "
\nrsx! {{\n    // use if statements without an else\n    if true {{\n        div {{ "true" }}\n    }}\n}}
\n", + name: "rsx_overview.rs".to_string(), + } + DemoFrame { rsx_overview::IfStatements {} } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceComponentsSection { + #[default] + Empty, + Components, +} +impl std::str::FromStr for ReferenceComponentsSection { + type Err = ReferenceComponentsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "components" => Ok(Self::Components), + _ => Err(ReferenceComponentsSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceComponentsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Components => f.write_str("components"), + } + } +} +#[derive(Debug)] +pub struct ReferenceComponentsSectionParseError; +impl std::fmt::Display for ReferenceComponentsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of ReferenceComponentsSectioncomponents")?; + Ok(()) + } +} +impl std::error::Error for ReferenceComponentsSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceComponents(section: ReferenceComponentsSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "components", + Link { + to: BookRoute::ReferenceComponents { + section: ReferenceComponentsSection::Components, + }, + class: "header", + "Components" + } + } + p { + "Just like you wouldn't want to write a complex program in a single, long, " + code { "main" } + " function, you shouldn't build a complex UI in a single " + code { "App" } + " function. Instead, you should break down the functionality of an app in logical parts called components." + } + p { + "A component is a Rust function, named in UpperCamelCase, that either takes no parameters or a properties struct and returns an " + code { "Element" } + " describing the UI it wants to render." + } + CodeBlock { + contents: "
\n// define a component that renders a div with the text "Hello, world!"\nfn App() -> Element {{\n    rsx! {{\n        div {{ "Hello, world!" }}\n    }}\n}}
\n", + name: "hello_world_desktop.rs".to_string(), + } + blockquote { + p { + "You'll probably want to add " + code { "#![allow(non_snake_case)]" } + " to the top of your crate to avoid warnings about UpperCamelCase component names" + } + } + p { + "A Component is responsible for some rendering task – typically, rendering an isolated part of the user interface. For example, you could have an " + code { "About" } + " component that renders a short description of Dioxus Labs:" + } + CodeBlock { + contents: "
\npub fn About() -> Element {{\n    rsx! {{\n        p {{\n            b {{ "Dioxus Labs" }}\n            " An Open Source project dedicated to making Rust UI wonderful."\n        }}\n    }}\n}}
\n", + name: "components.rs".to_string(), + } + DemoFrame { components::About {} } + p { + "Then, you can render your component in another component, similarly to how elements are rendered:" + } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    rsx! {{\n        About {{}}\n        About {{}}\n    }}\n}}
\n", + name: "components.rs".to_string(), + } + DemoFrame { components::App {} } + blockquote { + p { + "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!" + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceComponentPropsSection { + #[default] + Empty, + ComponentProps, + Deriveprops, + PropOptions, + OptionalProps, + ExplicitlyRequiredOption, + DefaultProps, + AutomaticConversionWithInto, + TheComponentMacro, + ComponentChildren, + TheChildrenField, +} +impl std::str::FromStr for ReferenceComponentPropsSection { + type Err = ReferenceComponentPropsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "component-props" => Ok(Self::ComponentProps), + "deriveprops" => Ok(Self::Deriveprops), + "prop-options" => Ok(Self::PropOptions), + "optional-props" => Ok(Self::OptionalProps), + "explicitly-required-option" => Ok(Self::ExplicitlyRequiredOption), + "default-props" => Ok(Self::DefaultProps), + "automatic-conversion-with-into" => Ok(Self::AutomaticConversionWithInto), + "the-component-macro" => Ok(Self::TheComponentMacro), + "component-children" => Ok(Self::ComponentChildren), + "the-children-field" => Ok(Self::TheChildrenField), + _ => Err(ReferenceComponentPropsSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceComponentPropsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ComponentProps => f.write_str("component-props"), + Self::Deriveprops => f.write_str("deriveprops"), + Self::PropOptions => f.write_str("prop-options"), + Self::OptionalProps => f.write_str("optional-props"), + Self::ExplicitlyRequiredOption => f.write_str("explicitly-required-option"), + Self::DefaultProps => f.write_str("default-props"), + Self::AutomaticConversionWithInto => f.write_str("automatic-conversion-with-into"), + Self::TheComponentMacro => f.write_str("the-component-macro"), + Self::ComponentChildren => f.write_str("component-children"), + Self::TheChildrenField => f.write_str("the-children-field"), + } + } +} +#[derive(Debug)] +pub struct ReferenceComponentPropsSectionParseError; +impl std::fmt::Display for ReferenceComponentPropsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceComponentPropsSectioncomponent-props, deriveprops, prop-options, optional-props, explicitly-required-option, default-props, automatic-conversion-with-into, the-component-macro, component-children, the-children-field", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceComponentPropsSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceComponentProps( + section: ReferenceComponentPropsSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "component-props", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::ComponentProps, + }, + class: "header", + "Component Props" + } + } + p { + "Just like you can pass arguments to a function or attributes to an element, 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." + } + h2 { id: "deriveprops", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::Deriveprops, + }, + class: "header", + "derive(Props)" + } + } + p { + "Component props are a single struct annotated with " + code { "#[derive(PartialEq, Clone, Props)]" } + ". For a component to accept props, the type of its argument must be " + code { "YourPropsStruct" } + "." + } + p { "Example:" } + CodeBlock { + contents: "
\n#[derive(PartialEq, Props, Clone)]\nstruct LikesProps {{\n    score: i32,\n}}\n\nfn Likes(props: LikesProps) -> Element {{\n    rsx! {{\n        div {{\n            "This post has "\n            b {{ "{{props.score}}" }}\n            " likes"\n        }}\n    }}\n}}
\n", + name: "component_owned_props.rs".to_string(), + } + p { + "You can then pass prop values to the component the same way you would pass attributes to an element:" + } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    rsx! {{ Likes {{ score: 42 }} }}\n}}
\n", + name: "component_owned_props.rs".to_string(), + } + DemoFrame { component_owned_props::App {} } + h2 { id: "prop-options", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::PropOptions, + }, + class: "header", + "Prop Options" + } + } + p { + "The " + code { "#[derive(Props)]" } + " macro has some features that let you customize the behavior of props." + } + h3 { id: "optional-props", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::OptionalProps, + }, + class: "header", + "Optional Props" + } + } + p { + "You can create optional fields by using the " + code { "Option<…>" } + " type for a field:" + } + CodeBlock { + contents: "
\n#[derive(PartialEq, Clone, Props)]\nstruct OptionalProps {{\n    title: String,\n    subtitle: Option<String>,\n}}\n\nfn Title(props: OptionalProps) -> Element {{\n    rsx! {{\n        h1 {{ "{{props.title}}: ", {{props.subtitle.unwrap_or_else(|| "No subtitle provided".to_string())}} }}\n    }}\n}}
\n", + name: "component_props_options.rs".to_string(), + } + p { "Then, you can choose to either provide them or not:" } + CodeBlock { + contents: "
\nTitle {{ title: "Some Title" }}\nTitle {{ title: "Some Title", subtitle: "Some Subtitle" }}\n// Providing an Option explicitly won't compile though:\n// Title {{\n//     title: "Some Title",\n//     subtitle: None,\n// }},
\n", + name: "component_props_options.rs".to_string(), + } + h3 { id: "explicitly-required-option", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::ExplicitlyRequiredOption, + }, + class: "header", + "Explicitly Required Option" + } + } + p { + "If you want to explicitly require an " + code { "Option" } + ", and not an optional prop, you can annotate it with " + code { "#[props(!optional)]" } + ":" + } + CodeBlock { + contents: "
\n#[derive(PartialEq, Clone, Props)]\nstruct ExplicitOptionProps {{\n    title: String,\n    #[props(!optional)]\n    subtitle: Option<String>,\n}}\n\nfn ExplicitOption(props: ExplicitOptionProps) -> Element {{\n    rsx! {{\n        h1 {{ "{{props.title}}: ", {{props.subtitle.unwrap_or_else(|| "No subtitle provided".to_string())}} }}\n    }}\n}}
\n", + name: "component_props_options.rs".to_string(), + } + p { + "Then, you have to explicitly pass either " + code { "Some(\"str\")" } + " or " + code { "None" } + ":" + } + CodeBlock { + contents: "
\nExplicitOption {{ title: "Some Title", subtitle: None }}\nExplicitOption {{ title: "Some Title", subtitle: Some("Some Title".to_string()) }}\n// This won't compile:\n// ExplicitOption {{\n//     title: "Some Title",\n// }},
\n", + name: "component_props_options.rs".to_string(), + } + h3 { id: "default-props", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::DefaultProps, + }, + class: "header", + "Default Props" + } + } + p { + "You can use " + code { "#[props(default = 42)]" } + " to make a field optional and specify its default value:" + } + CodeBlock { + contents: "
\n#[derive(PartialEq, Props, Clone)]\nstruct DefaultProps {{\n    // default to 42 when not provided\n    #[props(default = 42)]\n    number: i64,\n}}\n\nfn DefaultComponent(props: DefaultProps) -> Element {{\n    rsx! {{ h1 {{ "{{props.number}}" }} }}\n}}
\n", + name: "component_props_options.rs".to_string(), + } + p { "Then, similarly to optional props, you don't have to provide it:" } + CodeBlock { + contents: "
\nDefaultComponent {{ number: 5 }}\nDefaultComponent {{}}
\n", + name: "component_props_options.rs".to_string(), + } + h3 { id: "automatic-conversion-with-into", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::AutomaticConversionWithInto, + }, + class: "header", + "Automatic Conversion with into" + } + } + p { + "It is common for Rust functions to accept " + code { "impl Into" } + " rather than just " + code { "SomeType" } + " to support a wider range of parameters. If you want similar functionality with props, you can use " + code { "#[props(into)]" } + ". For example, you could add it on a " + code { "String" } + " prop – and " + code { "&str" } + " will also be automatically accepted, as it can be converted into " + code { "String" } + ":" + } + CodeBlock { + contents: "
\n#[derive(PartialEq, Props, Clone)]\nstruct IntoProps {{\n    #[props(into)]\n    string: String,\n}}\n\nfn IntoComponent(props: IntoProps) -> Element {{\n    rsx! {{ h1 {{ "{{props.string}}" }} }}\n}}
\n", + name: "component_props_options.rs".to_string(), + } + p { "Then, you can use it so:" } + CodeBlock { + contents: "
\nIntoComponent {{ string: "some &str" }}
\n", + name: "component_props_options.rs".to_string(), + } + h2 { id: "the-component-macro", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::TheComponentMacro, + }, + class: "header", + "The component macro" + } + } + p { + "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 " + code { "props.whatever" } + ", we could just use " + code { "whatever" } + " directly!" + } + p { + code { "component" } + " allows you to do just that. Instead of typing the \"full\" version:" + } + CodeBlock { contents: "
\n#[derive(Props, Clone, PartialEq)]\nstruct TitleCardProps {{\n    title: String,\n}}\n\nfn TitleCard(props: TitleCardProps) -> Element {{\n    rsx!{{\n        h1 {{ "{{props.title}}" }}\n    }}\n}}
\n" } + p { + "...you can define a function that accepts props as arguments. Then, just annotate it with " + code { "#[component]" } + ", and the macro will turn it into a regular Component for you:" + } + CodeBlock { contents: "
\n#[component]\nfn TitleCard(title: String) -> Element {{\n    rsx!{{\n        h1 {{ "{{title}}" }}\n    }}\n}}
\n" } + blockquote { + p { + "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." + } + } + h2 { id: "component-children", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::ComponentChildren, + }, + class: "header", + "Component Children" + } + } + p { + "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 " + code { "Element" } + ":" + } + CodeBlock { + contents: "
\n#[derive(PartialEq, Clone, Props)]\nstruct ClickableProps {{\n    href: String,\n    body: Element,\n}}\n\nfn Clickable(props: ClickableProps) -> Element {{\n    rsx! {{\n        a {{ href: "{{props.href}}", class: "fancy-button", {{props.body}} }}\n    }}\n}}
\n", + name: "component_element_props.rs".to_string(), + } + p { + "Then, when rendering the component, you can pass in the output of " + code { "rsx!{{...}}" } + ":" + } + CodeBlock { + contents: "
\nrsx! {{\n    Clickable {{\n        href: "https://www.youtube.com/watch?v=C-M2hs3sXGo",\n        body: rsx! {{\n            "How to " i {{ "not" }} " be seen"\n        }}\n    }}\n}}
\n", + name: "component_element_props.rs".to_string(), + } + blockquote { + p { + "Warning: While it may compile, do not include the same " + code { "Element" } + " more than once in the RSX. The resulting behavior is unspecified." + } + } + h3 { id: "the-children-field", + Link { + to: BookRoute::ReferenceComponentProps { + section: ReferenceComponentPropsSection::TheChildrenField, + }, + class: "header", + "The children field" + } + } + p { + "Rather than passing the RSX through a regular prop, you may wish to accept children similarly to how elements can have children. The \"magic\" " + code { "children" } + " prop lets you achieve this:" + } + CodeBlock { + contents: "
\n#[derive(PartialEq, Clone, Props)]\nstruct ClickableProps {{\n    href: String,\n    children: Element,\n}}\n\nfn Clickable(props: ClickableProps) -> Element {{\n    rsx! {{\n        a {{ href: "{{props.href}}", class: "fancy-button", {{props.children}} }}\n    }}\n}}
\n", + name: "component_children.rs".to_string(), + } + p { + "This makes using the component much simpler: simply put the RSX inside the " + code { "{{}}" } + " brackets – and there is no need for a " + code { "render" } + " call or another macro!" + } + CodeBlock { + contents: "
\nrsx! {{\n    Clickable {{ href: "https://www.youtube.com/watch?v=C-M2hs3sXGo",\n        "How to "\n        i {{ "not" }}\n        " be seen"\n    }}\n}}
\n", + name: "component_children.rs".to_string(), + } + DemoFrame { component_children::App {} } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceEventHandlersSection { + #[default] + Empty, + EventHandlers, + TheEventObject, + EventPropagation, + PreventDefault, + HandlerProps, + AsyncEventHandlers, + CustomData, +} +impl std::str::FromStr for ReferenceEventHandlersSection { + type Err = ReferenceEventHandlersSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "event-handlers" => Ok(Self::EventHandlers), + "the-event-object" => Ok(Self::TheEventObject), + "event-propagation" => Ok(Self::EventPropagation), + "prevent-default" => Ok(Self::PreventDefault), + "handler-props" => Ok(Self::HandlerProps), + "async-event-handlers" => Ok(Self::AsyncEventHandlers), + "custom-data" => Ok(Self::CustomData), + _ => Err(ReferenceEventHandlersSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceEventHandlersSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::EventHandlers => f.write_str("event-handlers"), + Self::TheEventObject => f.write_str("the-event-object"), + Self::EventPropagation => f.write_str("event-propagation"), + Self::PreventDefault => f.write_str("prevent-default"), + Self::HandlerProps => f.write_str("handler-props"), + Self::AsyncEventHandlers => f.write_str("async-event-handlers"), + Self::CustomData => f.write_str("custom-data"), + } + } +} +#[derive(Debug)] +pub struct ReferenceEventHandlersSectionParseError; +impl std::fmt::Display for ReferenceEventHandlersSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceEventHandlersSectionevent-handlers, the-event-object, event-propagation, prevent-default, handler-props, async-event-handlers, custom-data", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceEventHandlersSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceEventHandlers(section: ReferenceEventHandlersSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "event-handlers", + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::EventHandlers, + }, + class: "header", + "Event Handlers" + } + } + p { + "Event handlers are used to respond to user actions. For example, an event handler could be triggered when the user clicks, scrolls, moves the mouse, or types a character." + } + p { + "Event handlers are attached to elements. For example, we usually don't care about all the clicks that happen within an app, only those on a particular button." + } + p { + "Event handlers are similar to regular attributes, but their name usually starts with " + code { "on" } + "- and they accept closures as values. The closure will be called whenever the event it listens for is triggered and will be passed that event." + } + p { + "For example, to handle clicks on an element, we can specify an " + code { "onclick" } + " handler:" + } + CodeBlock { + contents: "
\nrsx! {{\n    button {{ onclick: move |event| log::info!("Clicked! Event: {{event:?}}"), "click me!" }}\n}}
\n", + name: "event_click.rs".to_string(), + } + DemoFrame { event_click::App {} } + h2 { id: "the-event-object", + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::TheEventObject, + }, + class: "header", + "The Event object" + } + } + p { + "Event handlers receive an " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/struct.Event.html", + code { "Event" } + } + " object containing information about the event. Different types of events contain different types of data. For example, mouse-related events contain " + Link { to: "https://docs.rs/dioxus/latest/dioxus/events/struct.MouseData.html", + code { "MouseData" } + } + ", which tells you things like where the mouse was clicked and what mouse buttons were used." + } + p { "In the example above, this event data was logged to the terminal:" } + CodeBlock { + contents: "
\nClicked! Event: UiEvent {{ bubble_state: Cell {{ value: true }}, data: MouseData {{ coordinates: Coordinates {{ screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }}, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) }} }}\nClicked! Event: UiEvent {{ bubble_state: Cell {{ value: true }}, data: MouseData {{ coordinates: Coordinates {{ screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }}, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) }} }}
\n", + } + p { + "To learn what the different event types for HTML provide, read the " + Link { to: "https://docs.rs/dioxus-html/latest/dioxus_html/events/index.html", + "events module docs" + } + "." + } + h3 { id: "event-propagation", + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::EventPropagation, + }, + class: "header", + "Event propagation" + } + } + p { + "Some events will trigger first on the element the event originated at upward. For example, a click event on a " + code { "button" } + " inside a " + code { "div" } + " would first trigger the button's event listener and then the div's event listener." + } + blockquote { + p { + "For more information about event propagation see " + Link { to: "https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling", + "the mdn docs on event bubbling" + } + } + } + p { + "If you want to prevent this behavior, you can call " + code { "stop_propagation()" } + " on the event:" + } + CodeBlock { + contents: "
\nrsx! {{\n    div {{ onclick: move |_event| {{}},\n        "outer"\n        button {{\n            onclick: move |event| {{\n                event.stop_propagation();\n            }},\n            "inner"\n        }}\n    }}\n}}
\n", + name: "event_nested.rs".to_string(), + } + h2 { id: "prevent-default", + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::PreventDefault, + }, + class: "header", + "Prevent Default" + } + } + p { + "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." + } + p { + "In some instances, might want to avoid this default behavior. For this, you can add the " + code { "prevent_default" } + " attribute with the name of the handler whose default behavior you want to stop. This attribute can be used for multiple handlers using their name separated by spaces:" + } + CodeBlock { + contents: "
\nrsx! {{\n    a {{\n        href: "https://example.com",\n        prevent_default: "onclick",\n        "example.com"\n    }}\n}}
\n", + name: "event_prevent_default.rs".to_string(), + } + DemoFrame { event_prevent_default::App {} } + p { "Any event handlers will still be called." } + blockquote { + p { + "Normally, in React or JavaScript, you'd call \"preventDefault\" on the event in the callback. Dioxus does " + em { "not" } + " currently support this behavior. Note: this means you cannot conditionally prevent default behavior based on the data in the event." + } + } + h2 { id: "handler-props", + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::HandlerProps, + }, + class: "header", + "Handler Props" + } + } + p { + "Sometimes, you might want to make a component that accepts an event handler. A simple example would be a " + code { "FancyButton" } + " component, which accepts an " + code { "onclick" } + " handler:" + } + CodeBlock { + contents: "
\n#[derive(PartialEq, Clone, Props)]\npub struct FancyButtonProps {{\n    onclick: EventHandler<MouseEvent>,\n}}\n\npub fn FancyButton(props: FancyButtonProps) -> Element {{\n    rsx! {{\n        button {{\n            class: "fancy-button",\n            onclick: move |evt| props.onclick.call(evt),\n            "click me pls."\n        }}\n    }}\n}}
\n", + name: "event_handler_prop.rs".to_string(), + } + p { "Then, you can use it like any other handler:" } + CodeBlock { + contents: "
\nrsx! {{\n    FancyButton {{\n        onclick: move |event| println!("Clicked! {{event:?}}"),\n    }}\n}}
\n", + name: "event_handler_prop.rs".to_string(), + } + blockquote { + p { + "Note: just like any other attribute, you can name the handlers anything you want! Any closure you pass in will automatically be turned into an " + code { "EventHandler" } + "." + } + } + h4 { id: "async-event-handlers", + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::AsyncEventHandlers, + }, + class: "header", + "Async Event Handlers" + } + } + p { + "Passing " + code { "EventHandler" } + "s as props does not support passing a closure that returns an async block. Instead, you must manually call " + code { "spawn" } + " to do async operations:" + } + CodeBlock { + contents: "
\nrsx! {{\n    FancyButton {{\n        // This does not work!\n        // onclick: move |event| async move {{\n        //      println!("Clicked! {{event:?}}");\n        // }},\n\n        // This does work!\n        onclick: move |event| {{\n            spawn(async move {{\n                println!("Clicked! {{event:?}}");\n            }});\n        }},\n    }}\n}}
\n", + name: "event_handler_prop.rs".to_string(), + } + p { "This is only the case for custom event handlers as props." } + h2 { id: "custom-data", + Link { + to: BookRoute::ReferenceEventHandlers { + section: ReferenceEventHandlersSection::CustomData, + }, + class: "header", + "Custom Data" + } + } + p { + "Event Handlers are generic over any type, so you can pass in any data you want to them, e.g:" + } + CodeBlock { + contents: "
\nstruct ComplexData(i32);\n\n#[derive(PartialEq, Clone, Props)]\npub struct CustomFancyButtonProps {{\n    onclick: EventHandler<ComplexData>,\n}}\n\npub fn CustomFancyButton(props: CustomFancyButtonProps) -> Element {{\n    rsx! {{\n        button {{\n            class: "fancy-button",\n            onclick: move |_| props.onclick.call(ComplexData(0)),\n            "click me pls."\n        }}\n    }}\n}}
\n", + name: "event_handler_prop.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceHooksSection { + #[default] + Empty, + HooksAndComponentState, + UseSignalHook, + RulesOfHooks, + NoHooksInConditionals, + NoHooksInClosures, + NoHooksInLoops, + AdditionalResources, +} +impl std::str::FromStr for ReferenceHooksSection { + type Err = ReferenceHooksSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "hooks-and-component-state" => Ok(Self::HooksAndComponentState), + "use-signal-hook" => Ok(Self::UseSignalHook), + "rules-of-hooks" => Ok(Self::RulesOfHooks), + "no-hooks-in-conditionals" => Ok(Self::NoHooksInConditionals), + "no-hooks-in-closures" => Ok(Self::NoHooksInClosures), + "no-hooks-in-loops" => Ok(Self::NoHooksInLoops), + "additional-resources" => Ok(Self::AdditionalResources), + _ => Err(ReferenceHooksSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceHooksSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::HooksAndComponentState => f.write_str("hooks-and-component-state"), + Self::UseSignalHook => f.write_str("use-signal-hook"), + Self::RulesOfHooks => f.write_str("rules-of-hooks"), + Self::NoHooksInConditionals => f.write_str("no-hooks-in-conditionals"), + Self::NoHooksInClosures => f.write_str("no-hooks-in-closures"), + Self::NoHooksInLoops => f.write_str("no-hooks-in-loops"), + Self::AdditionalResources => f.write_str("additional-resources"), + } + } +} +#[derive(Debug)] +pub struct ReferenceHooksSectionParseError; +impl std::fmt::Display for ReferenceHooksSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceHooksSectionhooks-and-component-state, use-signal-hook, rules-of-hooks, no-hooks-in-conditionals, no-hooks-in-closures, no-hooks-in-loops, additional-resources", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceHooksSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceHooks(section: ReferenceHooksSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "hooks-and-component-state", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::HooksAndComponentState, + }, + class: "header", + "Hooks and component state" + } + } + p { + "So far, our components have had no state like a normal Rust function. 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 opened a drop-down and render different things accordingly." + } + p { + "Hooks allow us to create state in our components. Hooks are Rust functions you call in a constant order in a component that add additional functionality to the component." + } + p { + "Dioxus provides many built-in hooks, but if those hooks don't fit your specific use case, you also can " + Link { + to: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::Empty, + }, + "create your own hook" + } + } + h2 { id: "use-signal-hook", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::UseSignalHook, + }, + class: "header", + "use_signal hook" + } + } + p { + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_signal.html", + code { "use_signal" } + } + " is one of the simplest hooks." + } + ul { + li { + "You provide a closure that determines the initial value: " + code { "let mut count = use_signal(|| 0);" } + } + li { + code { "use_signal" } + " gives you the current value, and a way to write to the value" + } + li { + "When the value updates, " + code { "use_signal" } + " makes the component re-render (along with any other component that references it), and then provides you with the new value." + } + } + p { + "For example, you might have seen the counter example, in which state (a number) is tracked using the " + code { "use_signal" } + " hook:" + } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    // count will be initialized to 0 the first time the component is rendered\n    let mut count = use_signal(|| 0);\n\n    rsx! {{\n        h1 {{ "High-Five counter: {{count}}" }}\n        button {{ onclick: move |_| count += 1, "Up high!" }}\n        button {{ onclick: move |_| count -= 1, "Down low!" }}\n    }}\n}}
\n", + name: "hooks_counter.rs".to_string(), + } + DemoFrame { hooks_counter::App {} } + p { + "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 – describe what you want in terms of the state, and Dioxus will take care of the rest!" + } + blockquote { + p { + code { "use_signal" } + " returns your value wrapped in a smart pointer of type " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/struct.Signal.html", + code { "Signal" } + } + " that is " + code { "Copy" } + ". This is why you can both read the value and update it, even within an event handler." + } + } + p { "You can use multiple hooks in the same component if you want:" } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    let mut count_a = use_signal(|| 0);\n    let mut count_b = use_signal(|| 0);\n\n    rsx! {{\n        h1 {{ "Counter_a: {{count_a}}" }}\n        button {{ onclick: move |_| count_a += 1, "a++" }}\n        button {{ onclick: move |_| count_a -= 1, "a--" }}\n        h1 {{ "Counter_b: {{count_b}}" }}\n        button {{ onclick: move |_| count_b += 1, "b++" }}\n        button {{ onclick: move |_| count_b -= 1, "b--" }}\n    }}\n}}
\n", + name: "hooks_counter_two_state.rs".to_string(), + } + DemoFrame { hooks_counter_two_state::App {} } + p { + "You can also use " + code { "use_signal" } + " to store more complex state, like a Vec. You can read and write to the state with the " + code { "read" } + " and " + code { "write" } + " methods:" + } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    let mut list = use_signal(Vec::new);\n\n    rsx! {{\n        p {{ "Current list: {{list:?}}" }}\n        button {{\n            onclick: move |event| {{\n                let list_len = list.len();\n                list.push(list_len);\n                list.push(list_len);\n            }},\n            "Add two elements!"\n        }}\n    }}\n}}
\n", + name: "hooks_use_signal.rs".to_string(), + } + DemoFrame { hooks_use_signal::App {} } + h2 { id: "rules-of-hooks", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::RulesOfHooks, + }, + class: "header", + "Rules of hooks" + } + } + p { + "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 hidden scope that is associated with the component." + } + p { + "But how can Dioxus differentiate between multiple hooks in the same component? As you saw in the second example, both " + code { "use_signal" } + " functions were called with the same parameters, so how come they can return different things when the counters are different?" + } + CodeBlock { + contents: "
\nlet mut count_a = use_signal(|| 0);\nlet mut count_b = use_signal(|| 0);
\n", + name: "hooks_counter_two_state.rs".to_string(), + } + p { + "This is only possible because the two hooks are always called in the same order, so Dioxus knows which is which. Because the order you call hooks matters, you must follow certain rules when using hooks:" + } + ol { + li { "Hooks may be only used in components or other hooks (we'll get to that later)." } + li { "On every call to a component function." } + li { + "The same hooks must be called (except in the case of early returns, as explained later in the " + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::Empty, + }, + "Error Handling chapter" + } + ")." + } + li { "In the same order." } + li { + "Hook names should start with " + code { "use_" } + " so you don't accidentally confuse them with regular" + " " + "functions (" + code { "use_signal()" } + ", " + code { "use_effect()" } + ", " + code { "use_resource()" } + ", etc...)." + } + } + p { "These rules mean that there are certain things you can't do with hooks:" } + h3 { id: "no-hooks-in-conditionals", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::NoHooksInConditionals, + }, + class: "header", + "No hooks in conditionals" + } + } + CodeBlock { + contents: "
\n// ❌ don't call hooks in conditionals!\n// We must ensure that the same hooks will be called every time\n// But `if` statements only run if the conditional is true!\n// So we might violate rule 2.\nif you_are_happy && you_know_it {{\n    let something = use_signal(|| "hands");\n    println!("clap your {{something}}")\n}}\n\n// ✅ instead, *always* call use_signal\n// You can put other stuff in the conditional though\nlet something = use_signal(|| "hands");\nif you_are_happy && you_know_it {{\n    println!("clap your {{something}}")\n}}
\n", + name: "hooks_bad.rs".to_string(), + } + h3 { id: "no-hooks-in-closures", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::NoHooksInClosures, + }, + class: "header", + "No hooks in closures" + } + } + CodeBlock { + contents: "
\n// ❌ don't call hooks inside closures!\n// We can't guarantee that the closure, if used, will be called in the same order every time\nlet _a = || {{\n    let b = use_signal(|| 0);\n    b()\n}};\n\n// ✅ instead, move hook `b` outside\nlet b = use_signal(|| 0);\nlet _a = || b();
\n", + name: "hooks_bad.rs".to_string(), + } + h3 { id: "no-hooks-in-loops", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::NoHooksInLoops, + }, + class: "header", + "No hooks in loops" + } + } + CodeBlock { + contents: "
\n// `names` is a Vec<&str>\n\n// ❌ Do not use hooks in loops!\n// In this case, if the length of the Vec changes, we break rule 2\nfor _name in &names {{\n    let is_selected = use_signal(|| false);\n    println!("selected: {{is_selected}}");\n}}\n\n// ✅ Instead, use a hashmap with use_signal\nlet selection_map = use_signal(HashMap::<&str, bool>::new);\n\nfor name in &names {{\n    let is_selected = selection_map.read()[name];\n    println!("selected: {{is_selected}}");\n}}
\n", + name: "hooks_bad.rs".to_string(), + } + h2 { id: "additional-resources", + Link { + to: BookRoute::ReferenceHooks { + section: ReferenceHooksSection::AdditionalResources, + }, + class: "header", + "Additional resources" + } + } + ul { + li { + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/", "dioxus_hooks API docs" } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/packages/hooks", + "dioxus_hooks source code" + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceUserInputSection { + #[default] + Empty, + UserInput, + ControlledInputs, + UncontrolledInputs, + HandlingFiles, +} +impl std::str::FromStr for ReferenceUserInputSection { + type Err = ReferenceUserInputSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "user-input" => Ok(Self::UserInput), + "controlled-inputs" => Ok(Self::ControlledInputs), + "uncontrolled-inputs" => Ok(Self::UncontrolledInputs), + "handling-files" => Ok(Self::HandlingFiles), + _ => Err(ReferenceUserInputSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceUserInputSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::UserInput => f.write_str("user-input"), + Self::ControlledInputs => f.write_str("controlled-inputs"), + Self::UncontrolledInputs => f.write_str("uncontrolled-inputs"), + Self::HandlingFiles => f.write_str("handling-files"), + } + } +} +#[derive(Debug)] +pub struct ReferenceUserInputSectionParseError; +impl std::fmt::Display for ReferenceUserInputSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceUserInputSectionuser-input, controlled-inputs, uncontrolled-inputs, handling-files", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceUserInputSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceUserInput(section: ReferenceUserInputSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "user-input", + Link { + to: BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::UserInput, + }, + class: "header", + "User Input" + } + } + p { + "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." + } + h2 { id: "controlled-inputs", + Link { + to: BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::ControlledInputs, + }, + class: "header", + "Controlled Inputs" + } + } + p { + "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:" + } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    let mut name = use_signal(|| "bob".to_string());\n\n    rsx! {{\n        input {{\n            // we tell the component what to render\n            value: "{{name}}",\n            // and what to do when the value changes\n            oninput: move |event| name.set(event.value())\n        }}\n    }}\n}}
\n", + name: "input_controlled.rs".to_string(), + } + DemoFrame { input_controlled::App {} } + p { "Notice the flexibility – you can:" } + ul { + li { "Also display the same contents in another element, and they will be in sync" } + li { "Transform the input every time it is modified (e.g. to make sure it is upper case)" } + li { "Validate the input every time it changes" } + li { + "Have custom logic happening when the input changes (e.g. network request for autocompletion)" + } + li { + "Programmatically change the value (e.g. a \"randomize\" button that fills the input with nonsense)" + } + } + h2 { id: "uncontrolled-inputs", + Link { + to: BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::UncontrolledInputs, + }, + class: "header", + "Uncontrolled Inputs" + } + } + p { + "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 will be editable anyway (this is built into the browser). This approach can be more performant, but less flexible. For example, it's harder to keep the input in sync with another element." + } + p { + "Since you don't necessarily have the current value of the uncontrolled input in state, you can access it either by listening to " + code { "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. " + code { "oninput" } + " or " + code { "onsubmit" } + "):" + } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    rsx! {{\n        form {{ onsubmit: move |event| {{ log::info!("Submitted! {{event:?}}") }},\n            input {{ name: "name" }}\n            input {{ name: "age" }}\n            input {{ name: "date" }}\n            input {{ r#type: "submit" }}\n        }}\n    }}\n}}
\n", + name: "input_uncontrolled.rs".to_string(), + } + DemoFrame { input_uncontrolled::App {} } + CodeBlock { contents: "
\nSubmitted! UiEvent {{ data: FormData {{ value: "", values: {{"age": "very old", "date": "1966", "name": "Fred"}} }} }}
\n" } + h2 { id: "handling-files", + Link { + to: BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::HandlingFiles, + }, + class: "header", + "Handling files" + } + } + p { + "You can insert a file picker by using an input element of type " + code { "file" } + ". This element supports the " + code { "multiple" } + " attribute, to let you pick more files at the same time. You can select a folder by adding the " + code { "directory" } + " attribute: Dioxus will map this attribute to browser specific attributes, because there is no standardized way to allow a directory to be selected." + } + p { + code { "type" } + " is a Rust keyword, so when specifying the type of the input field, you have to write it as " + code { "r#type:\"file\"" } + "." + } + p { + "Extracting the selected files is a bit different from what you may typically use in Javascript." + } + p { + "The " + code { "FormData" } + " event contains a " + code { "files" } + " field with data about the uploaded files. This field contains a " + code { "FileEngine" } + " struct which lets you fetch the filenames selected by the user. This example saves the filenames of the selected files to a " + code { "Vec" } + ":" + } + CodeBlock { + contents: "
\npub fn App() -> Element {{\n    let mut filenames: Signal<Vec<String>> = use_signal(Vec::new);\n    rsx! {{\n        input {{\n            // tell the input to pick a file\n            r#type: "file",\n            // list the accepted extensions\n            accept: ".txt,.rs",\n            // pick multiple files\n            multiple: true,\n            onchange: move |evt| {{\n                if let Some(file_engine) = &evt.files() {{\n                    let files = file_engine.files();\n                    for file_name in files {{\n                        filenames.write().push(file_name);\n                    }}\n                }}\n            }}\n        }}\n    }}\n}}
\n", + name: "input_fileengine.rs".to_string(), + } + p { + "If you're planning to read the file content, you need to do it asynchronously, to keep the rest of the UI interactive. This example event handler loads the content of the selected files in an async closure:" + } + CodeBlock { + contents: "
\nonchange: move |evt| {{\n    async move {{\n        if let Some(file_engine) = evt.files() {{\n            let files = file_engine.files();\n            for file_name in &files {{\n                if let Some(file) = file_engine.read_file_to_string(file_name).await\n                {{\n                    files_uploaded.write().push(file);\n                }}\n            }}\n        }}\n    }}\n}}
\n", + name: "input_fileengine_async.rs".to_string(), + } + p { + "Lastly, this example shows you how to select a folder, by setting the " + code { "directory" } + " attribute to " + code { "true" } + "." + } + CodeBlock { + contents: "
\ninput {{\n    r#type: "file",\n    // Select a folder by setting the directory attribute\n    directory: true,\n    onchange: move |evt| {{\n        if let Some(file_engine) = evt.files() {{\n            let files = file_engine.files();\n            for file_name in files {{\n                println!("{{}}", file_name);\n            }}\n        }}\n    }}\n}}
\n", + name: "input_fileengine_folder.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceContextSection { + #[default] + Empty, + SharingState, + LiftingState, + UsingSharedState, +} +impl std::str::FromStr for ReferenceContextSection { + type Err = ReferenceContextSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "sharing-state" => Ok(Self::SharingState), + "lifting-state" => Ok(Self::LiftingState), + "using-shared-state" => Ok(Self::UsingSharedState), + _ => Err(ReferenceContextSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceContextSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::SharingState => f.write_str("sharing-state"), + Self::LiftingState => f.write_str("lifting-state"), + Self::UsingSharedState => f.write_str("using-shared-state"), + } + } +} +#[derive(Debug)] +pub struct ReferenceContextSectionParseError; +impl std::fmt::Display for ReferenceContextSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceContextSectionsharing-state, lifting-state, using-shared-state", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceContextSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceContext(section: ReferenceContextSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "sharing-state", + Link { + to: BookRoute::ReferenceContext { + section: ReferenceContextSection::SharingState, + }, + class: "header", + "Sharing State" + } + } + p { + "Often, multiple components need to access the same state. Depending on your needs, there are several ways to implement this." + } + h2 { id: "lifting-state", + Link { + to: BookRoute::ReferenceContext { + section: ReferenceContextSection::LiftingState, + }, + class: "header", + "Lifting State" + } + } + p { + "One approach to share state between components is to \"lift\" it up to the nearest common ancestor. This means putting the " + code { "use_signal" } + " hook in a parent component, and passing the needed values down as props." + } + p { + "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)." + } + blockquote { + p { + "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, maintainable, and performant (this is even more important for larger, complex apps)." + } + } + p { + "We start with a " + code { "Meme" } + " component, responsible for rendering a meme with a given caption:" + } + CodeBlock { + contents: "
\n#[component]\nfn Meme(caption: String) -> Element {{\n    let container_style = r#"\n        position: relative;\n        width: fit-content;\n    "#;\n\n    let caption_container_style = r#"\n        position: absolute;\n        bottom: 0;\n        left: 0;\n        right: 0;\n        padding: 16px 8px;\n    "#;\n\n    let caption_style = r"\n        font-size: 32px;\n        margin: 0;\n        color: white;\n        text-align: center;\n    ";\n\n    rsx! {{\n        div {{ style: "{{container_style}}",\n            img {{ src: "https://i.imgflip.com/2zh47r.jpg", height: "500px" }}\n            div {{ style: "{{caption_container_style}}", p {{ style: "{{caption_style}}", "{{caption}}" }} }}\n        }}\n    }}\n}}
\n", + name: "meme_editor.rs".to_string(), + } + blockquote { + p { + "Note that the " + code { "Meme" } + " component is unaware where the caption is coming from – it could be stored in " + code { "use_signal" } + ", or a constant. This ensures that it is very reusable – the same component can be used for a meme gallery without any changes!" + } + } + p { + "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 " + code { "Meme" } + " component? Instead, it should accept the current caption as a prop, as well as an event handler to delegate input events to:" + } + CodeBlock { + contents: "
\n#[component]\nfn CaptionEditor(caption: String, oninput: EventHandler<FormEvent>) -> Element {{\n    let input_style = r"\n        border: none;\n        background: cornflowerblue;\n        padding: 8px 16px;\n        margin: 0;\n        border-radius: 4px;\n        color: white;\n    ";\n\n    rsx! {{\n        input {{\n            style: "{{input_style}}",\n            value: "{{caption}}",\n            oninput: move |event| oninput.call(event)\n        }}\n    }}\n}}
\n", + name: "meme_editor.rs".to_string(), + } + p { + "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." + } + CodeBlock { + contents: "
\nfn MemeEditor() -> Element {{\n    let container_style = r"\n        display: flex;\n        flex-direction: column;\n        gap: 16px;\n        margin: 0 auto;\n        width: fit-content;\n    ";\n\n    let mut caption = use_signal(|| "me waiting for my rust code to compile".to_string());\n\n    rsx! {{\n        div {{ style: "{{container_style}}",\n            h1 {{ "Meme Editor" }}\n            Meme {{ caption: caption }}\n            CaptionEditor {{ caption: caption, oninput: move |event: FormEvent| caption.set(event.value()) }}\n        }}\n    }}\n}}
\n", + name: "meme_editor.rs".to_string(), + } + p { + img { + src: asset!( + "/assets/static/meme_editor_screenshot.png", ImageAssetOptions::new().with_webp() + ), + alt: "Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: \"me waiting for a language feature\"", + title: "", + } + } + h2 { id: "using-shared-state", + Link { + to: BookRoute::ReferenceContext { + section: ReferenceContextSection::UsingSharedState, + }, + class: "header", + "Using Shared State" + } + } + p { + "Sometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient." + } + p { + "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." + } + blockquote { + p { + "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!" + } + } + p { + "Now, we could write another " + code { "use_signal" } + " in the top component, and pass " + code { "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!" + } + p { + "Dioxus offers a better solution than this \"prop drilling\" – providing context. The " + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html", + code { "use_context_provider" } + } + " hook provides any Clone context (including Signals!) to any child components. Child components can use the " + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html", + code { "use_context" } + } + " hook to get that context and if it is a Signal, they can read and write to it." + } + p { "First, we have to create a struct for our dark mode configuration:" } + CodeBlock { + contents: "
\n#[derive(Clone, Copy)]\nstruct DarkMode(bool);
\n", + name: "meme_editor_dark_mode.rs".to_string(), + } + p { + "Now, in a top-level component (like " + code { "App" } + "), we can provide the " + code { "DarkMode" } + " context to all children components:" + } + CodeBlock { + contents: "
\nuse_context_provider(|| Signal::new(DarkMode(false)));
\n", + name: "meme_editor_dark_mode.rs".to_string(), + } + p { + "As a result, any child component of " + code { "App" } + " (direct or not), can access the " + code { "DarkMode" } + " context." + } + CodeBlock { + contents: "
\nlet dark_mode_context = use_context::<Signal<DarkMode>>();
\n", + name: "meme_editor_dark_mode.rs".to_string(), + } + blockquote { + p { + code { "use_context" } + " returns " + code { "Signal" } + " here, because the Signal was provided by the parent. If the context hadn't been provided " + code { "use_context" } + " would have panicked." + } + } + p { + "If you have a component where the context might or not be provided, you might want to use " + code { "try_consume_context" } + "instead, so you can handle the " + code { "None" } + " case. The drawback of this method is that it will not memoize the value between renders, so it won't be as as efficient as " + code { "use_context" } + ", you could do it yourself with " + code { "use_hook" } + " though." + } + p { + "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):" + } + CodeBlock { + contents: "
\npub fn DarkModeToggle() -> Element {{\n    let mut dark_mode = use_context::<Signal<DarkMode>>();\n\n    let style = if dark_mode().0 {{ "color:white" }} else {{ "" }};\n\n    rsx! {{\n        label {{ style: "{{style}}",\n            "Dark Mode"\n            input {{\n                r#type: "checkbox",\n                oninput: move |event| {{\n                    let is_enabled = event.value() == "true";\n                    dark_mode.write().0 = is_enabled;\n                }}\n            }}\n        }}\n    }}\n}}
\n", + name: "meme_editor_dark_mode.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceDynamicRenderingSection { + #[default] + Empty, + DynamicRendering, + ConditionalRendering, + ImprovingTheIfElseExample, + InspectingElementProps, + RenderingNothing, + RenderingLists, + InlineForLoops, + TheKeyAttribute, +} +impl std::str::FromStr for ReferenceDynamicRenderingSection { + type Err = ReferenceDynamicRenderingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "dynamic-rendering" => Ok(Self::DynamicRendering), + "conditional-rendering" => Ok(Self::ConditionalRendering), + "improving-the-if-else-example" => Ok(Self::ImprovingTheIfElseExample), + "inspecting-element-props" => Ok(Self::InspectingElementProps), + "rendering-nothing" => Ok(Self::RenderingNothing), + "rendering-lists" => Ok(Self::RenderingLists), + "inline-for-loops" => Ok(Self::InlineForLoops), + "the-key-attribute" => Ok(Self::TheKeyAttribute), + _ => Err(ReferenceDynamicRenderingSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceDynamicRenderingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::DynamicRendering => f.write_str("dynamic-rendering"), + Self::ConditionalRendering => f.write_str("conditional-rendering"), + Self::ImprovingTheIfElseExample => f.write_str("improving-the-if-else-example"), + Self::InspectingElementProps => f.write_str("inspecting-element-props"), + Self::RenderingNothing => f.write_str("rendering-nothing"), + Self::RenderingLists => f.write_str("rendering-lists"), + Self::InlineForLoops => f.write_str("inline-for-loops"), + Self::TheKeyAttribute => f.write_str("the-key-attribute"), + } + } +} +#[derive(Debug)] +pub struct ReferenceDynamicRenderingSectionParseError; +impl std::fmt::Display for ReferenceDynamicRenderingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceDynamicRenderingSectiondynamic-rendering, conditional-rendering, improving-the-if-else-example, inspecting-element-props, rendering-nothing, rendering-lists, inline-for-loops, the-key-attribute", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceDynamicRenderingSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceDynamicRendering( + section: ReferenceDynamicRenderingSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "dynamic-rendering", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::DynamicRendering, + }, + class: "header", + "Dynamic Rendering" + } + } + p { + "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!" + } + h2 { id: "conditional-rendering", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::ConditionalRendering, + }, + class: "header", + "Conditional Rendering" + } + } + p { + "To render different elements based on a condition, you could use an " + code { "if-else" } + " statement:" + } + CodeBlock { + contents: "
\nif is_logged_in {{\n    rsx! {{\n        "Welcome!"\n        button {{ onclick: move |_| log_out.call(()), "Log Out" }}\n    }}\n}} else {{\n    rsx! {{\n        button {{ onclick: move |_| log_in.call(()), "Log In" }}\n    }}\n}}
\n", + name: "conditional_rendering.rs".to_string(), + } + DemoFrame { conditional_rendering::App {} } + blockquote { + p { + "You could also use " + code { "match" } + " statements, or any Rust function to conditionally render different things." + } + } + h3 { id: "improving-the-if-else-example", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::ImprovingTheIfElseExample, + }, + class: "header", + "Improving the if-else Example" + } + } + p { + "You may have noticed some repeated code in the " + code { "if-else" } + " example above. Repeating code like this is both bad for maintainability and performance. Dioxus will skip diffing static elements like the button, but when switching between multiple " + code { "rsx" } + " calls it cannot perform this optimization. For this example either approach is fine, but for components with large parts that are reused between conditionals, it can be more of an issue." + } + p { + "We can improve this example by splitting up the dynamic parts and inserting them where they are needed." + } + CodeBlock { + contents: "
\nrsx! {{\n    // We only render the welcome message if we are logged in\n    // You can use if statements in the middle of a render block to conditionally render elements\n    if is_logged_in {{\n        // Notice the body of this if statement is rsx code, not an expression\n        "Welcome!"\n    }}\n    button {{\n        // depending on the value of `is_logged_in`, we will call a different event handler\n        onclick: move |_| if is_logged_in {{ log_out.call(()) }} else {{ log_in.call(()) }},\n        if is_logged_in {{\n            // if we are logged in, the button should say "Log Out"\n            "Log Out"\n        }} else {{\n            // if we are not logged in, the button should say "Log In"\n            "Log In"\n        }}\n    }}\n}}
\n", + name: "conditional_rendering.rs".to_string(), + } + DemoFrame { conditional_rendering::LogInImprovedApp {} } + h3 { id: "inspecting-element-props", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::InspectingElementProps, + }, + class: "header", + "Inspecting Element props" + } + } + p { + "Since " + code { "Element" } + " is a " + code { "Option" } + ", components accepting " + code { "Element" } + " as a prop can inspect its contents, and render different things based on that. Example:" + } + CodeBlock { + contents: "
\nfn Clickable(props: ClickableProps) -> Element {{\n    match props.children {{\n        Some(VNode {{ .. }}) => {{\n            todo!("render some stuff")\n        }}\n        _ => {{\n            todo!("render some other stuff")\n        }}\n    }}\n}}
\n", + name: "component_children_inspect.rs".to_string(), + } + p { + "You can't mutate the " + code { "Element" } + ", but if you need a modified version of it, you can construct a new one based on its attributes/children/etc." + } + h2 { id: "rendering-nothing", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::RenderingNothing, + }, + class: "header", + "Rendering Nothing" + } + } + p { + "To render nothing, you can return " + code { "None" } + " from a component. This is useful if you want to conditionally hide something:" + } + CodeBlock { + contents: "
\nif is_logged_in {{\n    return rsx!();\n}}\n\nrsx! {{\n    p {{ "You must be logged in to comment" }}\n}}
\n", + name: "conditional_rendering.rs".to_string(), + } + DemoFrame { conditional_rendering::LogInWarningApp {} } + p { + "This works because the " + code { "Element" } + " type is just an alias for " + code { "Option" } + } + blockquote { + p { + "Again, you may use a different method to conditionally return " + code { "None" } + ". For example the boolean's " + Link { to: "https://doc.rust-lang.org/std/primitive.bool.html#method.then", + code { "then()" } + } + " function could be used." + } + } + h2 { id: "rendering-lists", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::RenderingLists, + }, + class: "header", + "Rendering Lists" + } + } + p { + "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." + } + p { + "For this, Dioxus accepts iterators that produce " + code { "Element" } + "s. So we need to:" + } + ul { + li { + "Get an iterator over all of our items (e.g., if you have a " + code { "Vec" } + " of comments, iterate over it with " + code { "iter()" } + ")" + } + li { + code { ".map" } + " the iterator to convert each item into a " + code { "LazyNode" } + " using " + code { "rsx!{{...}}" } + ul { + li { + "Add a unique " + code { "key" } + " attribute to each iterator item" + } + } + } + li { "Include this iterator in the final RSX (or use it inline)" } + } + p { + "Example: suppose you have a list of comments you want to render. Then, you can render them like this:" + } + CodeBlock { + contents: "
\nlet mut comment_field = use_signal(String::new);\nlet mut next_id = use_signal(|| 0);\nlet mut comments = use_signal(Vec::<CommentData>::new);\n\nlet comments_lock = comments.read();\nlet comments_rendered = comments_lock.iter().map(|comment| {{\n    rsx! {{ Comment {{ comment: comment.clone() }} }}\n}});\n\nrsx! {{\n    form {{\n        onsubmit: move |_| {{\n            comments\n                .write()\n                .push(CommentData {{\n                    content: comment_field(),\n                    id: next_id(),\n                }});\n            next_id += 1;\n            comment_field.set(String::new());\n        }},\n        input {{\n            value: "{{comment_field}}",\n            oninput: move |event| comment_field.set(event.value())\n        }}\n        input {{ r#type: "submit" }}\n    }}\n    {{comments_rendered}}\n}}
\n", + name: "rendering_lists.rs".to_string(), + } + DemoFrame { rendering_lists::App {} } + h3 { id: "inline-for-loops", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::InlineForLoops, + }, + class: "header", + "Inline for loops" + } + } + p { + "Because of how common it is to render a list of items, Dioxus provides a shorthand for this. Instead of using " + code { ".iter" } + ", " + code { ".map" } + ", and " + code { "rsx" } + ", you can use a " + code { "for" } + " loop with a body of rsx code:" + } + CodeBlock { + contents: "
\nlet mut comment_field = use_signal(String::new);\nlet mut next_id = use_signal(|| 0);\nlet mut comments = use_signal(Vec::<CommentData>::new);\n\nrsx! {{\n    form {{\n        onsubmit: move |_| {{\n            comments\n                .write()\n                .push(CommentData {{\n                    content: comment_field(),\n                    id: next_id(),\n                }});\n            next_id += 1;\n            comment_field.set(String::new());\n        }},\n        input {{\n            value: "{{comment_field}}",\n            oninput: move |event| comment_field.set(event.value())\n        }}\n        input {{ r#type: "submit" }}\n    }}\n    for comment in comments() {{\n        // Notice the body of this for loop is rsx code, not an expression\n        Comment {{ comment }}\n    }}\n}}
\n", + name: "rendering_lists.rs".to_string(), + } + DemoFrame { rendering_lists::AppForLoop {} } + h3 { id: "the-key-attribute", + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::TheKeyAttribute, + }, + class: "header", + "The key Attribute" + } + } + p { + "Every time you re-render your list, Dioxus needs to keep track of which items go where to determine what updates need to be made to the UI." + } + p { + "For example, suppose the " + code { "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!" + } + p { + "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 matter where you get the key from, as long as it meets the requirements:" + } + ul { + li { "Keys must be unique in a list" } + li { "The same item should always get associated with the same key" } + li { + "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" + } + } + p { + "You might be tempted to use an item's index in the list as its key. 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." + } + blockquote { + p { + "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." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceRouterSection { + #[default] + Empty, + Router, + WhatIsIt, + UsingTheRouter, + Links, + MoreReading, +} +impl std::str::FromStr for ReferenceRouterSection { + type Err = ReferenceRouterSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "router" => Ok(Self::Router), + "what-is-it" => Ok(Self::WhatIsIt), + "using-the-router" => Ok(Self::UsingTheRouter), + "links" => Ok(Self::Links), + "more-reading" => Ok(Self::MoreReading), + _ => Err(ReferenceRouterSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceRouterSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Router => f.write_str("router"), + Self::WhatIsIt => f.write_str("what-is-it"), + Self::UsingTheRouter => f.write_str("using-the-router"), + Self::Links => f.write_str("links"), + Self::MoreReading => f.write_str("more-reading"), + } + } +} +#[derive(Debug)] +pub struct ReferenceRouterSectionParseError; +impl std::fmt::Display for ReferenceRouterSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceRouterSectionrouter, what-is-it, using-the-router, links, more-reading", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceRouterSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceRouter(section: ReferenceRouterSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "router", + Link { + to: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::Router, + }, + class: "header", + "Router" + } + } + p { + "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. For a desktop app, these scenes might be different views in your app." + } + p { + "To unify these platforms, Dioxus provides a first-party solution for scene management called Dioxus Router." + } + h2 { id: "what-is-it", + Link { + to: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::WhatIsIt, + }, + class: "header", + "What is it?" + } + } + p { + "For an app like the Dioxus landing page (https://dioxuslabs.com), we want to have several different scenes:" + } + ul { + li { "Homepage" } + li { "Blog" } + } + p { + "Each of these scenes is independent – we don't want to render both the homepage and blog at the same time." + } + p { + "The Dioxus router makes it easy to create these scenes. To make sure we're using the router, add the " + code { "router" } + " feature to your " + code { "dioxus" } + " dependency:" + } + CodeBlock { contents: "
\ncargo add dioxus@0.5.0 --features router
\n" } + h2 { id: "using-the-router", + Link { + to: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::UsingTheRouter, + }, + class: "header", + "Using the router" + } + } + p { + "Unlike other routers in the Rust ecosystem, our router is built declaratively at compile time. This makes it possible to compose our app layout simply by defining an enum." + } + CodeBlock { + contents: "
\n// All of our routes will be a variant of this Route enum\n#[derive(Routable, PartialEq, Clone)]\nenum Route {{\n    // if the current location is "/home", render the Home component\n    #[route("/home")]\n    Home {{}},\n    // if the current location is "/blog", render the Blog component\n    #[route("/blog")]\n    Blog {{}},\n}}\n\nfn Home() -> Element {{\n    todo!()\n}}\n\nfn Blog() -> Element {{\n    todo!()\n}}
\n", + name: "router_reference.rs".to_string(), + } + p { + "Whenever we visit this app, we will get either the Home component or the Blog component rendered depending on which route we enter at. If neither of these routes match the current location, then nothing will render." + } + p { "We can fix this one of two ways:" } + ul { + li { "A fallback 404 page" } + } + CodeBlock { + contents: "
\n// All of our routes will be a variant of this Route enum\n#[derive(Routable, PartialEq, Clone)]\nenum Route {{\n    #[route("/home")]\n    Home {{}},\n    #[route("/blog")]\n    Blog {{}},\n    //  if the current location doesn't match any of the above routes, render the NotFound component\n    #[route("/:..segments")]\n    NotFound {{ segments: Vec<String> }},\n}}\n\nfn Home() -> Element {{\n    todo!()\n}}\n\nfn Blog() -> Element {{\n    todo!()\n}}\n\n#[component]\nfn NotFound(segments: Vec<String>) -> Element {{\n    todo!()\n}}
\n", + name: "router_reference.rs".to_string(), + } + ul { + li { "Redirect 404 to home" } + } + CodeBlock { + contents: "
\n// All of our routes will be a variant of this Route enum\n#[derive(Routable, PartialEq, Clone)]\nenum Route {{\n    #[route("/home")]\n    //  if the current location doesn't match any of the other routes, redirect to "/home"\n    #[redirect("/:..segments", |segments: Vec<String>| Route::Home {{}})]\n    Home {{}},\n    #[route("/blog")]\n    Blog {{}},\n}}
\n", + name: "router_reference.rs".to_string(), + } + h2 { id: "links", + Link { + to: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::Links, + }, + class: "header", + "Links" + } + } + p { + "For our app to navigate these routes, we can provide clickable elements called Links. These simply wrap " + code { "
" } + " elements that, when clicked, navigate the app to the given location. Because our route is an enum of valid routes, if you try to link to a page that doesn't exist, you will get a compiler error." + } + CodeBlock { + contents: "
\nrsx! {{\n    Link {{ to: Route::Home {{}}, "Go home!" }}\n}}
\n", + name: "router_reference.rs".to_string(), + } + h2 { id: "more-reading", + Link { + to: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::MoreReading, + }, + class: "header", + "More reading" + } + } + p { + "This page is just a very brief overview of the router. For more information, check out the " + Link { + to: BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }, + "router book" + } + " or some of the " + Link { to: "https://github.com/DioxusLabs/dioxus/blob/master/examples/router.rs", + "router examples" + } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceUseResourceSection { + #[default] + Empty, + Resource, + RestartingTheFuture, + Dependencies, +} +impl std::str::FromStr for ReferenceUseResourceSection { + type Err = ReferenceUseResourceSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "resource" => Ok(Self::Resource), + "restarting-the-future" => Ok(Self::RestartingTheFuture), + "dependencies" => Ok(Self::Dependencies), + _ => Err(ReferenceUseResourceSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceUseResourceSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Resource => f.write_str("resource"), + Self::RestartingTheFuture => f.write_str("restarting-the-future"), + Self::Dependencies => f.write_str("dependencies"), + } + } +} +#[derive(Debug)] +pub struct ReferenceUseResourceSectionParseError; +impl std::fmt::Display for ReferenceUseResourceSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceUseResourceSectionresource, restarting-the-future, dependencies", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceUseResourceSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceUseResource(section: ReferenceUseResourceSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "resource", + Link { + to: BookRoute::ReferenceUseResource { + section: ReferenceUseResourceSection::Resource, + }, + class: "header", + "Resource" + } + } + p { + Link { to: "https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_resource.html", + code { "use_resource" } + } + " lets you run an async closure, and provides you with its result." + } + p { + "For example, we can make an API request (using " + Link { to: "https://docs.rs/reqwest/latest/reqwest/index.html", "reqwest" } + ") inside " + code { "use_resource" } + ":" + } + CodeBlock { + contents: "
\nlet mut future = use_resource(|| async move {{\n    reqwest::get("https://dog.ceo/api/breeds/image/random")\n        .await\n        .unwrap()\n        .json::<ApiResponse>()\n        .await\n}});
\n", + name: "use_resource.rs".to_string(), + } + p { + "The code inside " + code { "use_resource" } + " will be submitted to the Dioxus scheduler once the component has rendered." + } + p { + "We can use " + code { ".read()" } + " 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 " + code { "None" } + ". However, once the future is finished, the component will be re-rendered and the value will now be " + code { "Some(...)" } + ", containing the return value of the closure." + } + p { "We can then render that result:" } + CodeBlock { + contents: "
\nmatch &*future.read_unchecked() {{\n    Some(Ok(response)) => rsx! {{\n        button {{ onclick: move |_| future.restart(), "Click to fetch another doggo" }}\n        div {{\n            img {{\n                max_width: "500px",\n                max_height: "500px",\n                src: "{{response.image_url}}",\n            }}\n        }}\n    }},\n    Some(Err(_)) => rsx! {{\n        div {{ "Loading dogs failed" }}\n    }},\n    None => rsx! {{\n        div {{ "Loading dogs..." }}\n    }},\n}}
\n", + name: "use_resource.rs".to_string(), + } + DemoFrame { use_resource::App {} } + h2 { id: "restarting-the-future", + Link { + to: BookRoute::ReferenceUseResource { + section: ReferenceUseResourceSection::RestartingTheFuture, + }, + class: "header", + "Restarting the Future" + } + } + p { + "The " + code { "Resource" } + " handle provides a " + code { "restart" } + " method. It can be used to execute the future again, producing a new value." + } + h2 { id: "dependencies", + Link { + to: BookRoute::ReferenceUseResource { + section: ReferenceUseResourceSection::Dependencies, + }, + class: "header", + "Dependencies" + } + } + p { + "Often, you will need to run the future again every time some value (e.g. a state) changes. Rather than calling " + code { "restart" } + " manually, you can read a signal inside of the future. It will automatically re-run the future when any of the states you read inside the future change. Example:" + } + CodeBlock { + contents: "
\nlet future = use_resource(move || async move {{\n    reqwest::get(format!("https://dog.ceo/api/breed/{{breed}}/images/random"))\n        .await\n        .unwrap()\n        .json::<ApiResponse>()\n        .await\n}});\n\n// You can also add non-reactive state to the resource hook with the use_reactive method\nlet non_reactive_state = "poodle";\nuse_resource(use_reactive!(|(non_reactive_state,)| async move {{\n    reqwest::get(format!(\n        "https://dog.ceo/api/breed/{{non_reactive_state}}/images/random"\n    ))\n    .await\n    .unwrap()\n    .json::<ApiResponse>()\n    .await\n}}));
\n", + name: "use_resource.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceUseCoroutineSection { + #[default] + Empty, + Coroutines, + UseCoroutine, + YieldingValues, + SendingValues, + AutomaticInjectionIntoTheContextApi, +} +impl std::str::FromStr for ReferenceUseCoroutineSection { + type Err = ReferenceUseCoroutineSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "coroutines" => Ok(Self::Coroutines), + "use-coroutine" => Ok(Self::UseCoroutine), + "yielding-values" => Ok(Self::YieldingValues), + "sending-values" => Ok(Self::SendingValues), + "automatic-injection-into-the-context-api" => { + Ok(Self::AutomaticInjectionIntoTheContextApi) + } + _ => Err(ReferenceUseCoroutineSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceUseCoroutineSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Coroutines => f.write_str("coroutines"), + Self::UseCoroutine => f.write_str("use-coroutine"), + Self::YieldingValues => f.write_str("yielding-values"), + Self::SendingValues => f.write_str("sending-values"), + Self::AutomaticInjectionIntoTheContextApi => { + f.write_str("automatic-injection-into-the-context-api") + } + } + } +} +#[derive(Debug)] +pub struct ReferenceUseCoroutineSectionParseError; +impl std::fmt::Display for ReferenceUseCoroutineSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceUseCoroutineSectioncoroutines, use-coroutine, yielding-values, sending-values, automatic-injection-into-the-context-api", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceUseCoroutineSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceUseCoroutine(section: ReferenceUseCoroutineSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "coroutines", + Link { + to: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::Coroutines, + }, + class: "header", + "Coroutines" + } + } + p { + "Another tool in your async toolbox are coroutines. Coroutines are futures that can have values sent to them." + } + p { + "Like regular futures, code in a coroutine will run until the next " + code { "await" } + " point before yielding. This low-level control over asynchronous tasks is quite powerful, allowing for infinitely looping tasks like WebSocket polling, background timers, and other periodic actions." + } + h2 { id: "use-coroutine", + Link { + to: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::UseCoroutine, + }, + class: "header", + "use_coroutine" + } + } + p { + "The " + code { "use_coroutine" } + " hook allows you to create a coroutine. Most coroutines we write will be polling loops using await." + } + CodeBlock { + contents: "
\nuse futures_util::StreamExt;\n\nfn app() {{\n    let ws: Coroutine<()> = use_coroutine(|rx| async move {{\n        // Connect to some sort of service\n        let mut conn = connect_to_ws_server().await;\n\n        // Wait for data on the service\n        while let Some(msg) = conn.next().await {{\n            // handle messages\n        }}\n    }});\n}}
\n", + name: "use_coroutine_reference.rs".to_string(), + } + p { "For many services, a simple async loop will handle the majority of use cases." } + h2 { id: "yielding-values", + Link { + to: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::YieldingValues, + }, + class: "header", + "Yielding Values" + } + } + p { + "To yield values from a coroutine, simply bring in a " + code { "Signal" } + " handle and set the value whenever your coroutine completes its work." + } + p { + "The future must be " + code { "'static" } + " – so any values captured by the task cannot carry any references to " + code { "cx" } + ", such as a " + code { "Signal" } + "." + } + p { + "You can use " + Link { to: "https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned", + "to_owned" + } + " to create a clone of the hook handle which can be moved into the async closure." + } + CodeBlock { + contents: "
\nlet sync_status = use_signal(|| Status::Launching);\nlet sync_task = use_coroutine(|rx: UnboundedReceiver<SyncAction>| {{\n    let mut sync_status = sync_status.to_owned();\n    async move {{\n        loop {{\n            tokio::time::sleep(Duration::from_secs(1)).await;\n            sync_status.set(Status::Working);\n        }}\n    }}\n}});
\n", + name: "use_coroutine_reference.rs".to_string(), + } + p { + "To make this a bit less verbose, Dioxus exports the " + code { "to_owned!" } + " macro which will create a binding as shown above, which can be quite helpful when dealing with many values." + } + CodeBlock { + contents: "
\nlet sync_status = use_signal(|| Status::Launching);\nlet load_status = use_signal(|| Status::Launching);\nlet sync_task = use_coroutine(|rx: UnboundedReceiver<SyncAction>| {{\n    async move {{\n        // ...\n    }}\n}});
\n", + name: "use_coroutine_reference.rs".to_string(), + } + h2 { id: "sending-values", + Link { + to: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::SendingValues, + }, + class: "header", + "Sending Values" + } + } + p { + "You might've noticed the " + code { "use_coroutine" } + " closure takes an argument called " + code { "rx" } + ". What is that? Well, a common pattern in complex apps is to handle a bunch of async code at once. With libraries like Redux Toolkit, managing multiple promises at once can be challenging and a common source of bugs." + } + p { + "With Coroutines, we can centralize our async logic. The " + code { "rx" } + " parameter is an Channel that allows code external to the coroutine to send data " + em { "into" } + " the coroutine. Instead of looping on an external service, we can loop on the channel itself, processing messages from within our app without needing to spawn a new future. To send data into the coroutine, we would call \"send\" on the handle." + } + CodeBlock { + contents: "
\nuse futures_util::StreamExt;\n\nenum ProfileUpdate {{\n    SetUsername(String),\n    SetAge(i32),\n}}\n\nlet profile = use_coroutine(|mut rx: UnboundedReceiver<ProfileUpdate>| async move {{\n    let mut server = connect_to_server().await;\n\n    while let Some(msg) = rx.next().await {{\n        match msg {{\n            ProfileUpdate::SetUsername(name) => server.update_username(name).await,\n            ProfileUpdate::SetAge(age) => server.update_age(age).await,\n        }}\n    }}\n}});\n\nrsx! {{\n    button {{ onclick: move |_| profile.send(ProfileUpdate::SetUsername("Bob".to_string())),\n        "Update username"\n    }}\n}}
\n", + name: "use_coroutine_reference.rs".to_string(), + } + blockquote { + p { + "Note: In order to use/run the " + code { "rx.next().await" } + " statement you will need to extend the " + "[ " + code { "Stream" } + "]" + " trait (used by " + "[ " + code { "UnboundedReceiver" } + "]" + " " + ") by adding 'futures_util' as a dependency to your project and adding the " + code { "use futures_util::stream::StreamExt;" } + "." + } + } + p { + "For sufficiently complex apps, we could build a bunch of different useful \"services\" that loop on channels to update the app." + } + CodeBlock { + contents: "
\nlet profile = use_coroutine(profile_service);\nlet editor = use_coroutine(editor_service);\nlet sync = use_coroutine(sync_service);\n\nasync fn profile_service(rx: UnboundedReceiver<ProfileCommand>) {{\n    // do stuff\n}}\n\nasync fn sync_service(rx: UnboundedReceiver<SyncCommand>) {{\n    // do stuff\n}}\n\nasync fn editor_service(rx: UnboundedReceiver<EditorCommand>) {{\n    // do stuff\n}}
\n", + name: "use_coroutine_reference.rs".to_string(), + } + p { + "We can combine coroutines with Global State to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state " + em { "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 " + em { "actual" } + " state does not need to be tied up in a system like " + code { "Signal::global" } + " or Redux – the only Atoms that need to exist are those that are used to drive the display/UI." + } + CodeBlock { + contents: "
\nstatic USERNAME: GlobalSignal<String> = Signal::global(|| "default".to_string());\n\nfn app() -> Element {{\n    use_coroutine(sync_service);\n\n    rsx! {{ Banner {{}} }}\n}}\n\nfn Banner() -> Element {{\n    rsx! {{ h1 {{ "Welcome back, {{USERNAME}}" }} }}\n}}
\n", + name: "use_coroutine_reference.rs".to_string(), + } + p { + "Now, in our sync service, we can structure our state however we want. We only need to update the view values when ready." + } + CodeBlock { + contents: "
\nuse futures_util::StreamExt;\n\nstatic USERNAME: GlobalSignal<String> = Signal::global(|| "default".to_string());\nstatic ERRORS: GlobalSignal<Vec<String>> = Signal::global(|| Vec::new());\n\nenum SyncAction {{\n    SetUsername(String),\n}}\n\nasync fn sync_service(mut rx: UnboundedReceiver<SyncAction>) {{\n    while let Some(msg) = rx.next().await {{\n        match msg {{\n            SyncAction::SetUsername(name) => {{\n                if set_name_on_server(&name).await.is_ok() {{\n                    *USERNAME.write() = name;\n                }} else {{\n                    *ERRORS.write() = vec!["Failed to set username".to_string()];\n                }}\n            }}\n        }}\n    }}\n}}
\n", + name: "use_coroutine_reference.rs".to_string(), + } + h2 { id: "automatic-injection-into-the-context-api", + Link { + to: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::AutomaticInjectionIntoTheContextApi, + }, + class: "header", + "Automatic injection into the Context API" + } + } + p { + "Coroutine handles are automatically injected through the context API. You can use the " + code { "use_coroutine_handle" } + " hook with the message type as a generic to fetch a handle." + } + CodeBlock { + contents: "
\nfn Child() -> Element {{\n    let sync_task = use_coroutine_handle::<SyncAction>();\n\n    sync_task.send(SyncAction::SetUsername);\n\n    todo!()\n}}
\n", + name: "use_coroutine_reference.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceSpawnSection { + #[default] + Empty, + SpawningFutures, + SpawningTokioTasks, +} +impl std::str::FromStr for ReferenceSpawnSection { + type Err = ReferenceSpawnSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "spawning-futures" => Ok(Self::SpawningFutures), + "spawning-tokio-tasks" => Ok(Self::SpawningTokioTasks), + _ => Err(ReferenceSpawnSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceSpawnSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::SpawningFutures => f.write_str("spawning-futures"), + Self::SpawningTokioTasks => f.write_str("spawning-tokio-tasks"), + } + } +} +#[derive(Debug)] +pub struct ReferenceSpawnSectionParseError; +impl std::fmt::Display for ReferenceSpawnSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceSpawnSectionspawning-futures, spawning-tokio-tasks", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceSpawnSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceSpawn(section: ReferenceSpawnSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "spawning-futures", + Link { + to: BookRoute::ReferenceSpawn { + section: ReferenceSpawnSection::SpawningFutures, + }, + class: "header", + "Spawning Futures" + } + } + p { + "The " + code { "use_resource" } + " and " + code { "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 " + code { "spawn" } + ":" + } + CodeBlock { + contents: "
\nlet mut response = use_signal(|| String::from("..."));\n\nlet log_in = move |_| {{\n    spawn(async move {{\n        let resp = reqwest::Client::new()\n            .get("https://dioxuslabs.com")\n            .send()\n            .await;\n\n        match resp {{\n            Ok(_data) => {{\n                log::info!("dioxuslabs.com responded!");\n                response.set("dioxuslabs.com responded!".into());\n            }}\n            Err(err) => {{\n                log::info!("Request failed with error: {{err:?}}")\n            }}\n        }}\n    }});\n}};\n\nrsx! {{\n    button {{ onclick: log_in, "Response: {{response}}" }}\n}}
\n", + name: "spawn.rs".to_string(), + } + DemoFrame { spawn::App {} } + blockquote { + p { + "Note: " + code { "spawn" } + " will always spawn a " + em { "new" } + " future. You most likely don't want to call it on every render." + } + } + p { + "Calling " + code { "spawn" } + " will give you a " + code { "JoinHandle" } + " which lets you cancel or pause the future." + } + h2 { id: "spawning-tokio-tasks", + Link { + to: BookRoute::ReferenceSpawn { + section: ReferenceSpawnSection::SpawningTokioTasks, + }, + class: "header", + "Spawning Tokio Tasks" + } + } + p { + "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:" + } + CodeBlock { + contents: "
\nspawn(async {{\n    let _ = tokio::spawn(async {{}}).await;\n\n    let _ = tokio::task::spawn_local(async {{\n        // some !Send work\n    }})\n    .await;\n}});
\n", + name: "spawn.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceAssetsSection { + #[default] + Empty, + Assets, + IncludingImages, + IncludingArbitraryFiles, + IncludingStylesheets, + Conclusion, +} +impl std::str::FromStr for ReferenceAssetsSection { + type Err = ReferenceAssetsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "assets" => Ok(Self::Assets), + "including-images" => Ok(Self::IncludingImages), + "including-arbitrary-files" => Ok(Self::IncludingArbitraryFiles), + "including-stylesheets" => Ok(Self::IncludingStylesheets), + "conclusion" => Ok(Self::Conclusion), + _ => Err(ReferenceAssetsSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceAssetsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Assets => f.write_str("assets"), + Self::IncludingImages => f.write_str("including-images"), + Self::IncludingArbitraryFiles => f.write_str("including-arbitrary-files"), + Self::IncludingStylesheets => f.write_str("including-stylesheets"), + Self::Conclusion => f.write_str("conclusion"), + } + } +} +#[derive(Debug)] +pub struct ReferenceAssetsSectionParseError; +impl std::fmt::Display for ReferenceAssetsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceAssetsSectionassets, including-images, including-arbitrary-files, including-stylesheets, conclusion", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceAssetsSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceAssets(section: ReferenceAssetsSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "assets", + Link { + to: BookRoute::ReferenceAssets { + section: ReferenceAssetsSection::Assets, + }, + class: "header", + "Assets" + } + } + blockquote { + p { + "⚠\u{fe0f} Support: Manganis is currently in alpha. API changes are planned and bugs are more likely" + } + } + p { + "Assets are files that are included in the final build of the application. They can be images, fonts, stylesheets, or any other file that is not a source file. Dioxus includes first class support for assets, and provides a simple way to include them in your application and automatically optimize them for production." + } + p { + "Assets in dioxus are also compatible with libraries! If you are building a library, you can include assets in your library and they will be automatically included in the final build of any application that uses your library." + } + p { + "First, you need to add the " + code { "manganis" } + " crate to your " + code { "Cargo.toml" } + " file:" + } + CodeBlock { contents: "
\ncargo add manganis
\n" } + h2 { id: "including-images", + Link { + to: BookRoute::ReferenceAssets { + section: ReferenceAssetsSection::IncludingImages, + }, + class: "header", + "Including images" + } + } + p { + "To include an asset in your application, you can simply wrap the path to the asset in a " + code { "mg!" } + " call. For example, to include an image in your application, you can use the following code:" + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\nfn App() -> Element {{\n    // You can link to assets that are relative to the package root or even link to an asset from a url\n    // These assets will automatically be picked up by the dioxus cli, optimized, and bundled with your final applications\n    const ASSET: Asset = asset!("/assets/static/ferrous_wave.png");\n\n    rsx! {{\n        img {{ src: "{{ASSET}}" }}\n    }}\n}}
\n", + name: "assets.rs".to_string(), + } + p { + "You can also optimize, resize, and preload images using the " + code { "mg!" } + " macro. Choosing an optimized file type (like WebP) and a reasonable quality setting can significantly reduce the size of your images which helps your application load faster. For example, you can use the following code to include an optimized image in your application:" + } + CodeBlock { + contents: "
\npub const ENUM_ROUTER_IMG: Asset = asset!("/assets/static/enum_router.png");\n\nfn EnumRouter() -> Element {{\n    rsx! {{\n        img {{ src: "{{ENUM_ROUTER_IMG}}" }}\n    }}\n}}
\n", + name: "assets.rs".to_string(), + } + h2 { id: "including-arbitrary-files", + Link { + to: BookRoute::ReferenceAssets { + section: ReferenceAssetsSection::IncludingArbitraryFiles, + }, + class: "header", + "Including arbitrary files" + } + } + p { + "In dioxus desktop, you may want to include a file with data for your application. You can use the " + code { "file" } + " function to include arbitrary files in your application. For example, you can use the following code to include a file in your application:" + } + CodeBlock { + contents: "
\n// You can also collect arbitrary files. Relative paths are resolved relative to the package root\nconst PATH_TO_BUNDLED_CARGO_TOML: &str = manganis::mg!(file("./Cargo.toml"));
\n", + name: "assets.rs".to_string(), + } + p { + "These files will be automatically included in the final build of your application, and you can use them in your application as you would any other file." + } + h2 { id: "including-stylesheets", + Link { + to: BookRoute::ReferenceAssets { + section: ReferenceAssetsSection::IncludingStylesheets, + }, + class: "header", + "Including stylesheets" + } + } + p { + "You can include stylesheets in your application using the " + code { "mg!" } + " macro. For example, you can use the following code to include a stylesheet in your application:" + } + CodeBlock { + contents: "
\n// You can also bundle stylesheets with your application\n// Any files that end with .css will be minified and bundled with your application even if you don't explicitly include them in your <head>\nconst _: &str = manganis::mg!(file("./tailwind.css"));
\n", + name: "assets.rs".to_string(), + } + blockquote { + p { + "The " + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Empty, + }, + "tailwind guide" + } + " has more information on how to use tailwind with dioxus." + } + } + h2 { id: "conclusion", + Link { + to: BookRoute::ReferenceAssets { + section: ReferenceAssetsSection::Conclusion, + }, + class: "header", + "Conclusion" + } + } + p { + "Dioxus provides first class support for assets, and makes it easy to include them in your application. You can include images, arbitrary files, and stylesheets in your application, and dioxus will automatically optimize them for production. This makes it easy to include assets in your application and ensure that they are optimized for production." + } + p { + "You can read more about assets and all the options available to optimize your assets in the " + Link { to: "https://docs.rs/manganis/0.2.2/manganis/", "manganis documentation" } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceChoosingAWebRendererSection { + #[default] + Empty, + ChoosingAWebRenderer, + DioxusLiveview, + DioxusWeb, + DioxusFullstack, +} +impl std::str::FromStr for ReferenceChoosingAWebRendererSection { + type Err = ReferenceChoosingAWebRendererSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "choosing-a-web-renderer" => Ok(Self::ChoosingAWebRenderer), + "dioxus-liveview" => Ok(Self::DioxusLiveview), + "dioxus-web" => Ok(Self::DioxusWeb), + "dioxus-fullstack" => Ok(Self::DioxusFullstack), + _ => Err(ReferenceChoosingAWebRendererSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceChoosingAWebRendererSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ChoosingAWebRenderer => f.write_str("choosing-a-web-renderer"), + Self::DioxusLiveview => f.write_str("dioxus-liveview"), + Self::DioxusWeb => f.write_str("dioxus-web"), + Self::DioxusFullstack => f.write_str("dioxus-fullstack"), + } + } +} +#[derive(Debug)] +pub struct ReferenceChoosingAWebRendererSectionParseError; +impl std::fmt::Display for ReferenceChoosingAWebRendererSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceChoosingAWebRendererSectionchoosing-a-web-renderer, dioxus-liveview, dioxus-web, dioxus-fullstack", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceChoosingAWebRendererSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceChoosingAWebRenderer( + section: ReferenceChoosingAWebRendererSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "choosing-a-web-renderer", + Link { + to: BookRoute::ReferenceChoosingAWebRenderer { + section: ReferenceChoosingAWebRendererSection::ChoosingAWebRenderer, + }, + class: "header", + "Choosing a web renderer" + } + } + p { "Dioxus has three different renderers that target the web:" } + ul { + li { + Link { + to: BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::Empty, + }, + "dioxus-web" + } + " allows you to render your application to HTML with " + Link { to: "https://rustwasm.github.io/docs/book/", "WebAssembly" } + " on the client" + } + li { + Link { + to: BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::Empty, + }, + "dioxus-liveview" + } + " allows you to run your application on the server and render it to HTML on the client with a websocket" + } + li { + Link { + to: BookRoute::ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection::Empty, + }, + "dioxus-fullstack" + } + " allows you to initially render static HTML on the server and then update that HTML from the client with " + Link { to: "https://rustwasm.github.io/docs/book/", "WebAssembly" } + } + } + p { "Each approach has its tradeoffs:" } + h3 { id: "dioxus-liveview", + Link { + to: BookRoute::ReferenceChoosingAWebRenderer { + section: ReferenceChoosingAWebRendererSection::DioxusLiveview, + }, + class: "header", + "Dioxus Liveview" + } + } + ul { + li { + p { + "Liveview rendering communicates with the server over a WebSocket connection. It essentially moves all of the work that Client-side rendering does to the server." + } + } + li { + p { + "This makes it " + strong { + "easy to communicate with the server, but more difficult to communicate with the client/browser APIS" + } + "." + } + } + li { + p { + "Each interaction also requires a message to be sent to the server and back which can cause " + strong { "issues with latency" } + "." + } + } + li { + p { + "Because Liveview uses a websocket to render, the page will be blank until the WebSocket connection has been established and the first renderer has been sent from the websocket. Just like with client side rendering, this can make the page " + strong { "less SEO-friendly" } + "." + } + } + li { + p { + "Because the page is rendered on the server and the page is sent to the client piece by piece, you never need to send the entire application to the client. The initial load time can be faster than client-side rendering with large applications because Liveview only needs to send a constant small websocket script regardless of the size of the application." + } + } + } + blockquote { + p { + "Liveview is a good fit for applications that already need to communicate with the server frequently (like real time collaborative apps), but don't need to communicate with as many client/browser APIs." + } + } + p { + Link { to: "https://mermaid.live/edit#pako:eNplULFOw0AM_RXLc7Mw3sBQVUIMRYgKdcli5ZzkRHIuPl8QqvrvXJICRXiy3nt-9-6dsRHP6DAZGe8CdUpjNd3VEcpsVT4SK1TVPRxYJ1YHL_yeOdkqWMGF3w4U32Y6nSQmXvknMQYNXW8g7bfk2JPBg0g3MCTmdH1rJhenx2is1FiYri43wJ8or3O2H1Liv0w3hw724kMb2MMzdcUYNziyjhR8-f15Pq3Reh65RldWzy3lwWqs46VIKZscPmODzjTzBvPJ__aFrqUhFZR9MNH92uhS7OULYSF1lw", + img { + src: "https://mermaid.ink/img/pako:eNplULFOw0AM_RXLc7Mw3sBQVUIMRYgKdcli5ZzkRHIuPl8QqvrvXJICRXiy3nt-9-6dsRHP6DAZGe8CdUpjNd3VEcpsVT4SK1TVPRxYJ1YHL_yeOdkqWMGF3w4U32Y6nSQmXvknMQYNXW8g7bfk2JPBg0g3MCTmdH1rJhenx2is1FiYri43wJ8or3O2H1Liv0w3hw724kMb2MMzdcUYNziyjhR8-f15Pq3Reh65RldWzy3lwWqs46VIKZscPmODzjTzBvPJ__aFrqUhFZR9MNH92uhS7OULYSF1lw?type=png", + alt: "", + title: "", + } + } + } + h3 { id: "dioxus-web", + Link { + to: BookRoute::ReferenceChoosingAWebRenderer { + section: ReferenceChoosingAWebRendererSection::DioxusWeb, + }, + class: "header", + "Dioxus Web" + } + } + ul { + li { + p { + "With Client side rendering, you send your application to the client, and then the client generates all of the HTML of the page dynamically." + } + } + li { + p { + "This means that the page will be blank until the JavaScript bundle has loaded and the application has initialized. This can result in " + strong { "slower first render times and poor SEO performance" } + "." + } + } + } + blockquote { + p { + "SEO stands for Search Engine Optimization. It refers to the practice of making your website more likely to appear in search engine results. Search engines like Google and Bing use web crawlers to index the content of websites. Most of these crawlers are not able to run JavaScript, so they will not be able to index the content of your page if it is rendered client-side." + } + } + ul { + li { + "Client-side rendered applications need to use " + strong { "weakly typed requests to communicate with the server" } + "." + } + } + blockquote { + p { + "Client-side rendering is a good starting point for most applications. It is well supported and makes it easy to communicate with the client/browser APIs." + } + } + p { + Link { to: "https://mermaid.live/edit#pako:eNpVkDFPwzAQhf-KdXOzMHpgqJAQAwytEIsXK35JLBJfez4Xoar_HSemQtzke9_z2e-u1HMAWcrqFU_Rj-KX7vLgkqm1F_7KENN1j-YIuUCsOeBckLUZmrjx_ezT54rziVNG42-sMBLHSQ0Pd8vH5NU8M48zTAby71sr3CYdkAIEoen37h-y5n3910tSiO81cqIdLZDFx1DDXNerjnTCAke2HgMGX2Z15NKtWn1RPn6nnqxKwY7KKfzFJzv4OVcVISrLa1vQtqfbDzd0ZKY", + img { + src: "https://mermaid.ink/img/pako:eNpVkDFPwzAQhf-KdXOzMHpgqJAQAwytEIsXK35JLBJfez4Xoar_HSemQtzke9_z2e-u1HMAWcrqFU_Rj-KX7vLgkqm1F_7KENN1j-YIuUCsOeBckLUZmrjx_ezT54rziVNG42-sMBLHSQ0Pd8vH5NU8M48zTAby71sr3CYdkAIEoen37h-y5n3910tSiO81cqIdLZDFx1DDXNerjnTCAke2HgMGX2Z15NKtWn1RPn6nnqxKwY7KKfzFJzv4OVcVISrLa1vQtqfbDzd0ZKY?type=png", + alt: "", + title: "", + } + } + } + h3 { id: "dioxus-fullstack", + Link { + to: BookRoute::ReferenceChoosingAWebRenderer { + section: ReferenceChoosingAWebRendererSection::DioxusFullstack, + }, + class: "header", + "Dioxus Fullstack" + } + } + p { "Fullstack rendering happens in two parts:" } + ol { + li { + "The page is rendered on the server. This can include fetching any data you need to render the page." + } + li { + "The page is hydrated on the client. (Hydration is taking the HTML page from the server and adding all of the event listeners Dioxus needs on the client). Any updates to the page happen on the client after this point." + } + } + p { + "Because the page is initially rendered on the server, the page will be fully rendered when it is sent to the client. This results in a faster first render time and makes the page more SEO-friendly." + } + ul { + li { + strong { "Fast initial render" } + } + li { + strong { "Works well with SEO" } + } + li { + strong { "Type safe easy communication with the server" } + } + li { + strong { "Access to the client/browser APIs" } + } + li { + strong { "Fast interactivity" } + } + } + p { + "Finally, we can use " + Link { + to: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::Empty, + }, + "server functions" + } + " to communicate with the server in a type-safe way." + } + p { + "This approach uses both the dioxus-web and dioxus-ssr crates. To integrate those two packages Dioxus provides the " + code { "dioxus-fullstack" } + " crate." + } + p { + "There can be more complexity with fullstack applications because your code runs in two different places. Dioxus tries to mitigate this with server functions and other helpers." + } + p { + Link { to: "https://mermaid.live/edit#pako:eNpdkL1uwzAMhF9F4BwvHTV0KAIUHdohQdFFi2CdbQG2mFCUiyDIu9e2-hOUE3H34UDelVoOIEtZvWIffS9-auYHl8wyT8KfGWKa5tEcITPEmgPOBVkrUMXNPyAFCMJK5BOnjIq8scJI7Ac13N1RH4NX88zcjzAZyJX-8bfIl6QQ32qcv7PuhP-ANe_rpb8KJ9rRBJl8DMt71zXAkQ6Y4Mgua0Dny6iOXLotqC_Kx0tqyaoU7Kicwl8hZDs_5kVFiMryWivbmrt9AacxbGg", + img { + src: "https://mermaid.ink/img/pako:eNpdkL1uwzAMhF9F4BwvHTV0KAIUHdohQdFFi2CdbQG2mFCUiyDIu9e2-hOUE3H34UDelVoOIEtZvWIffS9-auYHl8wyT8KfGWKa5tEcITPEmgPOBVkrUMXNPyAFCMJK5BOnjIq8scJI7Ac13N1RH4NX88zcjzAZyJX-8bfIl6QQ32qcv7PuhP-ANe_rpb8KJ9rRBJl8DMt71zXAkQ6Y4Mgua0Dny6iOXLotqC_Kx0tqyaoU7Kicwl8hZDs_5kVFiMryWivbmrt9AacxbGg?type=png", + alt: "", + title: "", + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceDesktopIndexSection { + #[default] + Empty, + Desktop, + Examples, + RunningJavascript, + CustomAssets, + IntegratingWithWry, +} +impl std::str::FromStr for ReferenceDesktopIndexSection { + type Err = ReferenceDesktopIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "desktop" => Ok(Self::Desktop), + "examples" => Ok(Self::Examples), + "running-javascript" => Ok(Self::RunningJavascript), + "custom-assets" => Ok(Self::CustomAssets), + "integrating-with-wry" => Ok(Self::IntegratingWithWry), + _ => Err(ReferenceDesktopIndexSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceDesktopIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Desktop => f.write_str("desktop"), + Self::Examples => f.write_str("examples"), + Self::RunningJavascript => f.write_str("running-javascript"), + Self::CustomAssets => f.write_str("custom-assets"), + Self::IntegratingWithWry => f.write_str("integrating-with-wry"), + } + } +} +#[derive(Debug)] +pub struct ReferenceDesktopIndexSectionParseError; +impl std::fmt::Display for ReferenceDesktopIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceDesktopIndexSectiondesktop, examples, running-javascript, custom-assets, integrating-with-wry", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceDesktopIndexSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceDesktopIndex(section: ReferenceDesktopIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "desktop", + Link { + to: BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::Desktop, + }, + class: "header", + "Desktop" + } + } + p { "This guide will cover concepts specific to the Dioxus desktop renderer." } + p { + "Apps built with Dioxus desktop use the system WebView to render the page. This makes the final size of application much smaller than other WebView renderers (typically under 5MB)." + } + p { + "Although desktop apps are rendered in a WebView, your Rust code runs natively. This means that browser APIs are " + em { "not" } + " available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs " + em { "are" } + " accessible, so streaming, WebSockets, filesystem, etc are all easily accessible though system APIs." + } + p { + "Dioxus desktop is built off " + Link { to: "https://tauri.app/", "Tauri" } + ". Right now there are limited Dioxus abstractions over the menubar, event handling, etc. In some places you may need to leverage Tauri directly – through " + Link { to: "http://github.com/tauri-apps/wry/", "Wry" } + " and " + Link { to: "http://github.com/tauri-apps/tao", "Tao" } + "." + } + blockquote { + p { + "In the future, we plan to move to a custom web renderer-based DOM renderer with WGPU integrations (" + Link { to: "https://github.com/DioxusLabs/blitz", "Blitz" } + ")." + } + } + h2 { id: "examples", + Link { + to: BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::Examples, + }, + class: "header", + "Examples" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/blob/main/example-projects/file-explorer", + "File Explorer" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/examples/tailwind", + "Tailwind App" + } + } + } + p { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/examples/tailwind", + img { + src: asset!( + "/assets/static/tailwind_desktop_app.png", ImageAssetOptions::new().with_webp() + ), + alt: "Tailwind App screenshot", + title: "", + } + } + } + h2 { id: "running-javascript", + Link { + to: BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::RunningJavascript, + }, + class: "header", + "Running Javascript" + } + } + p { + "Dioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose." + } + p { + "For these cases, Dioxus desktop exposes the use_eval hook that allows you to run raw Javascript in the webview:" + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\nfn main() {{\n    launch(app);\n}}\n\nfn app() -> Element {{\n    // You can create as many eval instances as you want\n    let mut eval = eval(\n        r#"\n        // You can send messages from JavaScript to Rust with the dioxus.send function\n        dioxus.send("Hi from JS!");\n        // You can receive messages from Rust to JavaScript with the dioxus.recv function\n        let msg = await dioxus.recv();\n        console.log(msg);\n        "#,\n    );\n\n    // You can send messages to JavaScript with the send method\n    eval.send("Hi from Rust!".into()).unwrap();\n\n    let future = use_resource(move || {{\n        to_owned![eval];\n        async move {{\n            // You can receive any message from JavaScript with the recv method\n            eval.recv().await.unwrap()\n        }}\n    }});\n\n    match future.read_unchecked().as_ref() {{\n        Some(v) => rsx! {{\n            p {{ "{{v}}" }}\n        }},\n        _ => rsx! {{\n            p {{ "hello" }}\n        }},\n    }}\n}}
\n", + name: "eval.rs".to_string(), + } + h2 { id: "custom-assets", + Link { + to: BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::CustomAssets, + }, + class: "header", + "Custom Assets" + } + } + p { "You can link to local assets in dioxus desktop instead of using a url:" } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\nfn main() {{\n    launch(app);\n}}\n\nfn app() -> Element {{\n    rsx! {{\n        div {{\n            img {{ src: "/public/static/scanner.png" }}\n        }}\n    }}\n}}
\n", + name: "custom_assets.rs".to_string(), + } + p { + "You can read more about assets in the " + Link { + to: BookRoute::ReferenceAssets { + section: ReferenceAssetsSection::Empty, + }, + "assets" + } + " reference." + } + h2 { id: "integrating-with-wry", + Link { + to: BookRoute::ReferenceDesktopIndex { + section: ReferenceDesktopIndexSection::IntegratingWithWry, + }, + class: "header", + "Integrating with Wry" + } + } + p { + "In cases where you need more low level control over your window, you can use wry APIs exposed through the " + Link { to: "https://docs.rs/dioxus-desktop/0.5.0/dioxus_desktop/struct.Config.html", + "Desktop Config" + } + " and the " + Link { to: "https://docs.rs/dioxus-desktop/0.5.0/dioxus_desktop/fn.use_window.html", + "use_window hook" + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceMobileIndexSection { + #[default] + Empty, + MobileApp, + Support, + GettingSetUp, + SettingUpDependencies, + AndroidDependencies, + IosDependencies, + SettingUpYourProject, + Running, + Android, + Ios, +} +impl std::str::FromStr for ReferenceMobileIndexSection { + type Err = ReferenceMobileIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "mobile-app" => Ok(Self::MobileApp), + "support" => Ok(Self::Support), + "getting-set-up" => Ok(Self::GettingSetUp), + "setting-up-dependencies" => Ok(Self::SettingUpDependencies), + "android-dependencies" => Ok(Self::AndroidDependencies), + "ios-dependencies" => Ok(Self::IosDependencies), + "setting-up-your-project" => Ok(Self::SettingUpYourProject), + "running" => Ok(Self::Running), + "android" => Ok(Self::Android), + "ios" => Ok(Self::Ios), + _ => Err(ReferenceMobileIndexSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceMobileIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::MobileApp => f.write_str("mobile-app"), + Self::Support => f.write_str("support"), + Self::GettingSetUp => f.write_str("getting-set-up"), + Self::SettingUpDependencies => f.write_str("setting-up-dependencies"), + Self::AndroidDependencies => f.write_str("android-dependencies"), + Self::IosDependencies => f.write_str("ios-dependencies"), + Self::SettingUpYourProject => f.write_str("setting-up-your-project"), + Self::Running => f.write_str("running"), + Self::Android => f.write_str("android"), + Self::Ios => f.write_str("ios"), + } + } +} +#[derive(Debug)] +pub struct ReferenceMobileIndexSectionParseError; +impl std::fmt::Display for ReferenceMobileIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceMobileIndexSectionmobile-app, support, getting-set-up, setting-up-dependencies, android-dependencies, ios-dependencies, setting-up-your-project, running, android, ios", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceMobileIndexSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceMobileIndex(section: ReferenceMobileIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "mobile-app", + Link { + to: BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::MobileApp, + }, + class: "header", + "Mobile App" + } + } + p { "Build a mobile app with Dioxus!" } + p { + "Example: " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/examples/mobile_demo", + "Mobile Demo" + } + } + h2 { id: "support", + Link { + to: BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::Support, + }, + class: "header", + "Support" + } + } + p { + "Mobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with either the platform's WebView or experimentally with " + Link { to: "https://github.com/DioxusLabs/blitz", "WGPU" } + ". WebView doesn't support animations, transparency, and native widgets." + } + p { + "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." + } + h2 { id: "getting-set-up", + Link { + to: BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::GettingSetUp, + }, + class: "header", + "Getting Set up" + } + } + p { + "Getting set up with mobile can be quite challenging. The tooling here isn't great (yet) and might take some hacking around to get things working." + } + h3 { id: "setting-up-dependencies", + Link { + to: BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::SettingUpDependencies, + }, + class: "header", + "Setting up dependencies" + } + } + h4 { id: "android-dependencies", + Link { + to: BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::AndroidDependencies, + }, + class: "header", + "Android Dependencies" + } + } + p { "First, install the rust Android targets:" } + CodeBlock { contents: "
\nrustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
\n" } + p { + "To develop on Android, you will need to " + Link { to: "https://developer.android.com/studio", "install Android Studio" } + "." + } + p { "Once you have installed Android Studio, you will need to install the Android SDK and NDK:" } + ol { + li { "Create a blank Android project" } + li { + "Select " + code { "Tools > SDK manager" } + } + li { + "Navigate to the " + code { "SDK tools" } + " window:" + } + } + p { + img { + src: asset!( + "/assets/static/android_ndk_install.png", ImageAssetOptions::new().with_webp() + ), + alt: "NDK install window", + title: "", + } + } + p { "Then select:" } + ul { + li { "The SDK" } + li { "The SDK Command line tools" } + li { "The NDK (side by side)" } + li { "CMAKE" } + } + ol { + li { + "Select " + code { "apply" } + " and follow the prompts" + } + } + blockquote { + p { + "More details that could be useful for debugging any errors you encounter are available " + Link { to: "https://developer.android.com/studio/intro/update#sdk-manager", + "in the official android docs" + } + } + } + p { "Next set the Java, Android and NDK home variables:" } + p { "Mac:" } + CodeBlock { contents: "
\nexport JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"\nexport ANDROID_HOME="$HOME/Library/Android/sdk"\nexport NDK_HOME="$ANDROID_HOME/ndk/25.2.9519653"
\n" } + p { "Windows:" } + CodeBlock { + contents: "
\n[System.Environment]::SetEnvironmentVariable("JAVA_HOME", "C:\\Program Files\\Android\\Android Studio\\jbr", "User")\n[System.Environment]::SetEnvironmentVariable("ANDROID_HOME", "$env:LocalAppData\\Android\\Sdk", "User")\n[System.Environment]::SetEnvironmentVariable("NDK_HOME", "$env:LocalAppData\\Android\\Sdk\\ndk\\25.2.9519653", "User")
\n", + } + blockquote { + p { "The NDK version in the paths should match the version you installed in the last step" } + } + h4 { id: "ios-dependencies", + Link { + to: BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::IosDependencies, + }, + class: "header", + "IOS Dependencies" + } + } + p { "First, install the rust IOS targets:" } + CodeBlock { contents: "
\nrustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim
\n" } + p { + "To develop on IOS, you will need to " + Link { to: "https://apps.apple.com/us/app/xcode/id497799835", "install XCode" } + "." + } + blockquote { + p { + "Note: On Apple silicon you must run Xcode on rosetta. Goto Application > Right Click Xcode > Get Info > Open in Rosetta." + " " + "If you are using M1, you will have to run " + code { "cargo build --target x86_64-apple-ios" } + " instead of " + code { "cargo apple build" } + " if you want to run in simulator." + } + } + h3 { id: "setting-up-your-project", + Link { + to: BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::SettingUpYourProject, + }, + class: "header", + "Setting up your project" + } + } + p { "First, we need to create a rust project:" } + CodeBlock { contents: "
\ncargo new dioxus-mobile-test --lib\ncd dioxus-mobile-test
\n" } + p { + "Next, we can use " + code { "cargo-mobile2" } + " to create a project for mobile:" + } + CodeBlock { contents: "
\ncargo install --git https://github.com/tauri-apps/cargo-mobile2\ncargo mobile init
\n" } + p { + "When you run " + code { "cargo mobile init" } + ", you will be asked a series of questions about your project. One of those questions is what template you should use. Dioxus currently doesn't have a template in Tauri mobile, instead you can use the " + code { "wry" } + " template." + } + blockquote { + p { + "You may also be asked to input your team ID for IOS. You can find your team id " + Link { to: "https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/", + "here" + } + " or create a team id by creating a developer account " + Link { to: "https://developer.apple.com/help/account/get-started/about-your-developer-account", + "here" + } + } + } + p { + "Next, we need to modify our dependencies to include dioxus and ensure the right version of wry is installed. Change the " + code { "[dependencies]" } + " section of your " + code { "Cargo.toml" } + ":" + } + CodeBlock { + contents: "
\n[dependencies]\nanyhow = "1.0.56"\nlog = "0.4.11"\ndioxus = {{ version = "0.5", features = ["mobile"] }}\nwry = "0.35.0"\ntao = "0.25.0"
\n", + } + p { + "Finally, we need to add a component to renderer. Replace the wry template in your " + code { "lib.rs" } + " file with this code:" + } + CodeBlock { + contents: "
\nuse anyhow::Result;\nuse dioxus::prelude::*;\n\n#[cfg(target_os = "android")]\nfn init_logging() {{\n    android_logger::init_once(\n        android_logger::Config::default()\n            .with_max_level(log::LevelFilter::Trace)\n    );\n}}\n\n#[cfg(not(target_os = "android"))]\nfn init_logging() {{\n    env_logger::init();\n}}\n\n#[cfg(any(target_os = "android", target_os = "ios"))]\nfn stop_unwind<F: FnOnce() -> T, T>(f: F) -> T {{\n    match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {{\n        Ok(t) => t,\n        Err(err) => {{\n            eprintln!("attempt to unwind out of `rust` with err: {{:?}}", err);\n            std::process::abort()\n        }}\n    }}\n}}\n\n#[no_mangle]\n#[inline(never)]\n#[cfg(any(target_os = "android", target_os = "ios"))]\npub extern "C" fn start_app() {{\n    fn _start_app() {{\n        stop_unwind(|| main().unwrap());\n    }}\n\n    #[cfg(target_os = "android")]\n    {{\n        tao::android_binding!(\n            com_example,\n            dioxus_mobile_test,\n            WryActivity,\n            wry::android_setup, // pass the wry::android_setup function to tao which will invoke when the event loop is created\n            _start_app\n        );\n        wry::android_binding!(com_example, dioxus_mobile_test);\n    }}\n    #[cfg(target_os = "ios")]\n    _start_app()\n}}\n\npub fn main() -> Result<()> {{\n    init_logging();\n\n    launch(app);\n\n    Ok(())\n}}\n\nfn app() -> Element {{\n    let mut items = use_signal(|| vec![1, 2, 3]);\n\n    log::debug!("Hello from the app");\n\n    rsx! {{\n        div {{\n            h1 {{ "Hello, Mobile"}}\n            div {{ margin_left: "auto", margin_right: "auto", width: "200px", padding: "10px", border: "1px solid black",\n                button {{\n                    onclick: move|_| {{\n                        println!("Clicked!");\n                        let mut items_mut = items.write();\n                        let new_item = items_mut.len() + 1;\n                        items_mut.push(new_item);\n                        println!("Requested update");\n                    }},\n                    "Add item"\n                }}\n                for item in items.read().iter() {{\n                    div {{ "- {{item}}" }}\n                }}\n            }}\n        }}\n    }}\n}}
\n", + } + h2 { id: "running", + Link { + to: BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::Running, + }, + class: "header", + "Running" + } + } + p { + "From there, you'll want to get a build of the crate using whichever platform you're targeting (simulator or actual hardware). For now, we'll just stick with the simulator." + } + p { "First, you need to make sure that the build variant is correct in Android Studio:" } + ol { + li { "Click \"Build\" in the top menu bar." } + li { "Click \"Select Build Variant...\" in the dropdown." } + li { + "Find the \"Build Variants\" panel and use the dropdown to change the selected build variant." + } + } + p { + img { + src: asset!("/assets/static/as-build-dropdown.png", ImageAssetOptions::new().with_webp()), + alt: "android studio build dropdown", + title: "", + } + img { + src: asset!( + "/assets/static/as-build-variant-menu.png", ImageAssetOptions::new().with_webp() + ), + alt: "android studio build variants", + title: "", + } + } + h3 { id: "android", + Link { + to: BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::Android, + }, + class: "header", + "Android" + } + } + p { "To build your project on Android you can run:" } + CodeBlock { contents: "
\ncargo android build
\n" } + p { "Next, open Android studio:" } + CodeBlock { contents: "
\ncargo android open
\n" } + p { "This will open an android studio project for this application." } + p { + "Next we need to create a simulator in Android studio to run our app in. To create a simulator click on the phone icon in the top right of Android studio:" + } + p { + img { + src: asset!( + "/assets/static/android-studio-simulator.png", ImageAssetOptions::new() + .with_webp() + ), + alt: "android studio manage devices", + title: "", + } + } + p { + "Then click the " + code { "create a virtual device" } + " button and follow the prompts:" + } + p { + img { + src: asset!( + "/assets/static/android-studio-devices.png", ImageAssetOptions::new().with_webp() + ), + alt: "android studio devices", + title: "", + } + } + p { "Finally, launch your device by clicking the play button on the device you created:" } + p { + img { + src: asset!( + "/assets/static/android-studio-device.png", ImageAssetOptions::new().with_webp() + ), + alt: "android studio device", + title: "", + } + } + p { "Now you can start your application from your terminal by running:" } + CodeBlock { contents: "
\ncargo android run
\n" } + p { + img { + src: asset!( + "/assets/static/Android-Dioxus-demo.png", ImageAssetOptions::new().with_webp() + ), + alt: "android_demo", + title: "", + } + } + blockquote { + p { "More information is available in the Android docs:" } + ul { + li { "https://developer.android.com/ndk/guides" } + li { "https://developer.android.com/studio/projects/install-ndk" } + li { "https://source.android.com/docs/setup/build/rust/building-rust-modules/overview" } + } + } + h3 { id: "ios", + Link { + to: BookRoute::ReferenceMobileIndex { + section: ReferenceMobileIndexSection::Ios, + }, + class: "header", + "IOS" + } + } + p { "To build your project for IOS, you can run:" } + CodeBlock { contents: "
\ncargo build --target aarch64-apple-ios-sim
\n" } + p { "Next, open XCode (this might take awhile if you've never opened XCode before):" } + CodeBlock { contents: "
\ncargo apple open
\n" } + p { "This will open XCode with this particular project." } + p { + "From there, just click the \"play\" button with the right target and the app should be running!" + } + p { + img { + src: asset!("/assets/static/IOS-dioxus-demo.png", ImageAssetOptions::new().with_webp()), + alt: "ios_demo", + title: "", + } + } + p { + "Note that clicking play doesn't cause a new build, so you'll need to keep rebuilding the app between changes. The tooling here is very young, so please be patient. If you want to contribute to make things easier, please do! We'll be happy to help." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceMobileApisSection { + #[default] + Empty, + Mobile, + RunningJavascript, + CustomAssets, + IntegratingWithWry, +} +impl std::str::FromStr for ReferenceMobileApisSection { + type Err = ReferenceMobileApisSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "mobile" => Ok(Self::Mobile), + "running-javascript" => Ok(Self::RunningJavascript), + "custom-assets" => Ok(Self::CustomAssets), + "integrating-with-wry" => Ok(Self::IntegratingWithWry), + _ => Err(ReferenceMobileApisSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceMobileApisSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Mobile => f.write_str("mobile"), + Self::RunningJavascript => f.write_str("running-javascript"), + Self::CustomAssets => f.write_str("custom-assets"), + Self::IntegratingWithWry => f.write_str("integrating-with-wry"), + } + } +} +#[derive(Debug)] +pub struct ReferenceMobileApisSectionParseError; +impl std::fmt::Display for ReferenceMobileApisSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceMobileApisSectionmobile, running-javascript, custom-assets, integrating-with-wry", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceMobileApisSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceMobileApis(section: ReferenceMobileApisSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "mobile", + Link { + to: BookRoute::ReferenceMobileApis { + section: ReferenceMobileApisSection::Mobile, + }, + class: "header", + "Mobile" + } + } + p { "This guide will cover concepts specific to the Dioxus mobile renderer." } + h2 { id: "running-javascript", + Link { + to: BookRoute::ReferenceMobileApis { + section: ReferenceMobileApisSection::RunningJavascript, + }, + class: "header", + "Running Javascript" + } + } + p { + "Dioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose." + } + p { + "For these cases, Dioxus desktop exposes the use_eval hook that allows you to run raw Javascript in the webview:" + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\nfn main() {{\n    launch(app);\n}}\n\nfn app() -> Element {{\n    // You can create as many eval instances as you want\n    let mut eval = eval(\n        r#"\n        // You can send messages from JavaScript to Rust with the dioxus.send function\n        dioxus.send("Hi from JS!");\n        // You can receive messages from Rust to JavaScript with the dioxus.recv function\n        let msg = await dioxus.recv();\n        console.log(msg);\n        "#,\n    );\n\n    // You can send messages to JavaScript with the send method\n    eval.send("Hi from Rust!".into()).unwrap();\n\n    let future = use_resource(move || {{\n        to_owned![eval];\n        async move {{\n            // You can receive any message from JavaScript with the recv method\n            eval.recv().await.unwrap()\n        }}\n    }});\n\n    match future.read_unchecked().as_ref() {{\n        Some(v) => rsx! {{\n            p {{ "{{v}}" }}\n        }},\n        _ => rsx! {{\n            p {{ "hello" }}\n        }},\n    }}\n}}
\n", + name: "eval.rs".to_string(), + } + h2 { id: "custom-assets", + Link { + to: BookRoute::ReferenceMobileApis { + section: ReferenceMobileApisSection::CustomAssets, + }, + class: "header", + "Custom Assets" + } + } + p { "You can link to local assets in dioxus mobile instead of using a url:" } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\nfn main() {{\n    launch(app);\n}}\n\nfn app() -> Element {{\n    rsx! {{\n        div {{\n            img {{ src: "/public/static/scanner.png" }}\n        }}\n    }}\n}}
\n", + name: "custom_assets.rs".to_string(), + } + h2 { id: "integrating-with-wry", + Link { + to: BookRoute::ReferenceMobileApis { + section: ReferenceMobileApisSection::IntegratingWithWry, + }, + class: "header", + "Integrating with Wry" + } + } + p { + "In cases where you need more low level control over your window, you can use wry APIs exposed through the " + Link { to: "https://docs.rs/dioxus-desktop/0.5.0/dioxus_desktop/struct.Config.html", + "Desktop Config" + } + " and the " + Link { to: "https://docs.rs/dioxus-desktop/0.5.0/dioxus_desktop/struct.DesktopContext.html", + "use_window hook" + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceWebIndexSection { + #[default] + Empty, + Web, + Support, + RunningJavascript, + CustomizingIndexTemplate, +} +impl std::str::FromStr for ReferenceWebIndexSection { + type Err = ReferenceWebIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "web" => Ok(Self::Web), + "support" => Ok(Self::Support), + "running-javascript" => Ok(Self::RunningJavascript), + "customizing-index-template" => Ok(Self::CustomizingIndexTemplate), + _ => Err(ReferenceWebIndexSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceWebIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Web => f.write_str("web"), + Self::Support => f.write_str("support"), + Self::RunningJavascript => f.write_str("running-javascript"), + Self::CustomizingIndexTemplate => f.write_str("customizing-index-template"), + } + } +} +#[derive(Debug)] +pub struct ReferenceWebIndexSectionParseError; +impl std::fmt::Display for ReferenceWebIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceWebIndexSectionweb, support, running-javascript, customizing-index-template", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceWebIndexSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceWebIndex(section: ReferenceWebIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "web", + Link { + to: BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::Web, + }, + class: "header", + "Web" + } + } + p { + "To run on the Web, your app must be compiled to WebAssembly and depend on the " + code { "dioxus" } + " and " + code { "dioxus-web" } + " crates." + } + p { + "A build of Dioxus for the web will be roughly equivalent to the size of a React build (70kb vs 65kb) but it will load significantly faster because " + Link { to: "https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/", + "WebAssembly can be compiled as it is streamed" + } + "." + } + p { "Examples:" } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/blob/main/examples/todomvc.rs", + "TodoMVC" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/examples/tailwind", + "Tailwind App" + } + } + } + p { + Link { to: "https://github.com/DioxusLabs/dioxus/blob/main/examples/todomvc.rs", + img { + src: "https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png", + alt: "TodoMVC example", + title: "", + } + } + } + blockquote { + p { + "Note: Because of the limitations of Wasm, " + Link { to: "https://rustwasm.github.io/docs/book/reference/which-crates-work-with-wasm.html", + "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)." + } + } + h2 { id: "support", + Link { + to: BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::Support, + }, + class: "header", + "Support" + } + } + p { "The Web is the best-supported target platform for Dioxus." } + ul { + li { + "Because your app will be compiled to WASM you have access to browser APIs through " + Link { to: "https://rustwasm.github.io/docs/wasm-bindgen/introduction.html", + "wasm-bindgen" + } + "." + } + li { + "Dioxus provides hydration to resume apps that are rendered on the server. See the " + Link { + to: BookRoute::ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection::Empty, + }, + "fullstack" + } + " reference for more information." + } + } + h2 { id: "running-javascript", + Link { + to: BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::RunningJavascript, + }, + class: "header", + "Running Javascript" + } + } + p { + "Dioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose." + } + p { + "For these cases, Dioxus web exposes the use_eval hook that allows you to run raw Javascript in the webview:" + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\nfn main() {{\n    launch(app);\n}}\n\nfn app() -> Element {{\n    // You can create as many eval instances as you want\n    let mut eval = eval(\n        r#"\n        // You can send messages from JavaScript to Rust with the dioxus.send function\n        dioxus.send("Hi from JS!");\n        // You can receive messages from Rust to JavaScript with the dioxus.recv function\n        let msg = await dioxus.recv();\n        console.log(msg);\n        "#,\n    );\n\n    // You can send messages to JavaScript with the send method\n    eval.send("Hi from Rust!".into()).unwrap();\n\n    let future = use_resource(move || {{\n        to_owned![eval];\n        async move {{\n            // You can receive any message from JavaScript with the recv method\n            eval.recv().await.unwrap()\n        }}\n    }});\n\n    match future.read_unchecked().as_ref() {{\n        Some(v) => rsx! {{\n            p {{ "{{v}}" }}\n        }},\n        _ => rsx! {{\n            p {{ "hello" }}\n        }},\n    }}\n}}
\n", + name: "eval.rs".to_string(), + } + p { + "If you are targeting web, but don't plan on targeting any other Dioxus renderer you can also use the generated wrappers in the " + Link { to: "https://rustwasm.github.io/wasm-bindgen/web-sys/index.html", + "web-sys" + } + " and " + Link { to: "https://gloo-rs.web.app/", "gloo" } + " crates." + } + h2 { id: "customizing-index-template", + Link { + to: BookRoute::ReferenceWebIndex { + section: ReferenceWebIndexSection::CustomizingIndexTemplate, + }, + class: "header", + "Customizing Index Template" + } + } + p { + "Dioxus supports providing custom index.html templates. The index.html must include a " + code { "div" } + " with the id " + code { "main" } + " to be used. Hot Reload is still supported. An example" + " " + "is provided in the " + Link { to: "https://github.com/DioxusLabs/dioxus/blob/main/examples/PWA-example/index.html", + "PWA-Example" + } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceSsrSection { + #[default] + Empty, + ServerSideRendering, + Setup, + MultithreadedSupport, +} +impl std::str::FromStr for ReferenceSsrSection { + type Err = ReferenceSsrSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "server-side-rendering" => Ok(Self::ServerSideRendering), + "setup" => Ok(Self::Setup), + "multithreaded-support" => Ok(Self::MultithreadedSupport), + _ => Err(ReferenceSsrSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceSsrSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ServerSideRendering => f.write_str("server-side-rendering"), + Self::Setup => f.write_str("setup"), + Self::MultithreadedSupport => f.write_str("multithreaded-support"), + } + } +} +#[derive(Debug)] +pub struct ReferenceSsrSectionParseError; +impl std::fmt::Display for ReferenceSsrSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceSsrSectionserver-side-rendering, setup, multithreaded-support", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceSsrSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceSsr(section: ReferenceSsrSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "server-side-rendering", + Link { + to: BookRoute::ReferenceSsr { + section: ReferenceSsrSection::ServerSideRendering, + }, + class: "header", + "Server-Side Rendering" + } + } + p { + "For lower-level control over the rendering process, you can use the " + code { "dioxus-ssr" } + " crate directly. This can be useful when integrating with a web framework that " + code { "dioxus-fullstack" } + " does not support, or pre-rendering pages." + } + h2 { id: "setup", + Link { + to: BookRoute::ReferenceSsr { + section: ReferenceSsrSection::Setup, + }, + class: "header", + "Setup" + } + } + p { + "For this guide, we're going to show how to use Dioxus SSR with " + Link { to: "https://docs.rs/axum/latest/axum/", "Axum" } + "." + } + p { "Make sure you have Rust and Cargo installed, and then create a new project:" } + CodeBlock { contents: "
\ncargo new --bin demo\ncd demo
\n" } + p { "Add Dioxus and the ssr renderer as dependencies:" } + CodeBlock { contents: "
\ncargo add dioxus@0.5.0\ncargo add dioxus-ssr@0.5.0
\n" } + p { + "Next, add all the Axum dependencies. This will be different if you're using a different Web Framework" + } + CodeBlock { contents: "
\ncargo add tokio --features full\ncargo add axum
\n" } + p { "Your dependencies should look roughly like this:" } + CodeBlock { + contents: "
\n[dependencies]\naxum = "0.7"\ndioxus = {{ version = "*" }}\ndioxus-ssr = {{ version = "*" }}\ntokio = {{ version = "1.15.0", features = ["full"] }}
\n", + } + p { "Now, set up your Axum app to respond on an endpoint." } + CodeBlock { + contents: "
\nuse axum::{{response::Html, routing::get, Router}};\nuse dioxus::prelude::*;\n\n#[tokio::main]\nasync fn main() {{\n    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")\n        .await\n        .unwrap();\n\n    println!("listening on http://127.0.0.1:3000");\n\n    axum::serve(\n        listener,\n        Router::new()\n            .route("/", get(app_endpoint))\n            .into_make_service(),\n    )\n    .await\n    .unwrap();\n}}
\n", + name: "ssr.rs".to_string(), + } + p { + "And then add our endpoint. We can either render " + code { "rsx!" } + " directly:" + } + CodeBlock { + contents: "
\nasync fn app_endpoint() -> Html<String> {{\n    // render the rsx! macro to HTML\n    Html(dioxus_ssr::render_element(rsx! {{ div {{ "hello world!" }} }}))\n}}
\n", + name: "ssr.rs".to_string(), + } + p { "Or we can render VirtualDoms." } + CodeBlock { + contents: "
\nasync fn app_endpoint() -> Html<String> {{\n    // create a component that renders a div with the text "hello world"\n    fn app() -> Element {{\n        rsx! {{ div {{ "hello world" }} }}\n    }}\n    // create a VirtualDom with the app component\n    let mut app = VirtualDom::new(app);\n    // rebuild the VirtualDom before rendering\n    app.rebuild_in_place();\n\n    // render the VirtualDom to HTML\n    Html(dioxus_ssr::render(&app))\n}}
\n", + name: "ssr.rs".to_string(), + } + p { + "Finally, you can run it using " + code { "cargo run" } + " rather than " + code { "dx serve" } + "." + } + h2 { id: "multithreaded-support", + Link { + to: BookRoute::ReferenceSsr { + section: ReferenceSsrSection::MultithreadedSupport, + }, + class: "header", + "Multithreaded Support" + } + } + p { + "The Dioxus VirtualDom, sadly, is not currently " + code { "Send" } + ". Internally, we use quite a bit of interior mutability which is not thread-safe." + " " + "When working with web frameworks that require " + code { "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 spawn a VirtualDom on its own thread and communicate with it via channels or create a pool of VirtualDoms." + " " + "You might notice that you cannot hold the VirtualDom across an await point. Because Dioxus is currently not ThreadSafe, it " + em { "must" } + " remain on the thread it started. We are working on loosening this requirement." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceLiveviewSection { + #[default] + Empty, + Liveview, + Support, + RouterIntegration, + ManagingLatency, +} +impl std::str::FromStr for ReferenceLiveviewSection { + type Err = ReferenceLiveviewSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "liveview" => Ok(Self::Liveview), + "support" => Ok(Self::Support), + "router-integration" => Ok(Self::RouterIntegration), + "managing-latency" => Ok(Self::ManagingLatency), + _ => Err(ReferenceLiveviewSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceLiveviewSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Liveview => f.write_str("liveview"), + Self::Support => f.write_str("support"), + Self::RouterIntegration => f.write_str("router-integration"), + Self::ManagingLatency => f.write_str("managing-latency"), + } + } +} +#[derive(Debug)] +pub struct ReferenceLiveviewSectionParseError; +impl std::fmt::Display for ReferenceLiveviewSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceLiveviewSectionliveview, support, router-integration, managing-latency", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceLiveviewSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceLiveview(section: ReferenceLiveviewSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "liveview", + Link { + to: BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::Liveview, + }, + class: "header", + "Liveview" + } + } + p { + "Liveview allows apps to " + em { "run" } + " on the server and " + em { "render" } + " in the browser. It uses WebSockets to communicate between the server and the browser." + } + p { "Examples:" } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/packages/liveview/examples/axum.rs", + "Simple Example" + } + } + } + h2 { id: "support", + Link { + to: BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::Support, + }, + class: "header", + "Support" + } + } + p { + "Dioxus liveview will be migrated to " + Link { + to: BookRoute::ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection::Empty, + }, + "dioxus-fullstack" + } + " in a future release. Once this migration occurs, you may need to update your code. We plan for this migration to be minimal." + } + p { + "Liveview is currently limited in capability when compared to the Web platform. Liveview apps run on the server in 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." + } + h2 { id: "router-integration", + Link { + to: BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::RouterIntegration, + }, + class: "header", + "Router Integration" + } + } + p { + "Currently, the Dioxus router does not integrate with the browser history in the liveview renderer. If you are interested in contributing this feature to Dioxus this issue is tracked " + Link { to: "https://github.com/DioxusLabs/dioxus/issues/1038", "here" } + "." + } + h2 { id: "managing-latency", + Link { + to: BookRoute::ReferenceLiveview { + section: ReferenceLiveviewSection::ManagingLatency, + }, + class: "header", + "Managing Latency" + } + } + p { + "Liveview makes it incredibly convenient to talk to your server from the client, but there are some downsides. Mainly in Dioxus Liveview every interaction goes through the server by default." + } + p { + "Because of this, with the liveview renderer you need to be very deliberate about managing latency. Events that would be fast enough on other renderers like " + Link { + to: BookRoute::ReferenceUserInput { + section: ReferenceUserInputSection::Empty, + }, + "controlled inputs" + } + ", can be frustrating to use in the liveview renderer." + } + p { + "To get around this issue you can inject bits of javascript in your liveview application. If you use a raw attribute as a listener, you can inject some javascript that will be run when the event is triggered:" + } + CodeBlock { contents: "
\nrsx! {{\n    div {{\n        input {{\n            "oninput": "console.log('input changed!')"\n        }}\n    }}\n}}
\n" } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceFullstackIndexSection { + #[default] + Empty, + FullstackDevelopment, +} +impl std::str::FromStr for ReferenceFullstackIndexSection { + type Err = ReferenceFullstackIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "fullstack-development" => Ok(Self::FullstackDevelopment), + _ => Err(ReferenceFullstackIndexSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceFullstackIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::FullstackDevelopment => f.write_str("fullstack-development"), + } + } +} +#[derive(Debug)] +pub struct ReferenceFullstackIndexSectionParseError; +impl std::fmt::Display for ReferenceFullstackIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceFullstackIndexSectionfullstack-development", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceFullstackIndexSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceFullstackIndex( + section: ReferenceFullstackIndexSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "fullstack-development", + Link { + to: BookRoute::ReferenceFullstackIndex { + section: ReferenceFullstackIndexSection::FullstackDevelopment, + }, + class: "header", + "Fullstack development" + } + } + p { "Dioxus Fullstack contains helpers for:" } + ul { + li { "Incremental, static, and server side rendering" } + li { "Hydrating your application on the Client" } + li { "Communicating between a server and a client" } + } + p { + "This guide will teach you everything you need to know about how to use the utilities in Dioxus fullstack to create amazing fullstack applications." + } + blockquote { + p { + "In addition to this guide, you can find more examples of full-stack apps and information about how to integrate with other frameworks and desktop renderers in the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/packages/fullstack/examples", + "dioxus-fullstack examples directory" + } + "." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceFullstackServerFunctionsSection { + #[default] + Empty, + CommunicatingWithTheServer, + CachedDataFetching, + RunningTheClientWithDioxusDesktop, + ClientCode, + ServerCode, +} +impl std::str::FromStr for ReferenceFullstackServerFunctionsSection { + type Err = ReferenceFullstackServerFunctionsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "communicating-with-the-server" => Ok(Self::CommunicatingWithTheServer), + "cached-data-fetching" => Ok(Self::CachedDataFetching), + "running-the-client-with-dioxus-desktop" => Ok(Self::RunningTheClientWithDioxusDesktop), + "client-code" => Ok(Self::ClientCode), + "server-code" => Ok(Self::ServerCode), + _ => Err(ReferenceFullstackServerFunctionsSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceFullstackServerFunctionsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::CommunicatingWithTheServer => f.write_str("communicating-with-the-server"), + Self::CachedDataFetching => f.write_str("cached-data-fetching"), + Self::RunningTheClientWithDioxusDesktop => { + f.write_str("running-the-client-with-dioxus-desktop") + } + Self::ClientCode => f.write_str("client-code"), + Self::ServerCode => f.write_str("server-code"), + } + } +} +#[derive(Debug)] +pub struct ReferenceFullstackServerFunctionsSectionParseError; +impl std::fmt::Display for ReferenceFullstackServerFunctionsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceFullstackServerFunctionsSectioncommunicating-with-the-server, cached-data-fetching, running-the-client-with-dioxus-desktop, client-code, server-code", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceFullstackServerFunctionsSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceFullstackServerFunctions( + section: ReferenceFullstackServerFunctionsSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "communicating-with-the-server", + Link { + to: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::CommunicatingWithTheServer, + }, + class: "header", + "Communicating with the server" + } + } + p { + code { "dioxus-fullstack" } + " provides server functions that allow you to call an automatically generated API on the server from the client as if it were a local function." + } + p { + "To make a server function, simply add the " + code { "#[server(YourUniqueType)]" } + " attribute to a function. The function must:" + } + ul { + li { "Be an async function" } + li { + "Have arguments and a return type that both implement serialize and deserialize (with " + Link { to: "https://serde.rs/", "serde" } + ")." + } + li { + "Return a " + code { "Result" } + " with an error type of ServerFnError" + } + } + blockquote { + p { + "If you are targeting WASM on the server with WASI, you must call " + code { "register" } + " on the type you passed into the server macro in your main function before starting your server to tell Dioxus about the server function. For all other targets, the server function will be registered automatically." + } + } + p { + "Let's continue building on the app we made in the " + Link { + to: BookRoute::GettingStartedIndex { + section: GettingStartedIndexSection::Empty, + }, + "getting started" + } + " guide. We will add a server function to our app that allows us to double the count on the server." + } + p { "First, add serde as a dependency:" } + CodeBlock { contents: "
\ncargo add serde
\n" } + p { + "Next, add the server function to your " + code { "main.rs" } + ":" + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\n\nfn main() {{\n    launch(App)\n}}\n\nfn App() -> Element {{\n    let mut count = use_signal(|| 0);\n\n    rsx! {{\n        h1 {{ "High-Five counter: {{count}}" }}\n        button {{ onclick: move |_| count += 1, "Up high!" }}\n        button {{ onclick: move |_| count -= 1, "Down low!" }}\n        button {{\n            onclick: move |_| {{\n                async move {{\n                    if let Ok(new_count) = double_server(count()).await {{\n                        count.set(new_count);\n                    }}\n                }}\n            }},\n            "Double"\n        }}\n    }}\n}}\n\n#[server]\nasync fn double_server(number: i32) -> Result<i32, ServerFnError> {{\n    // Perform some expensive computation or access a database on the server\n    tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n    let result = number * 2;\n    println!("server calculated {{result}}");\n    Ok(result)\n}}
\n", + name: "server_function.rs".to_string(), + } + p { + "Now, build your client-side bundle with " + code { "dx build --features web" } + " and run your server with " + code { "cargo run --features ssr" } + ". You should see a new button that multiplies the count by 2." + } + h2 { id: "cached-data-fetching", + Link { + to: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::CachedDataFetching, + }, + class: "header", + "Cached data fetching" + } + } + p { "One common use case for server functions is fetching data from the server:" } + CodeBlock { + contents: "
\n#![allow(non_snake_case, unused)]\n\nuse dioxus::prelude::*;\n\nfn main() {{\n    launch(app)\n}}\n\nfn app() -> Element {{\n    let mut count = use_resource(get_server_data);\n\n    rsx! {{"server data is {{count.value():?}}"}}\n}}\n\n#[server]\nasync fn get_server_data() -> Result<String, ServerFnError> {{\n    // Access a database\n    tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n    Ok("Hello from the server!".to_string())\n}}
\n", + name: "server_data_fetch.rs".to_string(), + } + p { + "If you navigate to the site above, you will first see " + code { "server data is None" } + ", then after the " + code { "WASM" } + " has loaded and the request to the server has finished, you will see " + code { "server data is Some(Ok(\"Hello from the server!\"))" } + "." + } + p { + "This approach works, but it can be slow. Instead of waiting for the client to load and send a request to the server, what if we could get all of the data we needed for the page on the server and send it down to the client with the initial HTML page?" + } + p { + "This is exactly what the " + code { "use_server_future" } + " hook allows us to do! " + code { "use_server_future" } + " is similar to the " + code { "use_resource" } + " hook, but it allows you to wait for a future on the server and send the result of the future down to the client." + } + p { + "Let's change our data fetching to use " + code { "use_server_future" } + ":" + } + CodeBlock { + contents: "
\n#![allow(non_snake_case, unused)]\n\nuse dioxus::prelude::*;\n\nfn main() {{\n    launch(app);\n}}\n\nfn app() -> Element {{\n    let mut count = use_server_future(get_server_data)?;\n\n    rsx! {{"server data is {{count.value():?}}"}}\n}}\n\n#[server]\nasync fn get_server_data() -> Result<String, ServerFnError> {{\n    // Access a database\n    tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n    Ok("Hello from the server!".to_string())\n}}
\n", + name: "server_data_prefetch.rs".to_string(), + } + blockquote { + p { + "Notice the " + code { "?" } + " after " + code { "use_server_future" } + ". This is what tells Dioxus fullstack to wait for the future to resolve before continuing rendering. If you want to not wait for a specific future, you can just remove the ? and deal with the " + code { "Option" } + " manually." + } + } + p { + "Now when you load the page, you should see " + code { "server data is Ok(\"Hello from the server!\")" } + ". No need to wait for the " + code { "WASM" } + " to load or wait for the request to finish!" + } + SandBoxFrame { url: "https://codesandbox.io/p/sandbox/dioxus-fullstack-server-future-qwpp4p?file=/src/main.rs:3,24" } + h2 { id: "running-the-client-with-dioxus-desktop", + Link { + to: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::RunningTheClientWithDioxusDesktop, + }, + class: "header", + "Running the client with dioxus-desktop" + } + } + p { + "The project presented so far makes a web browser interact with the server, but it is also possible to make a desktop program interact with the server in a similar fashion. (The full example code is available in the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/packages/fullstack/examples/axum-desktop", + "Dioxus repo" + } + ")" + } + p { + "First, we need to make two binary targets, one for the desktop program (the " + code { "client.rs" } + " file), one for the server (the " + code { "server.rs" } + " file). The client app and the server functions are written in a shared " + code { "lib.rs" } + " file." + } + p { + "The desktop and server targets have slightly different build configuration to enable additional dependencies or features." + " " + "The Cargo.toml in the full example has more information, but the main points are:" + } + ul { + li { + "the client.rs has to be run with the " + code { "desktop" } + " feature, so that the optional " + code { "dioxus-desktop" } + " dependency is included" + } + li { + "the server.rs has to be run with the " + code { "ssr" } + " features; this will generate the server part of the server functions and will run our backend server." + } + } + p { "Once you create your project, you can run the server executable with:" } + CodeBlock { contents: "
\ncargo run --bin server --features ssr
\n" } + p { "and the client desktop executable with:" } + CodeBlock { contents: "
\ncargo run --bin client --features desktop
\n" } + h3 { id: "client-code", + Link { + to: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::ClientCode, + }, + class: "header", + "Client code" + } + } + p { + "The client file is pretty straightforward. You only need to set the server url in the client code, so it knows where to send the network requests. Then, dioxus_desktop launches the app." + } + p { + "For development, the example project runs the server on " + code { "localhost:8080" } + ". " + strong { "Before you release remember to update the url to your production url." } + } + h3 { id: "server-code", + Link { + to: BookRoute::ReferenceFullstackServerFunctions { + section: ReferenceFullstackServerFunctionsSection::ServerCode, + }, + class: "header", + "Server code" + } + } + p { + "In the server code, first you have to set the network address and port where the server will listen to." + } + CodeBlock { + contents: "
\nlet listener = tokio::net::TcpListener::bind("127.0.0.1:3000")\n    .await\n    .unwrap();\nprintln!("listening on http://127.0.0.1:3000");
\n", + name: "server_function_desktop_client.rs".to_string(), + } + p { + "Then, you have to register the types declared in the server function macros into the server." + " " + "For example, consider this server function:" + } + CodeBlock { + contents: "
\n#[server(GetServerData)]\nasync fn get_server_data() -> Result<String, ServerFnError> {{\n    Ok("Hello from the server!".to_string())\n}}
\n", + name: "server_function_desktop_client.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceFullstackExtractorsSection { + #[default] + Empty, + Extractors, +} +impl std::str::FromStr for ReferenceFullstackExtractorsSection { + type Err = ReferenceFullstackExtractorsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "extractors" => Ok(Self::Extractors), + _ => Err(ReferenceFullstackExtractorsSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceFullstackExtractorsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Extractors => f.write_str("extractors"), + } + } +} +#[derive(Debug)] +pub struct ReferenceFullstackExtractorsSectionParseError; +impl std::fmt::Display for ReferenceFullstackExtractorsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceFullstackExtractorsSectionextractors", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceFullstackExtractorsSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceFullstackExtractors( + section: ReferenceFullstackExtractorsSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "extractors", + Link { + to: BookRoute::ReferenceFullstackExtractors { + section: ReferenceFullstackExtractorsSection::Extractors, + }, + class: "header", + "Extractors" + } + } + p { + "Server functions are an ergonomic way to call a function on the server. Server function work by registering an endpoint on the server and using requests on the client. Most of the time, you shouldn't need to worry about how server functions operate, but there are some times when you need to get some value from the request other than the data passed in the server function." + } + p { + "For example, requests contain information about the user's browser (called the " + Link { to: "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent", + "user agent" + } + "). We can use an extractor to retrieve that information." + } + p { + "You can use the " + code { "extract" } + " method within a server function to extract something from the request. You can extract any type that implements " + code { "FromServerContext" } + " (or when axum is enabled, you can use axum extractors directly):" + } + CodeBlock { + contents: "
\n#[server]\npub async fn log_headers() -> Result<(), ServerFnError> {{\n    let headers: http::HeaderMap = extract().await?;\n    log::info!("{{:?}}", headers[http::header::USER_AGENT]);\n    Ok(())\n}}
\n", + name: "server_function_extract.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceFullstackMiddlewareSection { + #[default] + Empty, + Middleware, +} +impl std::str::FromStr for ReferenceFullstackMiddlewareSection { + type Err = ReferenceFullstackMiddlewareSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "middleware" => Ok(Self::Middleware), + _ => Err(ReferenceFullstackMiddlewareSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceFullstackMiddlewareSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Middleware => f.write_str("middleware"), + } + } +} +#[derive(Debug)] +pub struct ReferenceFullstackMiddlewareSectionParseError; +impl std::fmt::Display for ReferenceFullstackMiddlewareSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceFullstackMiddlewareSectionmiddleware", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceFullstackMiddlewareSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceFullstackMiddleware( + section: ReferenceFullstackMiddlewareSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "middleware", + Link { + to: BookRoute::ReferenceFullstackMiddleware { + section: ReferenceFullstackMiddlewareSection::Middleware, + }, + class: "header", + "Middleware" + } + } + p { + "Extractors allow you to wrap your server function in some code that changes either the request or the response. Dioxus fullstack integrates with " + Link { to: "https://docs.rs/tower/latest/tower/index.html", "Tower" } + " to allow you to wrap your server functions in middleware." + } + p { + "You can use the " + code { "#[middleware]" } + " attribute to add a layer of middleware to your server function. Let's add a timeout middleware to a server function. This middleware will stop running the server function if it reaches a certain timeout:" + } + CodeBlock { + contents: "
\n#[server]\n// Add a timeout middleware to the server function that will return an error if the function takes longer than 1 second to execute\n#[middleware(tower_http::timeout::TimeoutLayer::new(std::time::Duration::from_secs(1)))]\npub async fn timeout() -> Result<(), ServerFnError> {{\n    tokio::time::sleep(std::time::Duration::from_secs(2)).await;\n    Ok(())\n}}
\n", + name: "server_function_middleware.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceFullstackAuthenticationSection { + #[default] + Empty, + Authentication, +} +impl std::str::FromStr for ReferenceFullstackAuthenticationSection { + type Err = ReferenceFullstackAuthenticationSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "authentication" => Ok(Self::Authentication), + _ => Err(ReferenceFullstackAuthenticationSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceFullstackAuthenticationSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Authentication => f.write_str("authentication"), + } + } +} +#[derive(Debug)] +pub struct ReferenceFullstackAuthenticationSectionParseError; +impl std::fmt::Display for ReferenceFullstackAuthenticationSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceFullstackAuthenticationSectionauthentication", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceFullstackAuthenticationSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceFullstackAuthentication( + section: ReferenceFullstackAuthenticationSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "authentication", + Link { + to: BookRoute::ReferenceFullstackAuthentication { + section: ReferenceFullstackAuthenticationSection::Authentication, + }, + class: "header", + "Authentication" + } + } + p { + "You can use " + Link { + to: BookRoute::ReferenceFullstackExtractors { + section: ReferenceFullstackExtractorsSection::Empty, + }, + "extractors" + } + " to integrate auth with your Fullstack application." + } + p { + "You can create a custom extractors that extracts the auth session from the request. From that auth session, you can check if the user has the required privileges before returning the private data." + } + p { + "A " + Link { to: "https://github.com/DioxusLabs/dioxus/blob/v0.5/packages/fullstack/examples/axum-auth/src/main.rs", + "full auth example" + } + " with the complete implementation is available in the fullstack examples." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ReferenceFullstackRoutingSection { + #[default] + Empty, + Routing, +} +impl std::str::FromStr for ReferenceFullstackRoutingSection { + type Err = ReferenceFullstackRoutingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "routing" => Ok(Self::Routing), + _ => Err(ReferenceFullstackRoutingSectionParseError), + } + } +} +impl std::fmt::Display for ReferenceFullstackRoutingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Routing => f.write_str("routing"), + } + } +} +#[derive(Debug)] +pub struct ReferenceFullstackRoutingSectionParseError; +impl std::fmt::Display for ReferenceFullstackRoutingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ReferenceFullstackRoutingSectionrouting", + )?; + Ok(()) + } +} +impl std::error::Error for ReferenceFullstackRoutingSectionParseError {} +#[component(no_case_check)] +pub fn ReferenceFullstackRouting( + section: ReferenceFullstackRoutingSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "routing", + Link { + to: BookRoute::ReferenceFullstackRouting { + section: ReferenceFullstackRoutingSection::Routing, + }, + class: "header", + "Routing" + } + } + p { + "You can easily integrate your fullstack application with a client side router using Dioxus Router. This allows you to create different scenes in your app and navigate between them. You can read more about the router in the " + Link { + to: BookRoute::ReferenceRouter { + section: ReferenceRouterSection::Empty, + }, + "router reference" + } + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\n\nuse axum::Router;\nuse dioxus::prelude::*;\n\nuse dioxus_router::prelude::*;\nuse serde::{{Deserialize, Serialize}};\n\nfn main() {{\n    launch(|| rsx! {{ Router::<Route> {{}} }});\n}}\n\n#[derive(Clone, Routable, Debug, PartialEq, Serialize, Deserialize)]\nenum Route {{\n    #[route("/")]\n    Home {{}},\n    #[route("/blog/:id")]\n    Blog {{ id: i32 }},\n}}\n\n#[component]\nfn Blog(id: i32) -> Element {{\n    rsx! {{\n        Link {{ to: Route::Home {{}}, "Go to counter" }}\n        table {{\n            tbody {{\n                for _ in 0..id {{\n                    tr {{\n                        for _ in 0..id {{\n                            td {{ "hello world!" }}\n                        }}\n                    }}\n                }}\n            }}\n        }}\n    }}\n}}\n\n#[component]\nfn Home() -> Element {{\n    let mut count = use_signal(|| 0);\n    let mut text = use_signal(|| "...".to_string());\n\n    rsx! {{\n        Link {{ to: Route::Blog {{ id: count() }}, "Go to blog" }}\n        div {{\n            h1 {{ "High-Five counter: {{count}}" }}\n            button {{ onclick: move |_| count += 1, "Up high!" }}\n            button {{ onclick: move |_| count -= 1, "Down low!" }}\n            button {{\n                onclick: move |_| {{\n                    async move {{\n                        if let Ok(data) = get_server_data().await {{\n                            println!("Client received: {{}}", data);\n                            text.set(data.clone());\n                            post_server_data(data).await.unwrap();\n                        }}\n                    }}\n                }},\n                "Run server function!"\n            }}\n            "Server said: {{text}}"\n        }}\n    }}\n}}\n\n#[server(PostServerData)]\nasync fn post_server_data(data: String) -> Result<(), ServerFnError> {{\n    println!("Server received: {{}}", data);\n\n    Ok(())\n}}\n\n#[server(GetServerData)]\nasync fn get_server_data() -> Result<String, ServerFnError> {{\n    Ok("Hello from the server!".to_string())\n}}
\n", + name: "server_router.rs".to_string(), + } + SandBoxFrame { url: "https://codesandbox.io/p/sandbox/dioxus-fullstack-router-s75v5q?file=%2Fsrc%2Fmain.rs%3A7%2C1" } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterIndexSection { + #[default] + Empty, + Introduction, +} +impl std::str::FromStr for RouterIndexSection { + type Err = RouterIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "introduction" => Ok(Self::Introduction), + _ => Err(RouterIndexSectionParseError), + } + } +} +impl std::fmt::Display for RouterIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Introduction => f.write_str("introduction"), + } + } +} +#[derive(Debug)] +pub struct RouterIndexSectionParseError; +impl std::fmt::Display for RouterIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of RouterIndexSectionintroduction")?; + Ok(()) + } +} +impl std::error::Error for RouterIndexSectionParseError {} +#[component(no_case_check)] +pub fn RouterIndex(section: RouterIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "introduction", + Link { + to: BookRoute::RouterIndex { + section: RouterIndexSection::Introduction, + }, + class: "header", + "Introduction" + } + } + blockquote { + p { + "If you are not familiar with Dioxus itself, check out the " + Link { + to: BookRoute::GuideIndex { + section: GuideIndexSection::Empty, + }, + "Dioxus guide" + } + " first." + } + } + p { + "Whether you are building a website, desktop app, or mobile app, splitting your app's views into \"pages\" can be an effective method for organization and maintainability." + } + p { + "For this purpose, Dioxus provides a router. Use the " + code { "cargo add" } + " command to add the dependency:" + } + CodeBlock { contents: "
\ncargo add dioxus@0.5.0 --features router
\n" } + p { + "Then, add this to your " + code { "Dioxus.toml" } + " (learn more about configuration " + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Empty, + }, + "here" + } + "):" + } + CodeBlock { contents: "
\n[web.watcher]\nindex_on_404 = true
\n" } + blockquote { + p { + "This configuration only works when using " + code { "dx serve" } + ". If you host your app in a different way (which you most likely do in production), you need to find out how to add a fallback 404 page to your app, and make it a copy of the generated " + code { "dist/index.html" } + "." + } + } + p { + "This will instruct " + code { "dx serve" } + " to redirect any unknown route to the index, to then be resolved by the router." + " " + "The router works on the client. If we connect through the index route (e.g., " + code { "localhost:8080" } + ", then click a link to go to " + code { "localhost:8080/contact" } + "), the app renders the new route without reloading." + " " + "However, when we go to a route " + em { "before" } + " going to the index (go straight to " + code { "localhost:8080/contact" } + "), we are trying to access a static route from the server, but the only static route on our server is the index (because the Dioxus frontend is a Single Page Application) and it will fail unless we redirect all missing routes to the index." + } + p { + "This book is intended to get you up to speed with Dioxus Router. It is split" + " " + "into two sections:" + } + ol { + li { + "The " + Link { + to: BookRoute::RouterReferenceIndex { + section: RouterReferenceIndexSection::Empty, + }, + "reference" + } + " section explains individual features in" + " " + "depth. You can read it from start to finish, or you can read individual chapters" + " " + "in whatever order you want." + } + li { + "If you prefer a learning-by-doing approach, you can check out the" + em { + Link { + to: BookRoute::RouterExampleIndex { + section: RouterExampleIndexSection::Empty, + }, + "example project" + } + } + ". It guides you through" + " " + "creating a dioxus app, setting up the router, and using some of its" + " " + "functionality." + } + } + blockquote { + p { + "Please note that this is not the only documentation for the Dioxus Router. You" + " " + "can also check out the " + Link { to: "https://docs.rs/dioxus-router/", "API Docs" } + "." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterExampleIndexSection { + #[default] + Empty, + Overview, + YoullLearnHowTo, +} +impl std::str::FromStr for RouterExampleIndexSection { + type Err = RouterExampleIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "overview" => Ok(Self::Overview), + "youll-learn-how-to" => Ok(Self::YoullLearnHowTo), + _ => Err(RouterExampleIndexSectionParseError), + } + } +} +impl std::fmt::Display for RouterExampleIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Overview => f.write_str("overview"), + Self::YoullLearnHowTo => f.write_str("youll-learn-how-to"), + } + } +} +#[derive(Debug)] +pub struct RouterExampleIndexSectionParseError; +impl std::fmt::Display for RouterExampleIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterExampleIndexSectionoverview, youll-learn-how-to", + )?; + Ok(()) + } +} +impl std::error::Error for RouterExampleIndexSectionParseError {} +#[component(no_case_check)] +pub fn RouterExampleIndex(section: RouterExampleIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "overview", + Link { + to: BookRoute::RouterExampleIndex { + section: RouterExampleIndexSection::Overview, + }, + class: "header", + "Overview" + } + } + p { + "In this guide, you'll learn to effectively use Dioxus Router whether you're" + " " + "building a small todo app or the next FAANG company. We will create a small" + " " + "website with a blog, homepage, and more!" + } + blockquote { + p { + "To follow along with the router example, you'll need a working Dioxus app." + " " + "Check out the " + Link { to: "https://dioxuslabs.com/learn/0.5/getting_started", "Dioxus book" } + " to get started." + } + } + blockquote { + p { + "Make sure to add Dioxus Router as a dependency, as explained in the" + Link { + to: BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }, + "introduction" + } + "." + } + } + h2 { id: "youll-learn-how-to", + Link { + to: BookRoute::RouterExampleIndex { + section: RouterExampleIndexSection::YoullLearnHowTo, + }, + class: "header", + "You'll learn how to" + } + } + ul { + li { "Create routes and render \"pages\"." } + li { + "Utilize nested routes, create a navigation bar, and render content for a" + " " + "set of routes." + } + li { "Parse URL parameters to dynamically display content." } + li { "Redirect visitors to different routes." } + } + blockquote { + p { + strong { "Disclaimer" } + } + p { + "The example will only display the features of Dioxus Router. It will not" + " " + "include any actual functionality. To keep things simple we will only be using" + " " + "a single file, this is not the recommended way of doing things with a real" + " " + "application." + } + } + p { + "You can find the complete application in the " + Link { + to: BookRoute::RouterExampleFullCode { + section: RouterExampleFullCodeSection::Empty, + }, + "full code" + } + " chapter." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterExampleFirstRouteSection { + #[default] + Empty, + CreatingOurFirstRoute, + Fundamentals, + CreatingRoutes, + FallbackRoute, + Conclusion, +} +impl std::str::FromStr for RouterExampleFirstRouteSection { + type Err = RouterExampleFirstRouteSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "creating-our-first-route" => Ok(Self::CreatingOurFirstRoute), + "fundamentals" => Ok(Self::Fundamentals), + "creating-routes" => Ok(Self::CreatingRoutes), + "fallback-route" => Ok(Self::FallbackRoute), + "conclusion" => Ok(Self::Conclusion), + _ => Err(RouterExampleFirstRouteSectionParseError), + } + } +} +impl std::fmt::Display for RouterExampleFirstRouteSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::CreatingOurFirstRoute => f.write_str("creating-our-first-route"), + Self::Fundamentals => f.write_str("fundamentals"), + Self::CreatingRoutes => f.write_str("creating-routes"), + Self::FallbackRoute => f.write_str("fallback-route"), + Self::Conclusion => f.write_str("conclusion"), + } + } +} +#[derive(Debug)] +pub struct RouterExampleFirstRouteSectionParseError; +impl std::fmt::Display for RouterExampleFirstRouteSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterExampleFirstRouteSectioncreating-our-first-route, fundamentals, creating-routes, fallback-route, conclusion", + )?; + Ok(()) + } +} +impl std::error::Error for RouterExampleFirstRouteSectionParseError {} +#[component(no_case_check)] +pub fn RouterExampleFirstRoute( + section: RouterExampleFirstRouteSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "creating-our-first-route", + Link { + to: BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::CreatingOurFirstRoute, + }, + class: "header", + "Creating Our First Route" + } + } + p { + "In this chapter, we will start utilizing Dioxus Router and add a homepage and a" + " " + "404 page to our project." + } + h2 { id: "fundamentals", + Link { + to: BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::Fundamentals, + }, + class: "header", + "Fundamentals" + } + } + p { + "The core of the Dioxus Router is the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Routable.html", + code { "Routable" } + } + " macro and the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Router.html", + code { "Router" } + } + " component." + } + p { "Routable is a trait for anything that can:" } + ul { + li { "Be parsed from a URL" } + li { "Be turned into a URL" } + li { "Be rendered as to a Element" } + } + p { + "Let's create a new router. First, we need an actual page to route to! Let's add a homepage component:" + } + CodeBlock { + contents: "
\n#[component]\nfn Home() -> Element {{\n    rsx! {{ h1 {{ "Welcome to the Dioxus Blog!" }} }}\n}}
\n", + name: "first_route.rs".to_string(), + } + h2 { id: "creating-routes", + Link { + to: BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::CreatingRoutes, + }, + class: "header", + "Creating Routes" + } + } + p { + "We want to use Dioxus Router to separate our application into different \"pages\"." + " " + "Dioxus Router will then determine which page to render based on the URL path." + } + p { + "To start using Dioxus Router, we need to use the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Routable.html", + code { "Routable" } + } + " macro." + } + p { + "The " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Routable.html", + code { "Routable" } + } + " macro takes an enum with all of the possible routes in our application. Each variant of the enum represents a route and must be annotated with the " + code { "#[route(path)]" } + " attribute." + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n/// An enum of all of the possible routes in the app.\n#[derive(Routable, Clone)]\nenum Route {{\n    // The home page is at the / route\n    #[route("/")]\n    Home {{}},\n}}
\n", + name: "first_route.rs".to_string(), + } + p { + "The " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Router.html", + code { "Router" } + } + " component will provide a router context for all the inner components and hooks to use. You usually will want to place this at the top of your components tree." + } + CodeBlock { + contents: "
\nfn App() -> Element {{\n    rsx! {{ Router::<Route> {{}} }}\n}}
\n", + name: "first_route.rs".to_string(), + } + p { + "If you head to your application's browser tab, you should now see the text" + code { "Welcome to Dioxus Blog!" } + " when on the root URL ( " + code { "http://localhost:8080/" } + "). If" + " " + "you enter a different path for the URL, nothing should be displayed." + } + p { + "This is because we told Dioxus Router to render the " + code { "Home" } + " component only when" + " " + "the URL path is " + code { "/" } + "." + } + h2 { id: "fallback-route", + Link { + to: BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::FallbackRoute, + }, + class: "header", + "Fallback Route" + } + } + p { + "In our example, when a route doesn't exist Dioxus Router doesn't render anything. Many sites also have a \"404\" page when a path does not exist. Let's add one to our site." + } + p { + "First, we create a new " + code { "PageNotFound" } + " component." + } + CodeBlock { + contents: "
\n#[component]\nfn PageNotFound(route: Vec<String>) -> Element {{\n    rsx! {{\n        h1 {{ "Page not found" }}\n        p {{ "We are terribly sorry, but the page you requested doesn't exist." }}\n        pre {{ color: "red", "log:\\nattemped to navigate to: {{route:?}}" }}\n    }}\n}}
\n", + name: "catch_all.rs".to_string(), + } + p { "Next, register the route in the Route enum to match if all other routes fail." } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\nenum Route {{\n    #[route("/")]\n    Home {{}},\n    // PageNotFound is a catch all route that will match any route and placing the matched segments in the route field\n    #[route("/:..route")]\n    PageNotFound {{ route: Vec<String> }},\n}}
\n", + name: "catch_all.rs".to_string(), + } + p { + "Now when you go to a route that doesn't exist, you should see the page not found" + " " + "text." + } + h2 { id: "conclusion", + Link { + to: BookRoute::RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection::Conclusion, + }, + class: "header", + "Conclusion" + } + } + p { + "In this chapter, we learned how to create a route and tell Dioxus Router what" + " " + "component to render when the URL path is " + code { "/" } + ". We also created a 404 page to" + " " + "handle when a route doesn't exist. Next, we'll create the blog portion of our" + " " + "site. We will utilize nested routes and URL parameters." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterExampleBuildingANestSection { + #[default] + Empty, + BuildingANest, + SiteNavigation, + UrlParametersAndNestedRoutes, + Conclusion, +} +impl std::str::FromStr for RouterExampleBuildingANestSection { + type Err = RouterExampleBuildingANestSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "building-a-nest" => Ok(Self::BuildingANest), + "site-navigation" => Ok(Self::SiteNavigation), + "url-parameters-and-nested-routes" => Ok(Self::UrlParametersAndNestedRoutes), + "conclusion" => Ok(Self::Conclusion), + _ => Err(RouterExampleBuildingANestSectionParseError), + } + } +} +impl std::fmt::Display for RouterExampleBuildingANestSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::BuildingANest => f.write_str("building-a-nest"), + Self::SiteNavigation => f.write_str("site-navigation"), + Self::UrlParametersAndNestedRoutes => f.write_str("url-parameters-and-nested-routes"), + Self::Conclusion => f.write_str("conclusion"), + } + } +} +#[derive(Debug)] +pub struct RouterExampleBuildingANestSectionParseError; +impl std::fmt::Display for RouterExampleBuildingANestSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterExampleBuildingANestSectionbuilding-a-nest, site-navigation, url-parameters-and-nested-routes, conclusion", + )?; + Ok(()) + } +} +impl std::error::Error for RouterExampleBuildingANestSectionParseError {} +#[component(no_case_check)] +pub fn RouterExampleBuildingANest( + section: RouterExampleBuildingANestSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "building-a-nest", + Link { + to: BookRoute::RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection::BuildingANest, + }, + class: "header", + "Building a Nest" + } + } + p { + "In this chapter, we will begin to build the blog portion of our site which will" + " " + "include links, nested routes, and route parameters." + } + h2 { id: "site-navigation", + Link { + to: BookRoute::RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection::SiteNavigation, + }, + class: "header", + "Site Navigation" + } + } + p { + "Our site visitors won't know all the available pages and blogs on our site so we" + " " + "should provide a navigation bar for them. Our navbar will be a list of links going between our pages." + } + p { + "We want our navbar component to be rendered on several different pages on our site. Instead of duplicating the code, we can create a component that wraps all children routes. This is called a layout component. To tell the router where to render the child routes, we use the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html", + code { "Outlet" } + } + " component." + } + p { + "Let's create a new " + code { "NavBar" } + " component:" + } + CodeBlock { + contents: "
\n#[component]\nfn NavBar() -> Element {{\n    rsx! {{\n        nav {{\n            ul {{ li {{ "links" }} }}\n        }}\n        // The Outlet component will render child routes (In this case just the Home component) inside the Outlet component\n        Outlet::<Route> {{}}\n    }}\n}}
\n", + name: "nested_routes.rs".to_string(), + } + p { + "Next, let's add our " + code { "NavBar" } + " component as a layout to our Route enum:" + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    // All routes under the NavBar layout will be rendered inside of the NavBar Outlet\n    #[layout(NavBar)]\n        #[route("/")]\n        Home {{}},\n    #[end_layout]\n    #[route("/:..route")]\n    PageNotFound {{ route: Vec<String> }},\n}}
\n", + name: "nested_routes.rs".to_string(), + } + p { + "To add links to our " + code { "NavBar" } + ", we could always use an HTML anchor element but that has two issues:" + } + ol { + li { "It causes a full-page reload" } + li { "We can accidentally link to a page that doesn't exist" } + } + p { + "Instead, we want to use the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html", + code { "Link" } + } + " component provided by Dioxus Router." + } + p { + "The " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html", + code { "Link" } + } + " is similar to a regular " + code { "
" } + " tag. It takes a target and children." + } + p { + "Unlike a regular " + code { "" } + " tag, we can pass in our Route enum as the target. Because we annotated our routes with the " + code { "#[route(path)]" } + " attribute, the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html", + code { "Link" } + } + " will know how to generate the correct URL. If we use the Route enum, the rust compiler will prevent us from linking to a page that doesn't exist." + } + p { "Let's add our links:" } + CodeBlock { + contents: "
\n#[component]\nfn NavBar() -> Element {{\n    rsx! {{\n        nav {{\n            ul {{\n                li {{\n                    Link {{ to: Route::Home {{}}, "Home" }}\n                }}\n            }}\n        }}\n        Outlet::<Route> {{}}\n    }}\n}}
\n", + name: "links.rs".to_string(), + } + blockquote { + p { + "Using this method, the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html", + code { "Link" } + } + " component only works for links within our" + " " + "application. To learn more about navigation targets see" + Link { + to: BookRoute::RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection::Empty, + }, + "here" + } + "." + } + } + p { + "Now you should see a list of links near the top of your page. Click on one and" + " " + "you should seamlessly travel between pages." + } + h2 { id: "url-parameters-and-nested-routes", + Link { + to: BookRoute::RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection::UrlParametersAndNestedRoutes, + }, + class: "header", + "URL Parameters and Nested Routes" + } + } + p { + "Many websites such as GitHub put parameters in their URL. For example," + code { "https://github.com/DioxusLabs" } + " utilizes the text after the domain to" + " " + "dynamically search and display content about an organization." + } + p { + "We want to store our blogs in a database and load them as needed. We also" + " " + "want our users to be able to send people a link to a specific blog post." + " " + "Instead of listing all of the blog titles at compile time, we can make a dynamic route." + } + p { + "We could utilize a search page that loads a blog when clicked but then our users" + " " + "won't be able to share our blogs easily. This is where URL parameters come in." + } + p { + "The path to our blog will look like " + code { "/blog/myBlogPage" } + ", " + code { "myBlogPage" } + " being the" + " " + "URL parameter." + } + p { + "First, let's create a layout component (similar to the navbar) that wraps the blog content. This allows us to add a heading that tells the user they are on the blog." + } + CodeBlock { + contents: "
\n#[component]\nfn Blog() -> Element {{\n    rsx! {{\n        h1 {{ "Blog" }}\n        Outlet::<Route> {{}}\n    }}\n}}
\n", + name: "dynamic_route.rs".to_string(), + } + p { + "Now we'll create another index component, that'll be displayed when no blog post" + " " + "is selected:" + } + CodeBlock { + contents: "
\n#[component]\nfn BlogList() -> Element {{\n    rsx! {{\n        h2 {{ "Choose a post" }}\n        ul {{\n            li {{\n                Link {{\n                    to: Route::BlogPost {{\n                        name: "Blog post 1".into(),\n                    }},\n                    "Read the first blog post"\n                }}\n            }}\n            li {{\n                Link {{\n                    to: Route::BlogPost {{\n                        name: "Blog post 2".into(),\n                    }},\n                    "Read the second blog post"\n                }}\n            }}\n        }}\n    }}\n}}
\n", + name: "dynamic_route.rs".to_string(), + } + p { + "We also need to create a component that displays an actual blog post. This component will accept the URL parameters as props:" + } + CodeBlock { + contents: "
\n// The name prop comes from the /:name route segment\n#[component]\nfn BlogPost(name: String) -> Element {{\n    rsx! {{ h2 {{ "Blog Post: {{name}}" }} }}\n}}
\n", + name: "dynamic_route.rs".to_string(), + } + p { "Finally, let's tell our router about those components:" } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    #[layout(NavBar)]\n        #[route("/")]\n        Home {{}},\n        #[nest("/blog")]\n            #[layout(Blog)]\n            #[route("/")]\n            BlogList {{}},\n            #[route("/post/:name")]\n            BlogPost {{ name: String }},\n            #[end_layout]\n        #[end_nest]\n    #[end_layout]\n    #[route("/:..route")]\n    PageNotFound {{\n        route: Vec<String>,\n    }},\n}}
\n", + name: "dynamic_route.rs".to_string(), + } + p { + "That's it! If you head to " + code { "/blog/1" } + " you should see our sample post." + } + h2 { id: "conclusion", + Link { + to: BookRoute::RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection::Conclusion, + }, + class: "header", + "Conclusion" + } + } + p { + "In this chapter, we utilized Dioxus Router's Link, and Route Parameter" + " " + "functionality to build the blog portion of our application. In the next chapter," + " " + "we will go over how navigation targets (like the one we passed to our links)" + " " + "work." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterExampleNavigationTargetsSection { + #[default] + Empty, + NavigationTargets, + WhatIsANavigationTarget, + ExternalNavigation, +} +impl std::str::FromStr for RouterExampleNavigationTargetsSection { + type Err = RouterExampleNavigationTargetsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "navigation-targets" => Ok(Self::NavigationTargets), + "what-is-a-navigation-target" => Ok(Self::WhatIsANavigationTarget), + "external-navigation" => Ok(Self::ExternalNavigation), + _ => Err(RouterExampleNavigationTargetsSectionParseError), + } + } +} +impl std::fmt::Display for RouterExampleNavigationTargetsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::NavigationTargets => f.write_str("navigation-targets"), + Self::WhatIsANavigationTarget => f.write_str("what-is-a-navigation-target"), + Self::ExternalNavigation => f.write_str("external-navigation"), + } + } +} +#[derive(Debug)] +pub struct RouterExampleNavigationTargetsSectionParseError; +impl std::fmt::Display for RouterExampleNavigationTargetsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterExampleNavigationTargetsSectionnavigation-targets, what-is-a-navigation-target, external-navigation", + )?; + Ok(()) + } +} +impl std::error::Error for RouterExampleNavigationTargetsSectionParseError {} +#[component(no_case_check)] +pub fn RouterExampleNavigationTargets( + section: RouterExampleNavigationTargetsSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "navigation-targets", + Link { + to: BookRoute::RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection::NavigationTargets, + }, + class: "header", + "Navigation Targets" + } + } + p { + "In the previous chapter, we learned how to create links to pages within our app." + " " + "We told them where to go using the " + code { "target" } + " property. This property takes something that can be converted to a " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html", + code { "NavigationTarget" } + } + "." + } + h2 { id: "what-is-a-navigation-target", + Link { + to: BookRoute::RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection::WhatIsANavigationTarget, + }, + class: "header", + "What is a navigation target?" + } + } + p { + "A " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html", + code { "NavigationTarget" } + } + " is similar to the " + code { "href" } + " of an HTML anchor element. It" + " " + "tells the router where to navigate to. The Dioxus Router knows two kinds of" + " " + "navigation targets:" + } + ul { + li { + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.Internal", + code { "Internal" } + } + ": We used internal links in the previous chapter. It's a link to a page within our" + " " + "app represented as a Route enum." + } + li { + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.External", + code { "External" } + } + ": This works exactly like an HTML anchors' " + code { "href" } + ". Don't use this for in-app" + " " + "navigation as it will trigger a page reload by the browser." + } + } + h2 { id: "external-navigation", + Link { + to: BookRoute::RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection::ExternalNavigation, + }, + class: "header", + "External navigation" + } + } + p { "If we need a link to an external page we can do it like this:" } + CodeBlock { + contents: "
\nfn GoToDioxus() -> Element {{\n    rsx! {{\n        Link {{ to: "https://dioxuslabs.com", "ExternalTarget target" }}\n    }}\n}}
\n", + name: "external_link.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterExampleRedirectionPerfectionSection { + #[default] + Empty, + RedirectionPerfection, + CreatingRedirects, + Conclusion, + Challenges, +} +impl std::str::FromStr for RouterExampleRedirectionPerfectionSection { + type Err = RouterExampleRedirectionPerfectionSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "redirection-perfection" => Ok(Self::RedirectionPerfection), + "creating-redirects" => Ok(Self::CreatingRedirects), + "conclusion" => Ok(Self::Conclusion), + "challenges" => Ok(Self::Challenges), + _ => Err(RouterExampleRedirectionPerfectionSectionParseError), + } + } +} +impl std::fmt::Display for RouterExampleRedirectionPerfectionSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::RedirectionPerfection => f.write_str("redirection-perfection"), + Self::CreatingRedirects => f.write_str("creating-redirects"), + Self::Conclusion => f.write_str("conclusion"), + Self::Challenges => f.write_str("challenges"), + } + } +} +#[derive(Debug)] +pub struct RouterExampleRedirectionPerfectionSectionParseError; +impl std::fmt::Display for RouterExampleRedirectionPerfectionSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterExampleRedirectionPerfectionSectionredirection-perfection, creating-redirects, conclusion, challenges", + )?; + Ok(()) + } +} +impl std::error::Error for RouterExampleRedirectionPerfectionSectionParseError {} +#[component(no_case_check)] +pub fn RouterExampleRedirectionPerfection( + section: RouterExampleRedirectionPerfectionSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "redirection-perfection", + Link { + to: BookRoute::RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection::RedirectionPerfection, + }, + class: "header", + "Redirection Perfection" + } + } + p { "You're well on your way to becoming a routing master!" } + p { "In this chapter, we will cover creating redirects" } + h2 { id: "creating-redirects", + Link { + to: BookRoute::RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection::CreatingRedirects, + }, + class: "header", + "Creating Redirects" + } + } + p { + "A redirect is very simple. When dioxus encounters a redirect while finding out" + " " + "what components to render, it will redirect the user to the target of the" + " " + "redirect." + } + p { + "As a simple example, let's say you want user to still land on your blog, even" + " " + "if they used the path " + code { "/myblog" } + " or " + code { "/myblog/:name" } + "." + } + p { + "Redirects are special attributes in the router enum that accept a route and a closure" + " " + "with the route parameters. The closure should return a route to redirect to." + } + p { "Let's add a redirect to our router enum:" } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    #[layout(NavBar)]\n        #[route("/")]\n        Home {{}},\n        #[nest("/blog")]\n            #[layout(Blog)]\n                #[route("/")]\n                BlogList {{}},\n                #[route("/post/:name")]\n                BlogPost {{ name: String }},\n            #[end_layout]\n        #[end_nest]\n    #[end_layout]\n    #[nest("/myblog")]\n        #[redirect("/", || Route::BlogList {{}})]\n        #[redirect("/:name", |name: String| Route::BlogPost {{ name }})]\n    #[end_nest]\n    #[route("/:..route")]\n    PageNotFound {{\n        route: Vec<String>,\n    }},\n}}
\n", + name: "full_example.rs".to_string(), + } + p { "That's it! Now your users will be redirected to the blog." } + h3 { id: "conclusion", + Link { + to: BookRoute::RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection::Conclusion, + }, + class: "header", + "Conclusion" + } + } + p { + "Well done! You've completed the Dioxus Router guide. You've built a small" + " " + "application and learned about the many things you can do with Dioxus Router." + " " + "To continue your journey, you attempt a challenge listed below, look at the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/packages/router/examples", + "router examples" + } + ", or" + " " + "the " + Link { to: "https://docs.rs/dioxus-router/", "API reference" } + "." + } + h3 { id: "challenges", + Link { + to: BookRoute::RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection::Challenges, + }, + class: "header", + "Challenges" + } + } + ul { + li { "Organize your components into separate files for better maintainability." } + li { "Give your app some style if you haven't already." } + li { "Build an about page so your visitors know who you are." } + li { "Add a user system that uses URL parameters." } + li { "Create a simple admin system to create, delete, and edit blogs." } + li { "If you want to go to the max, hook up your application to a rest API and database." } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterExampleFullCodeSection { + #[default] + Empty, + FullCode, +} +impl std::str::FromStr for RouterExampleFullCodeSection { + type Err = RouterExampleFullCodeSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "full-code" => Ok(Self::FullCode), + _ => Err(RouterExampleFullCodeSectionParseError), + } + } +} +impl std::fmt::Display for RouterExampleFullCodeSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::FullCode => f.write_str("full-code"), + } + } +} +#[derive(Debug)] +pub struct RouterExampleFullCodeSectionParseError; +impl std::fmt::Display for RouterExampleFullCodeSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of RouterExampleFullCodeSectionfull-code")?; + Ok(()) + } +} +impl std::error::Error for RouterExampleFullCodeSectionParseError {} +#[component(no_case_check)] +pub fn RouterExampleFullCode(section: RouterExampleFullCodeSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "full-code", + Link { + to: BookRoute::RouterExampleFullCode { + section: RouterExampleFullCodeSection::FullCode, + }, + class: "header", + "Full Code" + } + } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n// ANCHOR: router\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    #[layout(NavBar)]\n        #[route("/")]\n        Home {{}},\n        #[nest("/blog")]\n            #[layout(Blog)]\n                #[route("/")]\n                BlogList {{}},\n                #[route("/post/:name")]\n                BlogPost {{ name: String }},\n            #[end_layout]\n        #[end_nest]\n    #[end_layout]\n    #[nest("/myblog")]\n        #[redirect("/", || Route::BlogList {{}})]\n        #[redirect("/:name", |name: String| Route::BlogPost {{ name }})]\n    #[end_nest]\n    #[route("/:..route")]\n    PageNotFound {{\n        route: Vec<String>,\n    }},\n}}\n// ANCHOR_END: router\n\npub fn App() -> Element {{\n    rsx! {{ Router::<Route> {{}} }}\n}}\n\n#[component]\nfn NavBar() -> Element {{\n    rsx! {{\n        nav {{\n            ul {{\n                li {{\n                    Link {{ to: Route::Home {{}}, "Home" }}\n                }}\n                li {{\n                    Link {{ to: Route::BlogList {{}}, "Blog" }}\n                }}\n            }}\n        }}\n        Outlet::<Route> {{}}\n    }}\n}}\n\n#[component]\nfn Home() -> Element {{\n    rsx! {{ h1 {{ "Welcome to the Dioxus Blog!" }} }}\n}}\n\n#[component]\nfn Blog() -> Element {{\n    rsx! {{\n        h1 {{ "Blog" }}\n        Outlet::<Route> {{}}\n    }}\n}}\n\n#[component]\nfn BlogList() -> Element {{\n    rsx! {{\n        h2 {{ "Choose a post" }}\n        ul {{\n            li {{\n                Link {{\n                    to: Route::BlogPost {{\n                        name: "Blog post 1".into(),\n                    }},\n                    "Read the first blog post"\n                }}\n            }}\n            li {{\n                Link {{\n                    to: Route::BlogPost {{\n                        name: "Blog post 2".into(),\n                    }},\n                    "Read the second blog post"\n                }}\n            }}\n        }}\n    }}\n}}\n\n#[component]\nfn BlogPost(name: String) -> Element {{\n    rsx! {{ h2 {{ "Blog Post: {{name}}" }} }}\n}}\n\n#[component]\nfn PageNotFound(route: Vec<String>) -> Element {{\n    rsx! {{\n        h1 {{ "Page not found" }}\n        p {{ "We are terribly sorry, but the page you requested doesn't exist." }}\n        pre {{ color: "red", "log:\\nattemped to navigate to: {{route:?}}" }}\n    }}\n}}
\n", + name: "full_example.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceIndexSection { + #[default] + Empty, + AddingTheRouterToYourApplication, +} +impl std::str::FromStr for RouterReferenceIndexSection { + type Err = RouterReferenceIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "adding-the-router-to-your-application" => Ok(Self::AddingTheRouterToYourApplication), + _ => Err(RouterReferenceIndexSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::AddingTheRouterToYourApplication => { + f.write_str("adding-the-router-to-your-application") + } + } + } +} +#[derive(Debug)] +pub struct RouterReferenceIndexSectionParseError; +impl std::fmt::Display for RouterReferenceIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceIndexSectionadding-the-router-to-your-application", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceIndexSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceIndex(section: RouterReferenceIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "adding-the-router-to-your-application", + Link { + to: BookRoute::RouterReferenceIndex { + section: RouterReferenceIndexSection::AddingTheRouterToYourApplication, + }, + class: "header", + "Adding the router to your application" + } + } + p { + "In this chapter, we will learn how to add the router to our app. By itself, this" + " " + "is not very useful. However, it is a prerequisite for all the functionality" + " " + "described in the other chapters." + } + blockquote { + p { + "Make sure you added the " + code { "dioxus-router" } + " dependency as explained in the" + Link { + to: BookRoute::RouterIndex { + section: RouterIndexSection::Empty, + }, + "introduction" + } + "." + } + } + p { + "In most cases, we want to add the router to the root component of our app. This" + " " + "way, we can ensure that we have access to all its functionality everywhere." + } + p { "First, we define the router with the router macro:" } + CodeBlock { + contents: "
\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n/// An enum of all of the possible routes in the app.\n#[derive(Routable, Clone)]\nenum Route {{\n    // The home page is at the / route\n    #[route("/")]\n    Home {{}},\n}}
\n", + name: "first_route.rs".to_string(), + } + p { + "Then we render the router with the " + "[ " + code { "Router" } + "]" + " component." + } + CodeBlock { + contents: "
\nfn App() -> Element {{\n    rsx! {{ Router::<Route> {{}} }}\n}}
\n", + name: "first_route.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceRoutesIndexSection { + #[default] + Empty, + DefiningRoutes, + RouteSegments, + StaticSegments, + DynamicSegments, + CatchAllSegments, + QuerySegments, +} +impl std::str::FromStr for RouterReferenceRoutesIndexSection { + type Err = RouterReferenceRoutesIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "defining-routes" => Ok(Self::DefiningRoutes), + "route-segments" => Ok(Self::RouteSegments), + "static-segments" => Ok(Self::StaticSegments), + "dynamic-segments" => Ok(Self::DynamicSegments), + "catch-all-segments" => Ok(Self::CatchAllSegments), + "query-segments" => Ok(Self::QuerySegments), + _ => Err(RouterReferenceRoutesIndexSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceRoutesIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::DefiningRoutes => f.write_str("defining-routes"), + Self::RouteSegments => f.write_str("route-segments"), + Self::StaticSegments => f.write_str("static-segments"), + Self::DynamicSegments => f.write_str("dynamic-segments"), + Self::CatchAllSegments => f.write_str("catch-all-segments"), + Self::QuerySegments => f.write_str("query-segments"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceRoutesIndexSectionParseError; +impl std::fmt::Display for RouterReferenceRoutesIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceRoutesIndexSectiondefining-routes, route-segments, static-segments, dynamic-segments, catch-all-segments, query-segments", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceRoutesIndexSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceRoutesIndex( + section: RouterReferenceRoutesIndexSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "defining-routes", + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::DefiningRoutes, + }, + class: "header", + "Defining Routes" + } + } + p { + "When creating a " + "[ " + code { "Routable" } + "]" + " enum, we can define routes for our application using the " + code { "route(\"path\")" } + " attribute." + } + h2 { id: "route-segments", + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::RouteSegments, + }, + class: "header", + "Route Segments" + } + } + p { + "Each route is made up of segments. Most segments are separated by " + code { "/" } + " characters in the path." + } + p { "There are four fundamental types of segments:" } + ol { + li { + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::StaticSegments, + }, + "Static segments" + } + " are fixed strings that must be present in the path." + } + li { + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::DynamicSegments, + }, + "Dynamic segments" + } + " are types that can be parsed from a segment." + } + li { + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::CatchAllSegments, + }, + "Catch-all segments" + } + " are types that can be parsed from multiple segments." + } + li { + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::QuerySegments, + }, + "Query segments" + } + " are types that can be parsed from the query string." + } + } + p { "Routes are matched:" } + ul { + li { + "First, from most specific to least specific (Static then Dynamic then Catch All) (Query is always matched)" + } + li { + "Then, if multiple routes match the same path, the order in which they are defined in the enum is followed." + } + } + h2 { id: "static-segments", + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::StaticSegments, + }, + class: "header", + "Static segments" + } + } + p { + "Fixed routes match a specific path. For example, the route " + code { "#[route(\"/about\")]" } + " will match the path " + code { "/about" } + "." + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    // Routes always start with a slash\n    #[route("/")]\n    Home {{}},\n    // You can have multiple segments in a route\n    #[route("/hello/world")]\n    HelloWorld {{}},\n}}\n\n#[component]\nfn Home() -> Element {{\n    todo!()\n}}\n\n#[component]\nfn HelloWorld() -> Element {{\n    todo!()\n}}
\n", + name: "static_segments.rs".to_string(), + } + h2 { id: "dynamic-segments", + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::DynamicSegments, + }, + class: "header", + "Dynamic Segments" + } + } + p { + "Dynamic segments are in the form of " + code { ":name" } + " where " + code { "name" } + " is" + " " + "the name of the field in the route variant. If the segment is parsed" + " " + "successfully then the route matches, otherwise the matching continues." + } + p { + "The segment can be of any type that implements " + code { "FromStr" } + "." + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    // segments that start with : are dynamic segments\n    #[route("/post/:name")]\n    BlogPost {{\n        // You must include dynamic segments in child variants\n        name: String,\n    }},\n    #[route("/document/:id")]\n    Document {{\n        // You can use any type that implements FromStr\n        // If the segment can't be parsed, the route will not match\n        id: usize,\n    }},\n}}\n\n// Components must contain the same dynamic segments as their corresponding variant\n#[component]\nfn BlogPost(name: String) -> Element {{\n    todo!()\n}}\n\n#[component]\nfn Document(id: usize) -> Element {{\n    todo!()\n}}
\n", + name: "dynamic_segments.rs".to_string(), + } + h2 { id: "catch-all-segments", + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::CatchAllSegments, + }, + class: "header", + "Catch All Segments" + } + } + p { + "Catch All segments are in the form of " + code { ":..name" } + " where " + code { "name" } + " is the name of the field in the route variant. If the segments are parsed successfully then the route matches, otherwise the matching continues." + } + p { + "The segment can be of any type that implements " + code { "FromSegments" } + ". (Vec" + p { class: "inline-html-block", dangerous_inner_html: "" } + " implements this by default)" + } + p { + "Catch All segments must be the " + em { "last route segment" } + " in the path (query segments are not counted) and cannot be included in nests." + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    // segments that start with :.. are catch all segments\n    #[route("/blog/:..segments")]\n    BlogPost {{\n        // You must include catch all segment in child variants\n        segments: Vec<String>,\n    }},\n}}\n\n// Components must contain the same catch all segments as their corresponding variant\n#[component]\nfn BlogPost(segments: Vec<String>) -> Element {{\n    todo!()\n}}
\n", + name: "catch_all_segments.rs".to_string(), + } + h2 { id: "query-segments", + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::QuerySegments, + }, + class: "header", + "Query Segments" + } + } + p { + "Query segments are in the form of " + code { "?:name&:othername" } + " where " + code { "name" } + " and " + code { "othername" } + " are the names of fields in the route variant." + } + p { + "Unlike " + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::DynamicSegments, + }, + "Dynamic Segments" + } + " and " + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::CatchAllSegments, + }, + "Catch All Segments" + } + ", parsing a Query segment must not fail." + } + p { + "The segment can be of any type that implements " + code { "FromQueryArgument" } + "." + } + p { + "Query segments must be the " + em { "after all route segments" } + " and cannot be included in nests." + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    // segments that start with ?: are query segments\n    #[route("/blog?:name&:surname")]\n    BlogPost {{\n        // You must include query segments in child variants\n        name: String,\n        surname: String,\n    }},\n}}\n\n#[component]\nfn BlogPost(name: String, surname: String) -> Element {{\n    rsx! {{\n        div {{ "This is your blogpost with a query segment:" }}\n        div {{ "Name: {{name}}" }}\n        div {{ "Surname: {{surname}}" }}\n    }}\n}}\n\nfn App() -> Element {{\n    rsx! {{ Router::<Route> {{}} }}\n}}\n\nfn main() {{}}
\n", + name: "query_segments.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceRoutesNestedSection { + #[default] + Empty, + NestedRoutes, + Nesting, +} +impl std::str::FromStr for RouterReferenceRoutesNestedSection { + type Err = RouterReferenceRoutesNestedSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "nested-routes" => Ok(Self::NestedRoutes), + "nesting" => Ok(Self::Nesting), + _ => Err(RouterReferenceRoutesNestedSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceRoutesNestedSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::NestedRoutes => f.write_str("nested-routes"), + Self::Nesting => f.write_str("nesting"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceRoutesNestedSectionParseError; +impl std::fmt::Display for RouterReferenceRoutesNestedSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceRoutesNestedSectionnested-routes, nesting", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceRoutesNestedSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceRoutesNested( + section: RouterReferenceRoutesNestedSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "nested-routes", + Link { + to: BookRoute::RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection::NestedRoutes, + }, + class: "header", + "Nested Routes" + } + } + p { + "When developing bigger applications we often want to nest routes within each" + " " + "other. As an example, we might want to organize a settings menu using this" + " " + "pattern:" + } + CodeBlock { contents: "
\n└ Settings\n  ├ General Settings (displayed when opening the settings)\n  ├ Change Password\n  └ Privacy Settings
\n" } + p { "We might want to map this structure to these paths and components:" } + CodeBlock { contents: "
\n/settings\t\t  -> Settings {{ GeneralSettings }}\n/settings/password -> Settings {{ PWSettings }}\n/settings/privacy  -> Settings {{ PrivacySettings }}
\n" } + p { "Nested routes allow us to do this without repeating /settings in every route." } + h2 { id: "nesting", + Link { + to: BookRoute::RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection::Nesting, + }, + class: "header", + "Nesting" + } + } + p { + "To nest routes, we use the " + code { "#[nest(\"path\")]" } + " and " + code { "#[end_nest]" } + " attributes." + } + p { "The path in nest must not:" } + ol { + li { + "Contain a " + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::CatchAllSegments, + }, + "Catch All Segment" + } + } + li { + "Contain a " + Link { + to: BookRoute::RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection::QuerySegments, + }, + "Query Segment" + } + } + } + p { + "If you define a dynamic segment in a nest, it will be available to all child routes and layouts." + } + p { + "To finish a nest, we use the " + code { "#[end_nest]" } + " attribute or the end of the enum." + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n// Skipping formatting allows you to indent nests\n#[rustfmt::skip]\nenum Route {{\n    // Start the /blog nest\n    #[nest("/blog")]\n        // You can nest as many times as you want\n        #[nest("/:id")]\n            #[route("/post")]\n            PostId {{\n                // You must include parent dynamic segments in child variants\n                id: usize,\n            }},\n        // End nests manually with #[end_nest]\n        #[end_nest]\n        #[route("/:id")]\n        // The absolute route of BlogPost is /blog/:name\n        BlogPost {{\n            id: usize,\n        }},\n    // Or nests are ended automatically at the end of the enum\n}}\n\n#[component]\nfn BlogPost(id: usize) -> Element {{\n    todo!()\n}}\n\n#[component]\nfn PostId(id: usize) -> Element {{\n    todo!()\n}}
\n", + name: "nest.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceLayoutsSection { + #[default] + Empty, + Layouts, + LayoutsWithDynamicSegments, +} +impl std::str::FromStr for RouterReferenceLayoutsSection { + type Err = RouterReferenceLayoutsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "layouts" => Ok(Self::Layouts), + "layouts-with-dynamic-segments" => Ok(Self::LayoutsWithDynamicSegments), + _ => Err(RouterReferenceLayoutsSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceLayoutsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Layouts => f.write_str("layouts"), + Self::LayoutsWithDynamicSegments => f.write_str("layouts-with-dynamic-segments"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceLayoutsSectionParseError; +impl std::fmt::Display for RouterReferenceLayoutsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceLayoutsSectionlayouts, layouts-with-dynamic-segments", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceLayoutsSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceLayouts(section: RouterReferenceLayoutsSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "layouts", + Link { + to: BookRoute::RouterReferenceLayouts { + section: RouterReferenceLayoutsSection::Layouts, + }, + class: "header", + "Layouts" + } + } + p { + "Layouts allow you to wrap all child routes in a component. This can be useful when creating something like a header that will be used in many different routes." + } + p { + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html", + code { "Outlet" } + } + " tells the router where to render content in layouts. In the following example," + " " + "the Index will be rendered within the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html", + code { "Outlet" } + } + "." + } + p { + "This page is built with the Dioxus. It uses Layouts in several different places. Here is an outline of how layouts are used on the current page. Hover over different layouts to see what elements they are on the page." + } + LayoutsExplanation {} + p { "Here is a more complete example of a layout wrapping the body of a page." } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    #[layout(Wrapper)]\n        #[route("/")]\n        Index {{}},\n}}\n\n#[component]\nfn Wrapper() -> Element {{\n    rsx! {{\n        header {{ "header" }}\n        // The index route will be rendered here\n        Outlet::<Route> {{}}\n        footer {{ "footer" }}\n    }}\n}}\n\n#[component]\nfn Index() -> Element {{\n    rsx! {{ h1 {{ "Index" }} }}\n}}
\n", + name: "outlet.rs".to_string(), + } + p { + "The example above will output the following HTML (line breaks added for" + " " + "readability):" + } + CodeBlock { + contents: "
\n<header>header</header>\n<h1>Index</h1>\n<footer>footer</footer>
\n", + } + h2 { id: "layouts-with-dynamic-segments", + Link { + to: BookRoute::RouterReferenceLayouts { + section: RouterReferenceLayoutsSection::LayoutsWithDynamicSegments, + }, + class: "header", + "Layouts with dynamic segments" + } + } + p { + "You can combine layouts with " + Link { + to: BookRoute::RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection::Empty, + }, + "nested routes" + } + " to create dynamic layouts with content that changes based on the current route." + } + p { + "Just like routes, layouts components must accept a prop for each dynamic segment in the route. For example, if you have a route with a dynamic segment like " + code { "/:name" } + ", your layout component must accept a " + code { "name" } + " prop:" + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    #[nest("/:name")]\n        #[layout(Wrapper)]\n            #[route("/")]\n            Index {{\n                name: String,\n            }},\n}}\n\n#[component]\nfn Wrapper(name: String) -> Element {{\n    rsx! {{\n        header {{ "Welcome {{name}}!" }}\n        // The index route will be rendered here\n        Outlet::<Route> {{}}\n        footer {{ "footer" }}\n    }}\n}}\n\n#[component]\nfn Index(name: String) -> Element {{\n    rsx! {{ h1 {{ "This is a homepage for {{name}}" }} }}\n}}
\n", + name: "outlet.rs".to_string(), + } + p { + "Or to get the full route, you can use the " + code { "use_route" } + " hook." + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {{\n    #[layout(Wrapper)]\n        #[route("/:name")]\n        Index {{\n            name: String,\n        }},\n}}\n\n#[component]\nfn Wrapper() -> Element {{\n    let full_route = use_route::<Route>();\n    rsx! {{\n        header {{ "Welcome to {{full_route}}!" }}\n        // The index route will be rendered here\n        Outlet::<Route> {{}}\n        footer {{ "footer" }}\n    }}\n}}\n\n#[component]\nfn Index(name: String) -> Element {{\n    rsx! {{ h1 {{ "This is a homepage for {{name}}" }} }}\n}}
\n", + name: "outlet.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceNavigationIndexSection { + #[default] + Empty, + LinksNavigation, +} +impl std::str::FromStr for RouterReferenceNavigationIndexSection { + type Err = RouterReferenceNavigationIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "links--navigation" => Ok(Self::LinksNavigation), + _ => Err(RouterReferenceNavigationIndexSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceNavigationIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::LinksNavigation => f.write_str("links--navigation"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceNavigationIndexSectionParseError; +impl std::fmt::Display for RouterReferenceNavigationIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceNavigationIndexSectionlinks--navigation", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceNavigationIndexSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceNavigationIndex( + section: RouterReferenceNavigationIndexSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "links--navigation", + Link { + to: BookRoute::RouterReferenceNavigationIndex { + section: RouterReferenceNavigationIndexSection::LinksNavigation, + }, + class: "header", + "Links & Navigation" + } + } + p { + "When we split our app into pages, we need to provide our users with a way to" + " " + "navigate between them. On regular web pages, we'd use an anchor element for that," + " " + "like this:" + } + CodeBlock { contents: "
\n<a href="/other">Link to an other page</a>
\n" } + p { "However, we cannot do that when using the router for three reasons:" } + ol { + li { + "Anchor tags make the browser load a new page from the server. This takes a" + " " + "lot of time, and it is much faster to let the router handle the navigation" + " " + "client-side." + } + li { + "Navigation using anchor tags only works when the app is running inside a" + " " + "browser. This means we cannot use them inside apps using Dioxus Desktop." + } + li { + "Anchor tags cannot check if the target page exists. This means we cannot" + " " + "prevent accidentally linking to non-existent pages." + } + } + p { + "To solve these problems, the router provides us with a " + "[ " + code { "Link" } + "]" + " component we can" + " " + "use like this:" + } + CodeBlock { + contents: "
\n#[component]\nfn NavBar() -> Element {{\n    rsx! {{\n        nav {{\n            ul {{\n                li {{\n                    Link {{ to: Route::Home {{}}, "Home" }}\n                }}\n            }}\n        }}\n        Outlet::<Route> {{}}\n    }}\n}}
\n", + name: "links.rs".to_string(), + } + p { + "The " + code { "target" } + " in the example above is similar to the " + code { "href" } + " of a regular anchor" + " " + "element. However, it tells the router more about what kind of navigation it" + " " + "should perform. It accepts something that can be converted into a" + " " + "[ " + code { "NavigationTarget" } + "]" + " " + ":" + } + ul { + li { + "The example uses a Internal route. This is the most common type of navigation." + " " + "It tells the router to navigate to a page within our app by passing a variant of a " + "[" + code { "Routable" } + "]" + " enum. This type of navigation can never fail if the link component is used inside a router component." + } + li { + "[" + code { "External" } + "]" + " allows us to navigate to URLs outside of our app. This is useful" + " " + "for links to external websites. NavigationTarget::External accepts an URL to navigate to. This type of navigation can fail if the URL is invalid." + } + } + blockquote { + p { + "The " + "[ " + code { "Link" } + "]" + " accepts several props that modify its behavior. See the API docs" + " " + "for more details." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceNavigationProgrammaticSection { + #[default] + Empty, + ProgrammaticNavigation, + UsingANavigator, + ExternalNavigationTargets, +} +impl std::str::FromStr for RouterReferenceNavigationProgrammaticSection { + type Err = RouterReferenceNavigationProgrammaticSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "programmatic-navigation" => Ok(Self::ProgrammaticNavigation), + "using-a-navigator" => Ok(Self::UsingANavigator), + "external-navigation-targets" => Ok(Self::ExternalNavigationTargets), + _ => Err(RouterReferenceNavigationProgrammaticSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceNavigationProgrammaticSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ProgrammaticNavigation => f.write_str("programmatic-navigation"), + Self::UsingANavigator => f.write_str("using-a-navigator"), + Self::ExternalNavigationTargets => f.write_str("external-navigation-targets"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceNavigationProgrammaticSectionParseError; +impl std::fmt::Display for RouterReferenceNavigationProgrammaticSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceNavigationProgrammaticSectionprogrammatic-navigation, using-a-navigator, external-navigation-targets", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceNavigationProgrammaticSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceNavigationProgrammatic( + section: RouterReferenceNavigationProgrammaticSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "programmatic-navigation", + Link { + to: BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::ProgrammaticNavigation, + }, + class: "header", + "Programmatic Navigation" + } + } + p { + "Sometimes we want our application to navigate to another page without having the" + " " + "user click on a link. This is called programmatic navigation." + } + h2 { id: "using-a-navigator", + Link { + to: BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::UsingANavigator, + }, + class: "header", + "Using a Navigator" + } + } + p { + "We can get a navigator with the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/prelude/struct.Navigator.html", + code { "navigator" } + } + " function which returns a " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/prelude/struct.Navigator.html", + code { "Navigator" } + } + "." + } + p { + "We can use the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/prelude/struct.Navigator.html", + code { "Navigator" } + } + " to trigger four different kinds of navigation:" + } + ul { + li { + code { "push" } + " will navigate to the target. It works like a regular anchor tag." + } + li { + code { "replace" } + " works like " + code { "push" } + ", except that it replaces the current history entry" + " " + "instead of adding a new one. This means the prior page cannot be restored with the browser's back button." + } + li { + code { "Go back" } + " works like the browser's back button." + } + li { + code { "Go forward" } + " works like the browser's forward button." + } + } + CodeBlock { + contents: "
\n#[component]\nfn Home() -> Element {{\n    let nav = navigator();\n\n    // push\n    nav.push(Route::PageNotFound {{ route: vec![] }});\n\n    // replace\n    nav.replace(Route::Home {{}});\n\n    // go back\n    nav.go_back();\n\n    // go forward\n    nav.go_forward();\n\n    rsx! {{ h1 {{ "Welcome to the Dioxus Blog!" }} }}\n}}
\n", + name: "navigator.rs".to_string(), + } + p { + "You might have noticed that, like " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html", + code { "Link" } + } + ", the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/prelude/struct.Navigator.html", + code { "Navigator" } + } + "s " + code { "push" } + " and" + code { "replace" } + " functions take a " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html", + code { "NavigationTarget" } + } + ". This means we can use either" + code { "Internal" } + ", or " + code { "External" } + " targets." + } + h2 { id: "external-navigation-targets", + Link { + to: BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::ExternalNavigationTargets, + }, + class: "header", + "External Navigation Targets" + } + } + p { + "Unlike a " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html", + code { "Link" } + } + ", the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/prelude/struct.Navigator.html", + code { "Navigator" } + } + " cannot rely on the browser (or webview) to" + " " + "handle navigation to external targets via a generated anchor element." + } + p { + "This means, that under certain conditions, navigation to external targets can" + " " + "fail." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceHistoryProvidersSection { + #[default] + Empty, + HistoryProviders, +} +impl std::str::FromStr for RouterReferenceHistoryProvidersSection { + type Err = RouterReferenceHistoryProvidersSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "history-providers" => Ok(Self::HistoryProviders), + _ => Err(RouterReferenceHistoryProvidersSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceHistoryProvidersSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::HistoryProviders => f.write_str("history-providers"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceHistoryProvidersSectionParseError; +impl std::fmt::Display for RouterReferenceHistoryProvidersSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceHistoryProvidersSectionhistory-providers", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceHistoryProvidersSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceHistoryProviders( + section: RouterReferenceHistoryProvidersSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "history-providers", + Link { + to: BookRoute::RouterReferenceHistoryProviders { + section: RouterReferenceHistoryProvidersSection::HistoryProviders, + }, + class: "header", + "History Providers" + } + } + p { + "[ " + code { "HistoryProvider" } + "]" + " " + "s are used by the router to keep track of the navigation history" + " " + "and update any external state (e.g. the browser's URL)." + } + p { + "The router provides two " + "[ " + code { "HistoryProvider" } + "]" + " " + "s, but you can also create your own." + " " + "The two default implementations are:" + } + ul { + li { + "The " + "[" + code { "MemoryHistory" } + "]" + " is a custom implementation that works in memory." + } + li { + "The " + "[" + code { "LiveviewHistory" } + "]" + " is a custom implementation that works with the liveview renderer." + } + li { + "The " + "[" + code { "WebHistory" } + "]" + " integrates with the browser's URL." + } + } + p { + "By default, the router uses the " + "[ " + code { "MemoryHistory" } + "]" + " " + ". It might be changed to use" + " " + "[ " + code { "WebHistory" } + "]" + " when the " + code { "web" } + " feature is active, but that is not guaranteed." + } + p { "You can override the default history:" } + CodeBlock { + contents: "
\n#[component]\nfn App() -> Element {{\n    rsx! {{Router::<Route> {{ config: || RouterConfig::default().history(WebHistory::default()) }}}}\n}}
\n", + name: "history_provider.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceHistoryButtonsSection { + #[default] + Empty, + HistoryButtons, +} +impl std::str::FromStr for RouterReferenceHistoryButtonsSection { + type Err = RouterReferenceHistoryButtonsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "history-buttons" => Ok(Self::HistoryButtons), + _ => Err(RouterReferenceHistoryButtonsSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceHistoryButtonsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::HistoryButtons => f.write_str("history-buttons"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceHistoryButtonsSectionParseError; +impl std::fmt::Display for RouterReferenceHistoryButtonsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceHistoryButtonsSectionhistory-buttons", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceHistoryButtonsSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceHistoryButtons( + section: RouterReferenceHistoryButtonsSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "history-buttons", + Link { + to: BookRoute::RouterReferenceHistoryButtons { + section: RouterReferenceHistoryButtonsSection::HistoryButtons, + }, + class: "header", + "History Buttons" + } + } + p { + "Some platforms, like web browsers, provide users with an easy way to navigate" + " " + "through an app's history. They have UI elements or integrate with the OS." + } + p { + "However, native platforms usually don't provide such amenities, which means that" + " " + "apps wanting users to have access to them, need to implement them. For this" + " " + "reason, the router comes with two components, which emulate a browser's back and" + " " + "forward buttons:" + } + ul { + li { + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoBackButton.html", + code { "GoBackButton" } + } + } + li { + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoForwardButton.html", + code { "GoForwardButton" } + } + } + } + blockquote { + p { + "If you want to navigate through the history programmatically, take a look at" + Link { + to: BookRoute::RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection::Empty, + }, + code { "programmatic navigation" } + } + "." + } + } + CodeBlock { + contents: "
\nfn HistoryNavigation() -> Element {{\n    rsx! {{\n        GoBackButton {{ "Back to the Past" }}\n        GoForwardButton {{ "Back to the Future" }}\n    }}\n}}
\n", + name: "history_buttons.rs".to_string(), + } + p { + "As you might know, browsers usually disable the back and forward buttons if" + " " + "there is no history to navigate to. The router's history buttons try to do that" + " " + "too, but depending on the " + "[" + " " + "history provider" + " " + "]" + " that might not be possible." + } + p { + "Importantly, neither " + code { "WebHistory" } + " supports that feature." + " " + "This is due to limitations of the browser History API." + } + p { + "However, in both cases, the router will just ignore button presses, if there is" + " " + "no history to navigate to." + } + p { + "Also, when using " + code { "WebHistory" } + ", the history buttons might" + " " + "navigate a user to a history entry outside your app." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum RouterReferenceRoutingUpdateCallbackSection { + #[default] + Empty, + RoutingUpdateCallback, + HowDoesTheCallbackBehave, + CodeExample, +} +impl std::str::FromStr for RouterReferenceRoutingUpdateCallbackSection { + type Err = RouterReferenceRoutingUpdateCallbackSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "routing-update-callback" => Ok(Self::RoutingUpdateCallback), + "how-does-the-callback-behave" => Ok(Self::HowDoesTheCallbackBehave), + "code-example" => Ok(Self::CodeExample), + _ => Err(RouterReferenceRoutingUpdateCallbackSectionParseError), + } + } +} +impl std::fmt::Display for RouterReferenceRoutingUpdateCallbackSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::RoutingUpdateCallback => f.write_str("routing-update-callback"), + Self::HowDoesTheCallbackBehave => f.write_str("how-does-the-callback-behave"), + Self::CodeExample => f.write_str("code-example"), + } + } +} +#[derive(Debug)] +pub struct RouterReferenceRoutingUpdateCallbackSectionParseError; +impl std::fmt::Display for RouterReferenceRoutingUpdateCallbackSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of RouterReferenceRoutingUpdateCallbackSectionrouting-update-callback, how-does-the-callback-behave, code-example", + )?; + Ok(()) + } +} +impl std::error::Error for RouterReferenceRoutingUpdateCallbackSectionParseError {} +#[component(no_case_check)] +pub fn RouterReferenceRoutingUpdateCallback( + section: RouterReferenceRoutingUpdateCallbackSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "routing-update-callback", + Link { + to: BookRoute::RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection::RoutingUpdateCallback, + }, + class: "header", + "Routing Update Callback" + } + } + p { + "In some cases, we might want to run custom code when the current route changes. For this reason, the " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/prelude/struct.RouterConfig.html", + code { "RouterConfig" } + } + " exposes an " + code { "on_update" } + " field." + } + h2 { id: "how-does-the-callback-behave", + Link { + to: BookRoute::RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection::HowDoesTheCallbackBehave, + }, + class: "header", + "How does the callback behave?" + } + } + p { + "The " + code { "on_update" } + " is called whenever the current routing information changes. It is called after the router updated its internal state, but before dependent components and hooks are updated." + } + p { + "If the callback returns a " + Link { to: "https://docs.rs/dioxus-router/latest/dioxus_router/navigation/enum.NavigationTarget.html", + code { "NavigationTarget" } + } + ", the router will replace the current location with the specified target. It will not call the " + code { "on_update" } + " again." + } + p { + "If at any point the router encounters a navigation failure, it will go to the appropriate state without calling the " + code { "on_update" } + ". It doesn't matter if the invalid target initiated the navigation, was found as a redirect target, or was returned by the " + code { "on_update" } + " itself." + } + h2 { id: "code-example", + Link { + to: BookRoute::RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection::CodeExample, + }, + class: "header", + "Code Example" + } + } + CodeBlock { + contents: "
\n#[derive(Routable, Clone, PartialEq)]\nenum Route {{\n    #[route("/")]\n    Index {{}},\n    #[route("/home")]\n    Home {{}},\n}}\n\n#[component]\nfn Home() -> Element {{\n    rsx! {{ p {{ "Home" }} }}\n}}\n\n#[component]\nfn Index() -> Element {{\n    rsx! {{ p {{ "Index" }} }}\n}}\n\nfn app() -> Element {{\n    rsx! {{\n        Router::<Route> {{\n            config: || {{\n                RouterConfig::default()\n                    .on_update(|state| {{\n                        (state.current() == Route::Index {{}})\n                            .then_some(NavigationTarget::Internal(Route::Home {{}}))\n                    }})\n            }}\n        }}\n    }}\n}}
\n", + name: "routing_update.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookIndexSection { + #[default] + Empty, + Cookbook, +} +impl std::str::FromStr for CookbookIndexSection { + type Err = CookbookIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "cookbook" => Ok(Self::Cookbook), + _ => Err(CookbookIndexSectionParseError), + } + } +} +impl std::fmt::Display for CookbookIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Cookbook => f.write_str("cookbook"), + } + } +} +#[derive(Debug)] +pub struct CookbookIndexSectionParseError; +impl std::fmt::Display for CookbookIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of CookbookIndexSectioncookbook")?; + Ok(()) + } +} +impl std::error::Error for CookbookIndexSectionParseError {} +#[component(no_case_check)] +pub fn CookbookIndex(section: CookbookIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "cookbook", + Link { + to: BookRoute::CookbookIndex { + section: CookbookIndexSection::Cookbook, + }, + class: "header", + "Cookbook" + } + } + p { "The cookbook contains common recipes for different patterns within Dioxus." } + p { "There are a few different sections in the cookbook:" } + ul { + li { + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::Empty, + }, + "Publishing" + } + " will teach you how to present your app in a variety of delicious forms." + } + li { + "Explore the " + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::Empty, + }, + "Anti-patterns" + } + " section to discover what ingredients to avoid when preparing your application." + } + li { + "Within " + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::Empty, + }, + "Error Handling" + } + ", we'll master the fine art of managing spoiled ingredients in Dioxus." + } + li { + "Take a culinary journey through " + Link { + to: BookRoute::CookbookStateIndex { + section: CookbookStateIndexSection::Empty, + }, + "State management" + } + ", where we'll explore the world of handling local, global, and external state in Dioxus." + } + li { + Link { + to: BookRoute::CookbookIntegrationsIndex { + section: CookbookIntegrationsIndexSection::Empty, + }, + "Integrations" + } + " will guide you how to seamlessly blend external libraries into your Dioxus culinary creations." + } + li { + Link { + to: BookRoute::CookbookTesting { + section: CookbookTestingSection::Empty, + }, + "Testing" + } + " explains how to examine the unique flavor of Dioxus-specific features, like components." + } + li { + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Empty, + }, + "Tailwind" + } + " reveals the secrets of combining your Tailwind and Dioxus ingredients into a complete meal. You will also learn about using other NPM ingredients (packages) with Dioxus." + } + li { + "In the " + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Empty, + }, + "Custom Renderer" + } + " section, we embark on a cooking adventure, inventing new ways to cook with Dioxus!" + } + li { + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Empty, + }, + "Optimizing" + } + " will show you how to maximize the quality of your ingredients." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookPublishingSection { + #[default] + Empty, + Publishing, + WebPublishingWithGithubPages, + DesktopCreatingAnInstaller, + PreparingYourApplicationForBundling, + AddingAssetsToYourApplication, + InstallDioxusCli, + Building, +} +impl std::str::FromStr for CookbookPublishingSection { + type Err = CookbookPublishingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "publishing" => Ok(Self::Publishing), + "web-publishing-with-github-pages" => Ok(Self::WebPublishingWithGithubPages), + "desktop-creating-an-installer" => Ok(Self::DesktopCreatingAnInstaller), + "preparing-your-application-for-bundling" => { + Ok(Self::PreparingYourApplicationForBundling) + } + "adding-assets-to-your-application" => Ok(Self::AddingAssetsToYourApplication), + "install-dioxus-cli" => Ok(Self::InstallDioxusCli), + "building" => Ok(Self::Building), + _ => Err(CookbookPublishingSectionParseError), + } + } +} +impl std::fmt::Display for CookbookPublishingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Publishing => f.write_str("publishing"), + Self::WebPublishingWithGithubPages => f.write_str("web-publishing-with-github-pages"), + Self::DesktopCreatingAnInstaller => f.write_str("desktop-creating-an-installer"), + Self::PreparingYourApplicationForBundling => { + f.write_str("preparing-your-application-for-bundling") + } + Self::AddingAssetsToYourApplication => f.write_str("adding-assets-to-your-application"), + Self::InstallDioxusCli => f.write_str("install-dioxus-cli"), + Self::Building => f.write_str("building"), + } + } +} +#[derive(Debug)] +pub struct CookbookPublishingSectionParseError; +impl std::fmt::Display for CookbookPublishingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookPublishingSectionpublishing, web-publishing-with-github-pages, desktop-creating-an-installer, preparing-your-application-for-bundling, adding-assets-to-your-application, install-dioxus-cli, building", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookPublishingSectionParseError {} +#[component(no_case_check)] +pub fn CookbookPublishing(section: CookbookPublishingSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "publishing", + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::Publishing, + }, + class: "header", + "Publishing" + } + } + p { + "After you have build your application, you will need to publish it somewhere. This reference will outline different methods of publishing your desktop or web application." + } + h2 { id: "web-publishing-with-github-pages", + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::WebPublishingWithGithubPages, + }, + class: "header", + "Web: Publishing with GitHub Pages" + } + } + p { + "Edit your " + code { "Dioxus.toml" } + " to point your " + code { "out_dir" } + " to the " + code { "docs" } + " folder and the " + code { "base_path" } + " to the name of your repo:" + } + CodeBlock { contents: "
\n[application]\n# ...\nout_dir = "docs"\n\n[web.app]\nbase_path = "your_repo"
\n" } + p { "Then build your app and publish it to Github:" } + ul { + li { + "Make sure GitHub Pages is set up for your repo to publish any static files in the docs directory" + } + li { "Build your app with:" } + } + CodeBlock { contents: "
\ndx build --release
\n" } + ul { + li { + "Make a copy of your " + code { "docs/index.html" } + " file and rename the copy to " + code { "docs/404.html" } + " so that your app will work with client-side routing" + } + li { "Add and commit with git" } + li { "Push to GitHub" } + } + h2 { id: "desktop-creating-an-installer", + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::DesktopCreatingAnInstaller, + }, + class: "header", + "Desktop: Creating an installer" + } + } + p { + "Dioxus desktop app uses your operating system's WebView library, so it's portable to be distributed for other platforms." + } + p { "In this section, we'll cover how to bundle your app for macOS, Windows, and Linux." } + h2 { id: "preparing-your-application-for-bundling", + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::PreparingYourApplicationForBundling, + }, + class: "header", + "Preparing your application for bundling" + } + } + p { + "Depending on your platform, you may need to add some additional code to your " + code { "main.rs" } + " file to make sure your app is ready for bundling. On Windows, you'll need to add the " + code { "#![windows_subsystem = \"windows\"]" } + " attribute to your " + code { "main.rs" } + " file to hide the terminal window that pops up when you run your app. " + strong { "If you're developing on Windows, only use this when bundling." } + " It will disable the terminal, so you will not get logs of any kind. You can gate it behind a feature, like so:" + } + CodeBlock { contents: "
\n# Cargo.toml\n[features]\nbundle = []
\n" } + p { + "And then your " + code { "main.rs" } + ":" + } + CodeBlock { contents: "
\n#![cfg_attr(feature = "bundle", windows_subsystem = "windows")]
\n" } + h2 { id: "adding-assets-to-your-application", + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::AddingAssetsToYourApplication, + }, + class: "header", + "Adding assets to your application" + } + } + p { + "If you want to bundle assets with your application, you can either use them with the " + code { "manganis" } + " crate (covered more in the " + Link { + to: BookRoute::ReferenceAssets { + section: ReferenceAssetsSection::Empty, + }, + "assets" + } + " page), or you can include them in your " + code { "Dioxus.toml" } + " file:" + } + CodeBlock { contents: "
\n[bundle]\n# The list of files to include in the bundle. These can contain globs.\nresources = ["main.css", "header.svg", "**/*.png"]
\n" } + h2 { id: "install-dioxus-cli", + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::InstallDioxusCli, + }, + class: "header", + "Install dioxus CLI" + } + } + p { + "The first thing we'll do is install the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/packages/cli", + "dioxus-cli" + } + ". This extension to cargo will make it very easy to package our app for the various platforms." + } + p { "To install, simply run" } + p { + code { "cargo install dioxus-cli" } + } + h2 { id: "building", + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::Building, + }, + class: "header", + "Building" + } + } + p { + "To bundle your application you can simply run " + code { "dx bundle --release" } + " (also add " + code { "--features bundle" } + " if you're using that, see the " + Link { + to: BookRoute::CookbookPublishing { + section: CookbookPublishingSection::PreparingYourApplicationForBundling, + }, + "this" + } + " for more) to produce a final app with all the optimizations and assets builtin." + } + p { + "Once you've ran the command, your app should be accessible in " + code { "dist/bundle/" } + "." + } + p { "For example, a macOS app would look like this:" } + p { + img { + src: asset!("/assets/static/publish.png", ImageAssetOptions::new().with_webp()), + alt: "Published App", + title: "", + } + } + p { + "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." + } + blockquote { + p { + "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." + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookAntipatternsSection { + #[default] + Empty, + Antipatterns, + UnnecessarilyNestedFragments, + IncorrectIteratorKeys, + AvoidInteriorMutabilityInProps, + AvoidUpdatingStateDuringRender, + AvoidLargeGroupsOfState, + RunningNonDeterministicCodeInTheBodyOfAComponent, + OverlyPermissivePartialeqForProps, +} +impl std::str::FromStr for CookbookAntipatternsSection { + type Err = CookbookAntipatternsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "antipatterns" => Ok(Self::Antipatterns), + "unnecessarily-nested-fragments" => Ok(Self::UnnecessarilyNestedFragments), + "incorrect-iterator-keys" => Ok(Self::IncorrectIteratorKeys), + "avoid-interior-mutability-in-props" => Ok(Self::AvoidInteriorMutabilityInProps), + "avoid-updating-state-during-render" => Ok(Self::AvoidUpdatingStateDuringRender), + "avoid-large-groups-of-state" => Ok(Self::AvoidLargeGroupsOfState), + "running-non-deterministic-code-in-the-body-of-a-component" => { + Ok(Self::RunningNonDeterministicCodeInTheBodyOfAComponent) + } + "overly-permissive-partialeq-for-props" => Ok(Self::OverlyPermissivePartialeqForProps), + _ => Err(CookbookAntipatternsSectionParseError), + } + } +} +impl std::fmt::Display for CookbookAntipatternsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Antipatterns => f.write_str("antipatterns"), + Self::UnnecessarilyNestedFragments => f.write_str("unnecessarily-nested-fragments"), + Self::IncorrectIteratorKeys => f.write_str("incorrect-iterator-keys"), + Self::AvoidInteriorMutabilityInProps => { + f.write_str("avoid-interior-mutability-in-props") + } + Self::AvoidUpdatingStateDuringRender => { + f.write_str("avoid-updating-state-during-render") + } + Self::AvoidLargeGroupsOfState => f.write_str("avoid-large-groups-of-state"), + Self::RunningNonDeterministicCodeInTheBodyOfAComponent => { + f.write_str("running-non-deterministic-code-in-the-body-of-a-component") + } + Self::OverlyPermissivePartialeqForProps => { + f.write_str("overly-permissive-partialeq-for-props") + } + } + } +} +#[derive(Debug)] +pub struct CookbookAntipatternsSectionParseError; +impl std::fmt::Display for CookbookAntipatternsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookAntipatternsSectionantipatterns, unnecessarily-nested-fragments, incorrect-iterator-keys, avoid-interior-mutability-in-props, avoid-updating-state-during-render, avoid-large-groups-of-state, running-non-deterministic-code-in-the-body-of-a-component, overly-permissive-partialeq-for-props", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookAntipatternsSectionParseError {} +#[component(no_case_check)] +pub fn CookbookAntipatterns(section: CookbookAntipatternsSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "antipatterns", + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::Antipatterns, + }, + class: "header", + "Antipatterns" + } + } + p { + "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 for performance or code re-usability reasons." + } + h2 { id: "unnecessarily-nested-fragments", + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::UnnecessarilyNestedFragments, + }, + class: "header", + "Unnecessarily Nested Fragments" + } + } + p { + "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." + } + p { + "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." + } + CodeBlock { + contents: "
\n// ❌ Don't unnecessarily nest fragments\nlet _ = rsx! {{\n    Fragment {{\n        Fragment {{\n            Fragment {{\n                Fragment {{\n                    Fragment {{ div {{ "Finally have a real node!" }} }}\n                }}\n            }}\n        }}\n    }}\n}};\n\n// ✅ Render shallow structures\nrsx! {{ div {{ "Finally have a real node!" }} }}
\n", + name: "anti_patterns.rs".to_string(), + } + h2 { id: "incorrect-iterator-keys", + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::IncorrectIteratorKeys, + }, + class: "header", + "Incorrect Iterator Keys" + } + } + p { + "As described in the " + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::TheKeyAttribute, + }, + "dynamic 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 will never change." + } + CodeBlock { + contents: "
\nlet data: &HashMap<_, _> = &props.data;\n\n// ❌ No keys\nrsx! {{\n    ul {{\n        for value in data.values() {{\n            li {{ "List item: {{value}}" }}\n        }}\n    }}\n}};\n\n// ❌ Using index as keys\nrsx! {{\n    ul {{\n        for (index , value) in data.values().enumerate() {{\n            li {{ key: "{{index}}", "List item: {{value}}" }}\n        }}\n    }}\n}};\n\n// ✅ Using unique IDs as keys:\nrsx! {{\n    ul {{\n        for (key , value) in props.data.iter() {{\n            li {{ key: "{{key}}", "List item: {{value}}" }}\n        }}\n    }}\n}}
\n", + name: "anti_patterns.rs".to_string(), + } + h2 { id: "avoid-interior-mutability-in-props", + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::AvoidInteriorMutabilityInProps, + }, + class: "header", + "Avoid Interior Mutability in Props" + } + } + p { + "While it is technically acceptable to have a " + code { "Mutex" } + " or a " + code { "RwLock" } + " in the props, they will be difficult to use." + } + p { + "Suppose you have a struct " + code { "User" } + " containing the field " + code { "username: String" } + ". If you pass a " + code { "Mutex" } + " prop to a " + code { "UserComponent" } + " component, that component may wish to write to the " + code { "username" } + " field. However, when it does, the parent component will not be aware of the change, and the component will not re-render which causes the UI to be out of sync with the state. Instead, consider passing down a reactive value like a " + code { "Signal" } + " or immutable data." + } + CodeBlock { + contents: "
\n// ❌ Mutex/RwLock/RefCell in props\n#[derive(Props, Clone)]\nstruct AntipatternInteriorMutability {{\n    map: Rc<RefCell<HashMap<u32, String>>>,\n}}\n\nimpl PartialEq for AntipatternInteriorMutability {{\n    fn eq(&self, other: &Self) -> bool {{\n        std::rc::Rc::ptr_eq(&self.map, &other.map)\n    }}\n}}\n\nfn AntipatternInteriorMutability(map: Rc<RefCell<HashMap<u32, String>>>) -> Element {{\n    rsx! {{\n        button {{\n            onclick: {{\n                let map = map.clone();\n                move |_| {{\n                    // Writing to map will not rerun any components\n                    map.borrow_mut().insert(0, "Hello".to_string());\n                }}\n            }},\n            "Mutate map"\n        }}\n        // Since writing to map will not rerun any components, this will get out of date\n        "{{map.borrow().get(&0).unwrap()}}"\n    }}\n}}\n\n// ✅ Use a signal to pass mutable state\n#[component]\nfn AntipatternInteriorMutabilitySignal(map: Signal<HashMap<u32, String>>) -> Element {{\n    rsx! {{\n        button {{\n            onclick: move |_| {{\n                // Writing to map will rerun any components that read the map\n                map.write().insert(0, "Hello".to_string());\n            }},\n            "Mutate map"\n        }}\n        // Since writing to map will rerun subscribers, this will get updated\n        "{{map.read().get(&0).unwrap()}}"\n    }}\n}}
\n", + name: "anti_patterns.rs".to_string(), + } + h2 { id: "avoid-updating-state-during-render", + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::AvoidUpdatingStateDuringRender, + }, + class: "header", + "Avoid Updating State During Render" + } + } + p { + "Every time you update the state, Dioxus needs to re-render the component – this is inefficient! Consider refactoring your code to avoid this." + } + p { + "Also, if you unconditionally update the state during render, it will be re-rendered in an infinite loop." + } + CodeBlock { + contents: "
\n// ❌ Updating state in render\nlet first_signal = use_signal(|| 0);\nlet mut second_signal = use_signal(|| 0);\n\n// Updating the state during a render can easily lead to infinite loops\nif first_signal() + 1 != second_signal() {{\n    second_signal.set(first_signal() + 1);\n}}\n\n// ✅ Update state in an effect\nlet first_signal = use_signal(|| 0);\nlet mut second_signal = use_signal(|| 0);\n\n// The closure you pass to use_effect will be rerun whenever any of the dependencies change without re-rendering the component\nuse_effect(move || {{\n    if first_signal() + 1 != second_signal() {{\n        second_signal.set(first_signal() + 1);\n    }}\n}});\n\n// ✅ Deriving state with use_memo\nlet first_signal = use_signal(|| 0);\n// Memos are specifically designed for derived state. If your state fits this pattern, use it.\nlet second_signal = use_memo(move || first_signal() + 1);
\n", + name: "anti_patterns.rs".to_string(), + } + h2 { id: "avoid-large-groups-of-state", + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::AvoidLargeGroupsOfState, + }, + class: "header", + "Avoid Large Groups of State" + } + } + p { + "It can be tempting to have a single large state struct that contains all of your application's state. However, this can lead to issues:" + } + ul { + li { "It can be easy to accidentally mutate the state in a way that causes an infinite loop" } + li { "It can be difficult to reason about when and how the state is updated" } + li { + "It can lead to performance issues because many components will need to re-render when the state changes" + } + } + p { + "Instead, consider breaking your state into smaller, more manageable pieces. This will make it easier to reason about the state, avoid update loops, and improve performance." + } + CodeBlock { + contents: "
\nfn app() -> Element {{\n    // ❌ Large state struct\n    #[derive(Props, Clone, PartialEq)]\n    struct LargeState {{\n        users: Vec<User>,\n        logged_in: bool,\n        warnings: Vec<String>,\n    }}\n\n    #[derive(Props, Clone, PartialEq)]\n    struct User {{\n        name: String,\n        email: String,\n    }}\n\n    let mut all_my_state = use_signal(|| LargeState {{\n        users: vec![User {{\n            name: "Alice".to_string(),\n            email: "alice@example.com".to_string(),\n        }}],\n        logged_in: true,\n        warnings: vec![],\n    }});\n\n    use_effect(move || {{\n        // It is very easy to accidentally read and write to the state object if it contains all your state\n        let read = all_my_state.read();\n        let logged_in = read.logged_in;\n        if !logged_in {{\n            all_my_state\n                .write_unchecked()\n                .warnings\n                .push("You are not logged in".to_string());\n        }}\n    }});\n\n    // ✅ Use multiple signals to manage state\n    let users = use_signal(|| {{\n        vec![User {{\n            name: "Alice".to_string(),\n            email: "alice@example.com".to_string(),\n        }}]\n    }});\n    let logged_in = use_signal(|| true);\n    let mut warnings = use_signal(|| vec![]);\n\n    use_effect(move || {{\n        // Now you can read and write to separate signals which will not cause issues\n        if !logged_in() {{\n            warnings.write().push("You are not logged in".to_string());\n        }}\n    }});\n\n    // ✅ Use memos to create derived state when larger states are unavoidable\n    // Notice we didn't split everything into separate signals. Users still make sense as a vec of data\n    let users = use_signal(|| {{\n        vec![User {{\n            name: "Alice".to_string(),\n            email: "alice@example.com".to_string(),\n        }}]\n    }});\n    let logged_in = use_signal(|| true);\n    let warnings: Signal<Vec<String>> = use_signal(|| vec![]);\n\n    // In child components, you can use the memo to create derived that will only update when a specific part of the state changes\n    // This will help you avoid unnecessary re-renders and infinite loops\n    #[component]\n    fn FirstUser(users: Signal<Vec<User>>) -> Element {{\n        let first_user = use_memo(move || users.read().first().unwrap().clone());\n\n        rsx! {{\n            div {{\n                "First user: {{first_user().name}}"\n            }}\n        }}\n    }}\n\n    rsx! {{\n        FirstUser {{\n            users\n        }}\n    }}\n}}
\n", + name: "anti_patterns.rs".to_string(), + } + h2 { id: "running-non-deterministic-code-in-the-body-of-a-component", + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::RunningNonDeterministicCodeInTheBodyOfAComponent, + }, + class: "header", + "Running Non-Deterministic Code in the Body of a Component" + } + } + p { + "If you have a component that contains non-deterministic code, that code should generally not be run in the body of the component. If it is put in the body of the component, it will be executed every time the component is re-rendered which can lead to performance issues." + } + p { + "Instead, consider moving the non-deterministic code into a hook that only runs when the component is first created or an effect that reruns when dependencies change." + } + CodeBlock { + contents: "
\n// ❌ Non-deterministic code in the body of a component\n#[component]\nfn NonDeterministic(name: String) -> Element {{\n    let my_random_id = rand::random::<u64>();\n\n    rsx! {{\n        div {{\n            // Id will change every single time the component is re-rendered\n            id: "{{my_random_id}}",\n            "Hello {{name}}"\n        }}\n    }}\n}}\n\n// ✅ Use a hook to run non-deterministic code\nfn NonDeterministicHook(name: String) -> Element {{\n    // If you store the result of the non-deterministic code in a hook, it will stay the same between renders\n    let my_random_id = use_hook(|| rand::random::<u64>());\n\n    rsx! {{\n        div {{\n            id: "{{my_random_id}}",\n            "Hello {{name}}"\n        }}\n    }}\n}}
\n", + name: "anti_patterns.rs".to_string(), + } + h2 { id: "overly-permissive-partialeq-for-props", + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::OverlyPermissivePartialeqForProps, + }, + class: "header", + "Overly Permissive PartialEq for Props" + } + } + p { + "You may have noticed that " + code { "Props" } + " requires a " + code { "PartialEq" } + " implementation. That " + code { "PartialEq" } + " is very important for Dioxus to work correctly. It is used to determine if a component should re-render or not when the parent component re-renders." + } + p { + "If you cannot derive " + code { "PartialEq" } + " for your " + code { "Props" } + ", you will need to implement it yourself. If you do implement " + code { "PartialEq" } + ", make sure to return " + code { "false" } + " any time the props change in a way that would cause the UI in the child component to change." + } + p { + "In general, returning " + code { "false" } + " from " + code { "PartialEq" } + " if you aren't sure if the props have changed or not is better than returning " + code { "true" } + ". This will help you avoid out of date UI in your child components." + } + CodeBlock { + contents: "
\n// ❌ Permissive PartialEq for Props\n#[derive(Props, Clone)]\nstruct PermissivePartialEqProps {{\n    name: String,\n}}\n\n// This will cause the component to **never** re-render when the parent component re-renders\nimpl PartialEq for PermissivePartialEqProps {{\n    fn eq(&self, _: &Self) -> bool {{\n        true\n    }}\n}}\n\nfn PermissivePartialEq(name: PermissivePartialEqProps) -> Element {{\n    rsx! {{\n        div {{\n            "Hello {{name.name}}"\n        }}\n    }}\n}}\n\n#[component]\nfn PermissivePartialEqParent() -> Element {{\n    let name = use_signal(|| "Alice".to_string());\n\n    rsx! {{\n        PermissivePartialEq {{\n            // The PermissivePartialEq component will not get the updated value of name because the PartialEq implementation says that the props are the same\n            name: name()\n        }}\n    }}\n}}\n\n// ✅ Derive PartialEq for Props\n#[derive(Props, Clone, PartialEq)]\nstruct DerivePartialEqProps {{\n    name: String,\n}}\n\nfn DerivePartialEq(name: DerivePartialEqProps) -> Element {{\n    rsx! {{\n        div {{\n            "Hello {{name.name}}"\n        }}\n    }}\n}}\n\n#[component]\nfn DerivePartialEqParent() -> Element {{\n    let name = use_signal(|| "Alice".to_string());\n\n    rsx! {{\n        DerivePartialEq {{\n            name: name()\n        }}\n    }}\n}}\n\n// ✅ Return false from PartialEq if you are unsure if the props have changed\n#[derive(Debug)]\nstruct NonPartialEq;\n\n#[derive(Props, Clone)]\nstruct RcPartialEqProps {{\n    name: Rc<NonPartialEq>,\n}}\n\nimpl PartialEq for RcPartialEqProps {{\n    fn eq(&self, other: &Self) -> bool {{\n        // This will almost always return false because the Rc will likely point to a different value\n        // Implementing PartialEq for NonPartialEq would be better, but if it is controlled by another library, it may not be possible\n        // **Always** return false if you are unsure if the props have changed\n        std::rc::Rc::ptr_eq(&self.name, &other.name)\n    }}\n}}\n\nfn RcPartialEq(name: RcPartialEqProps) -> Element {{\n    rsx! {{\n        div {{\n            "Hello {{name.name:?}}"\n        }}\n    }}\n}}\n\nfn RcPartialEqParent() -> Element {{\n    let name = use_signal(|| Rc::new(NonPartialEq));\n\n    rsx! {{\n        RcPartialEq {{\n            // Generally, RcPartialEq will rerun even if the value of name hasn't actually changed because the Rc will point to a different value\n            name: name()\n        }}\n    }}\n}}
\n", + name: "anti_patterns.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookErrorHandlingSection { + #[default] + Empty, + ErrorHandling, + TheSimplestReturningNone, + EarlyReturnOnResult, + MatchResults, + PassingErrorStatesThroughComponents, + ThrowingErrors, +} +impl std::str::FromStr for CookbookErrorHandlingSection { + type Err = CookbookErrorHandlingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "error-handling" => Ok(Self::ErrorHandling), + "the-simplest--returning-none" => Ok(Self::TheSimplestReturningNone), + "early-return-on-result" => Ok(Self::EarlyReturnOnResult), + "match-results" => Ok(Self::MatchResults), + "passing-error-states-through-components" => { + Ok(Self::PassingErrorStatesThroughComponents) + } + "throwing-errors" => Ok(Self::ThrowingErrors), + _ => Err(CookbookErrorHandlingSectionParseError), + } + } +} +impl std::fmt::Display for CookbookErrorHandlingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ErrorHandling => f.write_str("error-handling"), + Self::TheSimplestReturningNone => f.write_str("the-simplest--returning-none"), + Self::EarlyReturnOnResult => f.write_str("early-return-on-result"), + Self::MatchResults => f.write_str("match-results"), + Self::PassingErrorStatesThroughComponents => { + f.write_str("passing-error-states-through-components") + } + Self::ThrowingErrors => f.write_str("throwing-errors"), + } + } +} +#[derive(Debug)] +pub struct CookbookErrorHandlingSectionParseError; +impl std::fmt::Display for CookbookErrorHandlingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookErrorHandlingSectionerror-handling, the-simplest--returning-none, early-return-on-result, match-results, passing-error-states-through-components, throwing-errors", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookErrorHandlingSectionParseError {} +#[component(no_case_check)] +pub fn CookbookErrorHandling(section: CookbookErrorHandlingSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "error-handling", + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::ErrorHandling, + }, + class: "header", + "Error handling" + } + } + p { + "A selling point of Rust for web development is the reliability of always knowing where errors can occur and being forced to handle them" + } + p { + "However, we haven't talked about error handling at all in this guide! In this chapter, we'll cover some strategies in handling errors to ensure your app never crashes." + } + h2 { id: "the-simplest--returning-none", + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::TheSimplestReturningNone, + }, + class: "header", + "The simplest – returning None" + } + } + p { + "Astute observers might have noticed that " + code { "Element" } + " is actually a type alias for " + code { "Option" } + ". You don't need to know what a " + code { "VNode" } + " is, but it's important to recognize that we could actually return nothing at all:" + } + CodeBlock { + contents: "
\nfn App() -> Element {{\n    None\n}}
\n", + name: "error_handling.rs".to_string(), + } + p { + "This lets us add in some syntactic sugar for operations we think " + em { "shouldn't" } + " fail, but we're still not confident enough to \"unwrap\" on." + } + blockquote { + p { + "The nature of " + code { "Option" } + " might change in the future as the " + code { "try" } + " trait gets upgraded." + } + } + CodeBlock { + contents: "
\nfn App() -> Element {{\n    // immediately return "None"\n    let name = use_hook(|| Some("hi"))?;\n\n    todo!()\n}}
\n", + name: "error_handling.rs".to_string(), + } + h2 { id: "early-return-on-result", + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::EarlyReturnOnResult, + }, + class: "header", + "Early return on result" + } + } + p { + "Because Rust can't accept both Options and Results with the existing try infrastructure, you'll need to manually handle Results. This can be done by converting them into Options or by explicitly handling them. If you choose to convert your Result into an Option and bubble it with a " + code { "?" } + ", keep in mind that if you do hit an error you will lose error information and nothing will be rendered for that component." + } + CodeBlock { + contents: "
\nfn App() -> Element {{\n    // Convert Result to Option\n    let name: i32 = use_hook(|| "1.234").parse().ok()?;\n\n    // Early return\n    let count = use_hook(|| "1.234");\n    let val: i32 = match count.parse() {{\n        Ok(val) => val,\n        Err(err) => return rsx! {{"Parsing failed"}},\n    }};\n\n    todo!()\n}}
\n", + name: "error_handling.rs".to_string(), + } + p { + "Notice that while hooks in Dioxus do not like being called in conditionals or loops, they " + em { "are" } + " okay with early returns. Returning an error state early is a completely valid way of handling errors." + } + h2 { id: "match-results", + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::MatchResults, + }, + class: "header", + "Match results" + } + } + p { + "The next \"best\" way of handling errors in Dioxus is to match on the error locally. This is the most robust way of handling errors, but it doesn't scale to architectures beyond a single component." + } + p { "To do this, we simply have an error state built into our component:" } + CodeBlock { + contents: "
\nlet mut error = use_signal(|| None);
\n", + name: "error_handling.rs".to_string(), + } + p { + "Whenever we perform an action that generates an error, we'll set that error state. We can then match on the error in a number of ways (early return, return Element, etc)." + } + CodeBlock { + contents: "
\nfn Commandline() -> Element {{\n    let mut error = use_signal(|| None);\n\n    match error() {{\n        Some(error) => rsx! {{ h1 {{ "An error occurred" }} }},\n        None => rsx! {{ input {{ oninput: move |_| error.set(Some("bad thing happened!")) }} }},\n    }}\n}}
\n", + name: "error_handling.rs".to_string(), + } + h2 { id: "passing-error-states-through-components", + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::PassingErrorStatesThroughComponents, + }, + class: "header", + "Passing error states through components" + } + } + p { + "If you're dealing with a handful of components with minimal nesting, you can just pass the error handle into child components." + } + CodeBlock { + contents: "
\nfn Commandline() -> Element {{\n    let error = use_signal(|| None);\n\n    if let Some(error) = error() {{\n        return rsx! {{"An error occurred"}};\n    }}\n\n    rsx! {{\n        Child {{ error }}\n        Child {{ error }}\n        Child {{ error }}\n        Child {{ error }}\n    }}\n}}\n\n#[component]\nfn Child(error: Signal<Option<&'static str>>) -> Element {{\n    rsx! {{ input {{ oninput: move |_| error.set(Some("bad thing happened!")) }} }}\n}}
\n", + name: "error_handling.rs".to_string(), + } + p { + "Much like before, our child components can manually set the error during their own actions. The advantage to this pattern is that we can easily isolate error states to a few components at a time, making our app more predictable and robust." + } + h2 { id: "throwing-errors", + Link { + to: BookRoute::CookbookErrorHandling { + section: CookbookErrorHandlingSection::ThrowingErrors, + }, + class: "header", + "Throwing errors" + } + } + p { + "Dioxus provides a much easier way to handle errors: throwing them. Throwing errors combines the best parts of an error state and early return: you can easily throw and error with " + code { "?" } + ", but you keep information about the error so that you can handle it in a parent component." + } + p { + "You can call " + code { "throw" } + " on any " + code { "Result" } + " type that implements " + code { "Debug" } + " to turn it into an error state and then use " + code { "?" } + " to return early if you do hit an error. You can capture the error state with an " + code { "ErrorBoundary" } + " component that will render the a different component if an error is thrown in any of its children." + } + CodeBlock { + contents: "
\nfn Parent() -> Element {{\n    rsx! {{\n        ErrorBoundary {{\n            handle_error: |error| {{\n                rsx! {{\n                    "Oops, we encountered an error. Please report {{error}} to the developer of this application"\n                }}\n            }},\n            ThrowsError {{}}\n        }}\n    }}\n}}\n\nfn ThrowsError() -> Element {{\n    let name: i32 = use_hook(|| "1.234").parse().throw()?;\n\n    todo!()\n}}
\n", + name: "error_handling.rs".to_string(), + } + p { + "You can even nest " + code { "ErrorBoundary" } + " components to capture errors at different levels of your app." + } + CodeBlock { + contents: "
\nfn App() -> Element {{\n    rsx! {{\n        ErrorBoundary {{\n            handle_error: |error| {{\n                rsx! {{\n                    "Hmm, something went wrong. Please report {{error}} to the developer of this application"\n                }}\n            }},\n            Parent {{}}\n        }}\n    }}\n}}\n\nfn Parent() -> Element {{\n    rsx! {{\n        ErrorBoundary {{\n            handle_error: |error| {{\n                rsx! {{\n                    "The child component encountered an error: {{error}}"\n                }}\n            }},\n            ThrowsError {{}}\n        }}\n    }}\n}}\n\nfn ThrowsError() -> Element {{\n    let name: i32 = use_hook(|| "1.234").parse().throw()?;\n\n    todo!()\n}}
\n", + name: "error_handling.rs".to_string(), + } + p { + "This pattern is particularly helpful whenever your code generates a non-recoverable error. You can gracefully capture these \"global\" error states without panicking or handling state for each error yourself." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookIntegrationsIndexSection { + #[default] + Empty, +} +impl std::str::FromStr for CookbookIntegrationsIndexSection { + type Err = CookbookIntegrationsIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + _ => Err(CookbookIntegrationsIndexSectionParseError), + } + } +} +impl std::fmt::Display for CookbookIntegrationsIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + } + } +} +#[derive(Debug)] +pub struct CookbookIntegrationsIndexSectionParseError; +impl std::fmt::Display for CookbookIntegrationsIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of CookbookIntegrationsIndexSection")?; + Ok(()) + } +} +impl std::error::Error for CookbookIntegrationsIndexSectionParseError {} +#[component(no_case_check)] +pub fn CookbookIntegrationsIndex( + section: CookbookIntegrationsIndexSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + p { + "This section of the guide provides getting started guides for common tools used with Dioxus." + } + ul { + li { + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Empty, + }, + "Logging" + } + } + li { + Link { + to: BookRoute::CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection::Empty, + }, + "Internationalization" + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookIntegrationsLoggingSection { + #[default] + Empty, + Logging, + TheTracingCrate, + DioxusLogger, + PlatformIntricacies, + FinalNotes, + DesktopAndServer, + Web, + Mobile, + Android, + ViewingAndroidLogs, + Ios, + ViewingIosLogs, +} +impl std::str::FromStr for CookbookIntegrationsLoggingSection { + type Err = CookbookIntegrationsLoggingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "logging" => Ok(Self::Logging), + "the-tracing-crate" => Ok(Self::TheTracingCrate), + "dioxus-logger" => Ok(Self::DioxusLogger), + "platform-intricacies" => Ok(Self::PlatformIntricacies), + "final-notes" => Ok(Self::FinalNotes), + "desktop-and-server" => Ok(Self::DesktopAndServer), + "web" => Ok(Self::Web), + "mobile" => Ok(Self::Mobile), + "android" => Ok(Self::Android), + "viewing-android-logs" => Ok(Self::ViewingAndroidLogs), + "ios" => Ok(Self::Ios), + "viewing-ios-logs" => Ok(Self::ViewingIosLogs), + _ => Err(CookbookIntegrationsLoggingSectionParseError), + } + } +} +impl std::fmt::Display for CookbookIntegrationsLoggingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Logging => f.write_str("logging"), + Self::TheTracingCrate => f.write_str("the-tracing-crate"), + Self::DioxusLogger => f.write_str("dioxus-logger"), + Self::PlatformIntricacies => f.write_str("platform-intricacies"), + Self::FinalNotes => f.write_str("final-notes"), + Self::DesktopAndServer => f.write_str("desktop-and-server"), + Self::Web => f.write_str("web"), + Self::Mobile => f.write_str("mobile"), + Self::Android => f.write_str("android"), + Self::ViewingAndroidLogs => f.write_str("viewing-android-logs"), + Self::Ios => f.write_str("ios"), + Self::ViewingIosLogs => f.write_str("viewing-ios-logs"), + } + } +} +#[derive(Debug)] +pub struct CookbookIntegrationsLoggingSectionParseError; +impl std::fmt::Display for CookbookIntegrationsLoggingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookIntegrationsLoggingSectionlogging, the-tracing-crate, dioxus-logger, platform-intricacies, final-notes, desktop-and-server, web, mobile, android, viewing-android-logs, ios, viewing-ios-logs", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookIntegrationsLoggingSectionParseError {} +#[component(no_case_check)] +pub fn CookbookIntegrationsLogging( + section: CookbookIntegrationsLoggingSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "logging", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Logging, + }, + class: "header", + "Logging" + } + } + p { + "Dioxus has a wide range of supported platforms, each with their own logging requirements. We'll discuss the different options available for your projects." + } + h4 { id: "the-tracing-crate", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::TheTracingCrate, + }, + class: "header", + "The Tracing Crate" + } + } + p { + "The " + Link { to: "https://crates.io/crates/tracing", "Tracing" } + " crate is the logging interface that the Dioxus library uses. It is not required to use the Tracing crate, but you will not recieve logs from the Dioxus library." + } + p { + "The Tracing crate provides a variety of simple " + code { "println" } + "-like macros with varying levels of severity." + " " + "The available macros are as follows with the highest severity on the bottom:" + } + CodeBlock { + contents: "
\nfn main() {{\n    tracing::trace!("trace");\n    tracing::debug!("debug");\n    tracing::info!("info");\n    tracing::warn!("warn");\n    tracing::error!("error");\n}}
\n", + } + p { + "All the loggers provided on this page are, besides configuration and initialization, interfaced using these macros. Often you will also utilize the Tracing crate's " + code { "Level" } + " enum. This enum usually represents the maximum log severity you want your application to emit and can be loaded from a variety of sources such as configuration file, environment variable, and more." + } + p { + "For more information, visit the Tracing crate's " + Link { to: "https://docs.rs/tracing/latest/tracing/", "docs" } + "." + } + h2 { id: "dioxus-logger", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::DioxusLogger, + }, + class: "header", + "Dioxus Logger" + } + } + p { + Link { to: "https://crates.io/crates/dioxus-logger", "Dioxus Logger" } + " is a logging utility that will start the appropriate logger for the platform. Currently every platform except mobile is supported." + } + p { + "To use Dioxus Logger, call the " + code { "init()" } + " function:" + } + CodeBlock { contents: "
\nuse tracing::Level;\n\nfn main() {{\n    // Init logger\n    dioxus_logger::init(Level::INFO).expect("failed to init logger");\n    // Dioxus launch code\n}}
\n" } + p { + "The " + code { "dioxus_logger::init()" } + " function initializes Dioxus Logger with the appropriate tracing logger using the default configuration and provided " + code { "Level" } + "." + } + h4 { id: "platform-intricacies", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::PlatformIntricacies, + }, + class: "header", + "Platform Intricacies" + } + } + p { + "On web, Dioxus Logger will use " + Link { to: "https://crates.io/crates/tracing-wasm", "tracing-wasm" } + ". On Desktop and server-based targets, Dioxus Logger will use " + Link { to: "https://crates.io/crates/tracing-subscriber", "tracing-subscriber" } + "'s " + code { "FmtSubscriber" } + "." + } + h4 { id: "final-notes", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::FinalNotes, + }, + class: "header", + "Final Notes" + } + } + p { + "Dioxus Logger is the preferred logger to use with Dioxus if it suites your needs. There are more features to come and Dioxus Logger is planned to become an integral part of Dioxus. If there are any feature suggestions or issues with Dioxus Logger, feel free to reach out on the " + Link { to: "https://discord.gg/XgGxMSkvUM", "Dioxus Discord Server" } + "!" + } + p { + "For more information, visit Dioxus Logger's " + Link { to: "https://docs.rs/dioxus-logger/latest/dioxus_logger/", "docs" } + "." + } + h2 { id: "desktop-and-server", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::DesktopAndServer, + }, + class: "header", + "Desktop and Server" + } + } + p { "For Dioxus' desktop and server targets, you can generally use the logger of your choice." } + p { "Some popular options are:" } + ul { + li { + Link { to: "https://crates.io/crates/tracing-subscriber", "tracing-subscriber" } + "'s " + code { "FmtSubscriber" } + " for console output." + } + li { + Link { to: "https://crates.io/crates/tracing-appender", "tracing-appender" } + " for logging to files." + } + li { + Link { to: "https://crates.io/crates/tracing-bunyan-formatter", + "tracing-bunyan-formatter" + } + " for the Bunyan format." + } + } + p { "To keep this guide short, we will not be covering the usage of these crates." } + p { + "For a full list of popular tracing-based logging crates, visit " + Link { to: "https://docs.rs/tracing/latest/tracing/#related-crates", "this" } + " list in the Tracing crate's docs." + } + h2 { id: "web", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Web, + }, + class: "header", + "Web" + } + } + p { + Link { to: "https://crates.io/crates/tracing-wasm", "tracing-wasm" } + " is a logging interface that can be used with Dioxus' web platform." + } + p { + "The easiest way to use WASM Logger is with the " + code { "set_as_global_default" } + " function:" + } + CodeBlock { contents: "
\nfn main() {{\n    // Init logger\n    tracing_wasm::set_as_global_default();\n    // Dioxus code\n}}
\n" } + p { + "This starts tracing with a " + code { "Level" } + " of " + code { "Trace" } + "." + } + p { + "Using a custom " + code { "level" } + " is a little trickier. We need to use the " + code { "WasmLayerConfigBuilder" } + " and start the logger with " + code { "set_as_global_default_with_config()" } + ":" + } + CodeBlock { + contents: "
\nuse tracing::Level;\n\nfn main() {{\n    // Init logger\n    let tracing_config = tracing_wasm::WASMLayerConfigBuilder::new().set_max_level(Level::INFO).build();\n    tracing_wasm::set_as_global_default_with_config(tracing_config);\n    // Dioxus code\n}}
\n", + } + h1 { id: "mobile", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Mobile, + }, + class: "header", + "Mobile" + } + } + p { + "Unfortunately there are no tracing crates that work with mobile targets. As an alternative you can use the " + Link { to: "https://crates.io/crates/log", "log" } + " crate." + } + h2 { id: "android", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Android, + }, + class: "header", + "Android" + } + } + p { + Link { to: "https://crates.io/crates/android_logger", "Android Logger" } + " is a logging interface that can be used when targeting Android. Android Logger runs whenever an event " + code { "native_activity_create" } + " is called by the Android system:" + } + CodeBlock { + contents: "
\nuse log::LevelFilter;\nuse android_logger::Config;\n\nfn native_activity_create() {{\n    android_logger::init_once(\n        Config::default()\n            .with_max_level(LevelFilter::Info)\n            .with_tag("myapp");\n    );\n}}
\n", + } + p { + "The " + code { "with_tag()" } + " is what your app's logs will show as." + } + h4 { id: "viewing-android-logs", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::ViewingAndroidLogs, + }, + class: "header", + "Viewing Android Logs" + } + } + p { "Android logs are sent to logcat. To use logcat through the Android debugger, run:" } + CodeBlock { contents: "
\nadb -d logcat
\n" } + p { "Your Android device will need developer options/usb debugging enabled." } + p { + "For more information, visit android_logger's " + Link { to: "https://docs.rs/android_logger/latest/android_logger/", "docs" } + "." + } + h2 { id: "ios", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::Ios, + }, + class: "header", + "iOS" + } + } + p { + "The current option for iOS is the " + Link { to: "https://crates.io/crates/oslog", "oslog" } + " crate." + } + CodeBlock { + contents: "
\nfn main() {{\n    // Init logger\n    OsLogger::new("com.example.test")\n        .level_filter(LevelFilter::Debug)\n        .init()\n        .expect("failed to init logger");\n    // Dioxus code\n}}
\n", + } + h4 { id: "viewing-ios-logs", + Link { + to: BookRoute::CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection::ViewingIosLogs, + }, + class: "header", + "Viewing IOS Logs" + } + } + p { "You can view the emitted logs in Xcode." } + p { + "For more information, visit " + Link { to: "https://crates.io/crates/oslog", "oslog" } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookIntegrationsInternationalizationSection { + #[default] + Empty, + Internationalization, + TheFullCodeForInternationalization, +} +impl std::str::FromStr for CookbookIntegrationsInternationalizationSection { + type Err = CookbookIntegrationsInternationalizationSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "internationalization" => Ok(Self::Internationalization), + "the-full-code-for-internationalization" => { + Ok(Self::TheFullCodeForInternationalization) + } + _ => Err(CookbookIntegrationsInternationalizationSectionParseError), + } + } +} +impl std::fmt::Display for CookbookIntegrationsInternationalizationSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Internationalization => f.write_str("internationalization"), + Self::TheFullCodeForInternationalization => { + f.write_str("the-full-code-for-internationalization") + } + } + } +} +#[derive(Debug)] +pub struct CookbookIntegrationsInternationalizationSectionParseError; +impl std::fmt::Display for CookbookIntegrationsInternationalizationSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookIntegrationsInternationalizationSectioninternationalization, the-full-code-for-internationalization", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookIntegrationsInternationalizationSectionParseError {} +#[component(no_case_check)] +pub fn CookbookIntegrationsInternationalization( + section: CookbookIntegrationsInternationalizationSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "internationalization", + Link { + to: BookRoute::CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection::Internationalization, + }, + class: "header", + "Internationalization" + } + } + p { + "If your application supports multiple languages, the " + Link { to: "https://github.com/DioxusLabs/sdk", "Dioxus SDK" } + " crate contains helpers to make working with translations in your application easier." + } + h2 { id: "the-full-code-for-internationalization", + Link { + to: BookRoute::CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection::TheFullCodeForInternationalization, + }, + class: "header", + "The full code for internationalization" + } + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\nuse dioxus_sdk::i18n::*;\nuse dioxus_sdk::translate;\nuse std::str::FromStr;\n\nfn main() {{\n    launch(app);\n}}\n\nstatic EN_US: &str = r#"{{\n    "id": "en-US",\n    "texts": {{\n        "messages": {{\n            "hello_world": "Hello World!"\n        }},\n        "messages.hello": "Hello {{name}}"\n    }}\n}}"#;\nstatic ES_ES: &str = r#"{{\n    "id": "es-ES",\n    "texts": {{\n        "messages": {{\n            "hello_world": "Hola Mundo!"\n        }},\n        "messages.hello": "Hola {{name}}"\n    }}\n}}"#;\n\n#[allow(non_snake_case)]\nfn Body() -> Element {{\n    let mut i18 = use_i18();\n\n    let change_to_english = move |_| i18.set_language("en-US".parse().unwrap());\n    let change_to_spanish = move |_| i18.set_language("es-ES".parse().unwrap());\n\n    rsx! {{\n        button {{ onclick: change_to_english, label {{ "English" }} }}\n        button {{ onclick: change_to_spanish, label {{ "Spanish" }} }}\n        p {{ {{translate!(i18, "messages.hello_world")}} }}\n        p {{ {{translate!(i18, "messages.hello", name: "Dioxus")}} }}\n    }}\n}}\n\nfn app() -> Element {{\n    use_init_i18n("en-US".parse().unwrap(), "en-US".parse().unwrap(), || {{\n        let en_us = Language::from_str(EN_US).unwrap();\n        let es_es = Language::from_str(ES_ES).unwrap();\n        vec![en_us, es_es]\n    }});\n\n    rsx! {{ Body {{}} }}\n}}
\n", + name: "i18n.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookStateIndexSection { + #[default] + Empty, + StateCookbook, +} +impl std::str::FromStr for CookbookStateIndexSection { + type Err = CookbookStateIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "state-cookbook" => Ok(Self::StateCookbook), + _ => Err(CookbookStateIndexSectionParseError), + } + } +} +impl std::fmt::Display for CookbookStateIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::StateCookbook => f.write_str("state-cookbook"), + } + } +} +#[derive(Debug)] +pub struct CookbookStateIndexSectionParseError; +impl std::fmt::Display for CookbookStateIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookStateIndexSectionstate-cookbook", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookStateIndexSectionParseError {} +#[component(no_case_check)] +pub fn CookbookStateIndex(section: CookbookStateIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "state-cookbook", + Link { + to: BookRoute::CookbookStateIndex { + section: CookbookStateIndexSection::StateCookbook, + }, + class: "header", + "State Cookbook" + } + } + ul { + li { + Link { + to: BookRoute::CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection::Empty, + }, + "External State" + } + } + li { + Link { + to: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::Empty, + }, + "Custom Hook" + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookStateExternalIndexSection { + #[default] + Empty, + WorkingWithExternalState, + WorkingWithNonReactiveState, + MakingReactiveStateExternal, +} +impl std::str::FromStr for CookbookStateExternalIndexSection { + type Err = CookbookStateExternalIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "working-with-external-state" => Ok(Self::WorkingWithExternalState), + "working-with-non-reactive-state" => Ok(Self::WorkingWithNonReactiveState), + "making-reactive-state-external" => Ok(Self::MakingReactiveStateExternal), + _ => Err(CookbookStateExternalIndexSectionParseError), + } + } +} +impl std::fmt::Display for CookbookStateExternalIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::WorkingWithExternalState => f.write_str("working-with-external-state"), + Self::WorkingWithNonReactiveState => f.write_str("working-with-non-reactive-state"), + Self::MakingReactiveStateExternal => f.write_str("making-reactive-state-external"), + } + } +} +#[derive(Debug)] +pub struct CookbookStateExternalIndexSectionParseError; +impl std::fmt::Display for CookbookStateExternalIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookStateExternalIndexSectionworking-with-external-state, working-with-non-reactive-state, making-reactive-state-external", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookStateExternalIndexSectionParseError {} +#[component(no_case_check)] +pub fn CookbookStateExternalIndex( + section: CookbookStateExternalIndexSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "working-with-external-state", + Link { + to: BookRoute::CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection::WorkingWithExternalState, + }, + class: "header", + "Working with External State" + } + } + p { + "This guide will help you integrate your Dioxus application with some external state like a different thread or a websocket connection." + } + h2 { id: "working-with-non-reactive-state", + Link { + to: BookRoute::CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection::WorkingWithNonReactiveState, + }, + class: "header", + "Working with non-reactive State" + } + } + p { + Link { + to: BookRoute::ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection::Empty, + }, + "Coroutines" + } + " are great tool for dealing with non-reactive (state you don't render directly) state within your application." + } + p { + "You can store your state inside the coroutine async block and communicate with the coroutine with messages from any child components." + } + CodeBlock { + contents: "
\n// import futures::StreamExt to use the next() method\nuse futures::StreamExt;\nlet mut response_state = use_signal(|| None);\nlet tx = use_coroutine(move |mut rx| async move {{\n    // Define your state before the loop\n    let mut state = reqwest::Client::new();\n    let mut cache: HashMap<String, String> = HashMap::new();\n    loop {{\n        // Loop and wait for the next message\n        if let Some(request) = rx.next().await {{\n            // Resolve the message\n            let response = if let Some(response) = cache.get(&request) {{\n                response.clone()\n            }} else {{\n                let response = state\n                    .get(&request)\n                    .send()\n                    .await\n                    .unwrap()\n                    .text()\n                    .await\n                    .unwrap();\n                cache.insert(request, response.clone());\n                response\n            }};\n            response_state.set(Some(response));\n        }} else {{\n            break;\n        }}\n    }}\n}});\n// Send a message to the coroutine\ntx.send("https://example.com".to_string());\n// Get the current state of the coroutine\nlet response = response_state.read();
\n", + name: "use_coroutine.rs".to_string(), + } + h2 { id: "making-reactive-state-external", + Link { + to: BookRoute::CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection::MakingReactiveStateExternal, + }, + class: "header", + "Making Reactive State External" + } + } + p { + "If you have some reactive state (state that is rendered), that you want to modify from another thread, you can use a signal that is sync. Signals take an optional second generic value with information about syncness. Sync signals have a slightly higher overhead than thread local signals, but they can be used in a multithreaded environment." + } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\nfn main() {{\n    launch(app);\n}}\n\nfn app() -> Element {{\n    let mut signal = use_signal_sync(|| 0);\n\n    use_hook(|| {{\n        std::thread::spawn(move || loop {{\n            std::thread::sleep(std::time::Duration::from_secs(1));\n            // You can easily update the signal from a different thread\n            signal += 1;\n        }});\n    }});\n\n    rsx! {{\n        button {{ onclick: move |_| signal += 1, "Increase" }}\n        "{{signal}}"\n    }}\n}}
\n", + name: "sync_signal.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookStateCustomHooksIndexSection { + #[default] + Empty, + CustomHooks, + ComposingHooks, + CustomHookLogic, +} +impl std::str::FromStr for CookbookStateCustomHooksIndexSection { + type Err = CookbookStateCustomHooksIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "custom-hooks" => Ok(Self::CustomHooks), + "composing-hooks" => Ok(Self::ComposingHooks), + "custom-hook-logic" => Ok(Self::CustomHookLogic), + _ => Err(CookbookStateCustomHooksIndexSectionParseError), + } + } +} +impl std::fmt::Display for CookbookStateCustomHooksIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::CustomHooks => f.write_str("custom-hooks"), + Self::ComposingHooks => f.write_str("composing-hooks"), + Self::CustomHookLogic => f.write_str("custom-hook-logic"), + } + } +} +#[derive(Debug)] +pub struct CookbookStateCustomHooksIndexSectionParseError; +impl std::fmt::Display for CookbookStateCustomHooksIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookStateCustomHooksIndexSectioncustom-hooks, composing-hooks, custom-hook-logic", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookStateCustomHooksIndexSectionParseError {} +#[component(no_case_check)] +pub fn CookbookStateCustomHooksIndex( + section: CookbookStateCustomHooksIndexSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "custom-hooks", + Link { + to: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::CustomHooks, + }, + class: "header", + "Custom Hooks" + } + } + p { + "Hooks are a great way to encapsulate business logic. If none of the existing hooks work for your problem, you can write your own." + } + p { + "When writing your hook, you can make a function that starts with " + code { "use_" } + " and takes any arguments you need. You can then use the " + code { "use_hook" } + " method to create a hook that will be called the first time the component is rendered." + } + h2 { id: "composing-hooks", + Link { + to: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::ComposingHooks, + }, + class: "header", + "Composing Hooks" + } + } + p { + "To avoid repetition, you can encapsulate business logic based on existing hooks to create a new hook." + } + p { + "For example, if many components need to access an " + code { "AppSettings" } + " struct, you can create a \"shortcut\" hook:" + } + CodeBlock { + contents: "
\nfn use_settings() -> Signal<AppSettings> {{\n    consume_context()\n}}
\n", + name: "hooks_composed.rs".to_string(), + } + p { + "Or if you want to wrap a hook that persists reloads with the storage API, you can build on top of the use_signal hook to work with mutable state:" + } + CodeBlock { + contents: "
\nuse gloo_storage::{{LocalStorage, Storage}};\nuse serde::{{de::DeserializeOwned, Serialize}};\n\n/// A persistent storage hook that can be used to store data across application reloads.\n#[allow(clippy::needless_return)]\npub fn use_persistent<T: Serialize + DeserializeOwned + Default + 'static>(\n    // A unique key for the storage entry\n    key: impl ToString,\n    // A function that returns the initial value if the storage entry is empty\n    init: impl FnOnce() -> T,\n) -> UsePersistent<T> {{\n    // Use the use_signal hook to create a mutable state for the storage entry\n    let state = use_signal(move || {{\n        // This closure will run when the hook is created\n        let key = key.to_string();\n        let value = LocalStorage::get(key.as_str()).ok().unwrap_or_else(init);\n        StorageEntry {{ key, value }}\n    }});\n\n    // Wrap the state in a new struct with a custom API\n    UsePersistent {{ inner: state }}\n}}\n\nstruct StorageEntry<T> {{\n    key: String,\n    value: T,\n}}\n\n/// Storage that persists across application reloads\npub struct UsePersistent<T: 'static> {{\n    inner: Signal<StorageEntry<T>>,\n}}\n\nimpl<T> Clone for UsePersistent<T> {{\n    fn clone(&self) -> Self {{\n        *self\n    }}\n}}\n\nimpl<T> Copy for UsePersistent<T> {{}}\n\nimpl<T: Serialize + DeserializeOwned + Clone + 'static> UsePersistent<T> {{\n    /// Returns a reference to the value\n    pub fn get(&self) -> T {{\n        self.inner.read().value.clone()\n    }}\n\n    /// Sets the value\n    pub fn set(&mut self, value: T) {{\n        let mut inner = self.inner.write();\n        // Write the new value to local storage\n        LocalStorage::set(inner.key.as_str(), &value);\n        inner.value = value;\n    }}\n}}
\n", + name: "hooks_composed.rs".to_string(), + } + h2 { id: "custom-hook-logic", + Link { + to: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::CustomHookLogic, + }, + class: "header", + "Custom Hook Logic" + } + } + p { + "You can use " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_hook.html", + code { "use_hook" } + } + " to build your own hooks. In fact, this is what all the standard hooks are built on!" + } + p { + code { "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." + } + blockquote { + p { + "Note: You can use the " + code { "use_on_destroy" } + " hook to clean up any resources the hook uses when the component is destroyed." + } + } + p { + "Inside the initialization closure, you will typically make calls to other " + code { "cx" } + " methods. For example:" + } + ul { + li { + "The " + code { "use_signal" } + " hook tracks state in the hook value, and uses " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/fn.schedule_update.html", + code { "schedule_update" } + } + " to make Dioxus re-render the component whenever it changes." + } + } + p { + "Here is a simplified implementation of the " + code { "use_signal" } + " hook:" + } + CodeBlock { + contents: "
\nuse std::cell::RefCell;\nuse std::rc::Rc;\nuse std::sync::Arc;\n\nstruct Signal<T> {{\n    value: Rc<RefCell<T>>,\n    update: Arc<dyn Fn()>,\n}}\n\nimpl<T> Clone for Signal<T> {{\n    fn clone(&self) -> Self {{\n        Self {{\n            value: self.value.clone(),\n            update: self.update.clone(),\n        }}\n    }}\n}}\n\nfn my_use_signal<T: 'static>(init: impl FnOnce() -> T) -> Signal<T> {{\n    use_hook(|| {{\n        // The update function will trigger a re-render in the component cx is attached to\n        let update = schedule_update();\n        // Create the initial state\n        let value = Rc::new(RefCell::new(init()));\n\n        Signal {{ value, update }}\n    }})\n}}\n\nimpl<T: Clone> Signal<T> {{\n    fn get(&self) -> T {{\n        self.value.borrow().clone()\n    }}\n\n    fn set(&self, value: T) {{\n        // Update the state\n        *self.value.borrow_mut() = value;\n        // Trigger a re-render on the component the state is from\n        (self.update)();\n    }}\n}}
\n", + name: "hooks_custom_logic.rs".to_string(), + } + ul { + li { + "The " + code { "use_context" } + " hook calls " + Link { to: "https://docs.rs/dioxus/latest/dioxus/prelude/fn.consume_context.html", + code { "consume_context" } + } + " (which would be expensive to call on every render) to get some context from the component" + } + } + p { + "Here is an implementation of the " + code { "use_context" } + " and " + code { "use_context_provider" } + " hooks:" + } + CodeBlock { + contents: "
\npub fn use_context<T: 'static + Clone>() -> T {{\n    use_hook(|| consume_context())\n}}\n\npub fn use_context_provider<T: 'static + Clone>(f: impl FnOnce() -> T) -> T {{\n    use_hook(|| {{\n        let val = f();\n        // Provide the context state to the component\n        provide_context(val.clone());\n        val\n    }})\n}}
\n", + name: "hooks_custom_logic.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookTestingSection { + #[default] + Empty, + Testing, + ComponentTesting, + HookTesting, + EndToEndTesting, +} +impl std::str::FromStr for CookbookTestingSection { + type Err = CookbookTestingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "testing" => Ok(Self::Testing), + "component-testing" => Ok(Self::ComponentTesting), + "hook-testing" => Ok(Self::HookTesting), + "end-to-end-testing" => Ok(Self::EndToEndTesting), + _ => Err(CookbookTestingSectionParseError), + } + } +} +impl std::fmt::Display for CookbookTestingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Testing => f.write_str("testing"), + Self::ComponentTesting => f.write_str("component-testing"), + Self::HookTesting => f.write_str("hook-testing"), + Self::EndToEndTesting => f.write_str("end-to-end-testing"), + } + } +} +#[derive(Debug)] +pub struct CookbookTestingSectionParseError; +impl std::fmt::Display for CookbookTestingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookTestingSectiontesting, component-testing, hook-testing, end-to-end-testing", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookTestingSectionParseError {} +#[component(no_case_check)] +pub fn CookbookTesting(section: CookbookTestingSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "testing", + Link { + to: BookRoute::CookbookTesting { + section: CookbookTestingSection::Testing, + }, + class: "header", + "Testing" + } + } + p { + "When building application or libraries with Dioxus, you may want to include some tests to check the behavior of parts of your application. This guide will teach you how to test different parts of your Dioxus application." + } + h2 { id: "component-testing", + Link { + to: BookRoute::CookbookTesting { + section: CookbookTestingSection::ComponentTesting, + }, + class: "header", + "Component Testing" + } + } + p { + "You can use a combination of " + Link { to: "https://docs.rs/pretty_assertions/latest/pretty_assertions/", + "pretty-assertions" + } + " and " + Link { to: "https://docs.rs/dioxus-ssr/latest/dioxus_ssr/", "dioxus-ssr" } + " to check that two snippets of rsx are equal:" + } + CodeBlock { + contents: "
\nuse futures::FutureExt;\nuse std::{{cell::RefCell, sync::Arc}};\n\nuse dioxus::prelude::*;\n\n#[test]\nfn test() {{\n    assert_rsx_eq(\n        rsx! {{\n            div {{ "Hello world" }}\n            div {{ "Hello world" }}\n        }},\n        rsx! {{\n            for _ in 0..2 {{\n                div {{ "Hello world" }}\n            }}\n        }},\n    )\n}}\n\nfn assert_rsx_eq(first: Element, second: Element) {{\n    let first = dioxus_ssr::render_element(first);\n    let second = dioxus_ssr::render_element(second);\n    pretty_assertions::assert_str_eq!(first, second);\n}}
\n", + name: "component_test.rs".to_string(), + } + h2 { id: "hook-testing", + Link { + to: BookRoute::CookbookTesting { + section: CookbookTestingSection::HookTesting, + }, + class: "header", + "Hook Testing" + } + } + p { + "When creating libraries around Dioxus, it can be helpful to make tests for your " + Link { + to: BookRoute::CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection::Empty, + }, + "custom hooks" + } + "." + } + p { + "Dioxus does not currently have a full hook testing library, but you can build a bespoke testing framework by manually driving the virtual dom." + } + CodeBlock { + contents: "
\nuse futures::FutureExt;\nuse std::{{cell::RefCell, rc::Rc, sync::Arc, thread::Scope}};\n\nuse dioxus::{{dioxus_core::NoOpMutations, prelude::*}};\n\n#[test]\nfn test() {{\n    test_hook(\n        || use_signal(|| 0),\n        |mut value, mut proxy| match proxy.generation {{\n            0 => {{\n                value.set(1);\n            }}\n            1 => {{\n                assert_eq!(*value.read(), 1);\n                value.set(2);\n            }}\n            2 => {{\n                proxy.rerun();\n            }}\n            3 => {{}}\n            _ => todo!(),\n        }},\n        |proxy| assert_eq!(proxy.generation, 4),\n    );\n}}\n\nfn test_hook<V: 'static>(\n    initialize: impl FnMut() -> V + 'static,\n    check: impl FnMut(V, MockProxy) + 'static,\n    mut final_check: impl FnMut(MockProxy) + 'static,\n) {{\n    #[derive(Props)]\n    struct MockAppComponent<I: 'static, C: 'static> {{\n        hook: Rc<RefCell<I>>,\n        check: Rc<RefCell<C>>,\n    }}\n\n    impl<I, C> PartialEq for MockAppComponent<I, C> {{\n        fn eq(&self, _: &Self) -> bool {{\n            true\n        }}\n    }}\n\n    impl<I, C> Clone for MockAppComponent<I, C> {{\n        fn clone(&self) -> Self {{\n            Self {{\n                hook: self.hook.clone(),\n                check: self.check.clone(),\n            }}\n        }}\n    }}\n\n    fn mock_app<I: FnMut() -> V, C: FnMut(V, MockProxy), V>(\n        props: MockAppComponent<I, C>,\n    ) -> Element {{\n        let value = props.hook.borrow_mut()();\n\n        props.check.borrow_mut()(value, MockProxy::new());\n\n        rsx! {{ div {{}} }}\n    }}\n\n    let mut vdom = VirtualDom::new_with_props(\n        mock_app,\n        MockAppComponent {{\n            hook: Rc::new(RefCell::new(initialize)),\n            check: Rc::new(RefCell::new(check)),\n        }},\n    );\n\n    vdom.rebuild_in_place();\n\n    while vdom.wait_for_work().now_or_never().is_some() {{\n        vdom.render_immediate(&mut NoOpMutations);\n    }}\n\n    vdom.in_runtime(|| {{\n        ScopeId::ROOT.in_runtime(|| {{\n            final_check(MockProxy::new());\n        }})\n    }})\n}}\n\nstruct MockProxy {{\n    rerender: Arc<dyn Fn()>,\n    pub generation: usize,\n}}\n\nimpl MockProxy {{\n    fn new() -> Self {{\n        let generation = generation();\n        let rerender = schedule_update();\n\n        Self {{\n            rerender,\n            generation,\n        }}\n    }}\n\n    pub fn rerun(&mut self) {{\n        (self.rerender)();\n    }}\n}}
\n", + name: "hook_test.rs".to_string(), + } + h2 { id: "end-to-end-testing", + Link { + to: BookRoute::CookbookTesting { + section: CookbookTestingSection::EndToEndTesting, + }, + class: "header", + "End to End Testing" + } + } + p { + "You can use " + Link { to: "https://playwright.dev/", "Playwright" } + " to create end to end tests for your dioxus application." + } + p { + "In your " + code { "playwright.config.js" } + ", you will need to run cargo run or dx serve instead of the default build command. Here is a snippet from the end to end web example:" + } + CodeBlock { + contents: "
\n//...\nwebServer: [\n    {{\n        cwd: path.join(process.cwd(), 'playwright-tests', 'web'),\n        command: 'dx serve',\n        port: 8080,\n        timeout: 10 * 60 * 1000,\n        reuseExistingServer: !process.env.CI,\n        stdout: "pipe",\n    }},\n],
\n", + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/playwright-tests/web", + "Web example" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/playwright-tests/liveview", + "Liveview example" + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/playwright-tests/fullstack", + "Fullstack example" + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookTailwindSection { + #[default] + Empty, + Tailwind, + Setup, + BonusSteps, + Development, + Web, + Desktop, +} +impl std::str::FromStr for CookbookTailwindSection { + type Err = CookbookTailwindSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "tailwind" => Ok(Self::Tailwind), + "setup" => Ok(Self::Setup), + "bonus-steps" => Ok(Self::BonusSteps), + "development" => Ok(Self::Development), + "web" => Ok(Self::Web), + "desktop" => Ok(Self::Desktop), + _ => Err(CookbookTailwindSectionParseError), + } + } +} +impl std::fmt::Display for CookbookTailwindSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Tailwind => f.write_str("tailwind"), + Self::Setup => f.write_str("setup"), + Self::BonusSteps => f.write_str("bonus-steps"), + Self::Development => f.write_str("development"), + Self::Web => f.write_str("web"), + Self::Desktop => f.write_str("desktop"), + } + } +} +#[derive(Debug)] +pub struct CookbookTailwindSectionParseError; +impl std::fmt::Display for CookbookTailwindSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookTailwindSectiontailwind, setup, bonus-steps, development, web, desktop", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookTailwindSectionParseError {} +#[component(no_case_check)] +pub fn CookbookTailwind(section: CookbookTailwindSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "tailwind", + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Tailwind, + }, + class: "header", + "Tailwind" + } + } + p { + "You can style your Dioxus application with whatever CSS framework you choose, or just write vanilla CSS." + } + p { + "One popular option for styling your Dioxus application is " + Link { to: "https://tailwindcss.com/", "Tailwind" } + ". Tailwind allows you to style your elements with CSS utility classes. This guide will show you how to setup tailwind CSS with your Dioxus application." + } + h2 { id: "setup", + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Setup, + }, + class: "header", + "Setup" + } + } + ol { + li { "Install the Dioxus CLI:" } + } + CodeBlock { contents: "
\ncargo install dioxus-cli
\n" } + ol { + li { + "Install npm: " + Link { to: "https://docs.npmjs.com/downloading-and-installing-node-js-and-npm", + "https://docs.npmjs.com/downloading-and-installing-node-js-and-npm" + } + } + li { + "Install the tailwind css cli: " + Link { to: "https://tailwindcss.com/docs/installation", + "https://tailwindcss.com/docs/installation" + } + } + li { "Initialize the tailwind css project:" } + } + CodeBlock { contents: "
\nnpx tailwindcss init
\n" } + p { + "This should create a " + code { "tailwind.config.js" } + " file in the root of the project." + } + ol { + li { + "Edit the " + code { "tailwind.config.js" } + " file to include rust files:" + } + } + CodeBlock { + contents: "
\nmodule.exports = {{\n    mode: "all",\n    content: [\n        // include all rust, html and css files in the src directory\n        "./src/**/*.{{rs,html,css}}",\n        // include all html files in the output (dist) directory\n        "./dist/**/*.html",\n    ],\n    theme: {{\n        extend: {{}},\n    }},\n    plugins: [],\n}}
\n", + } + ol { + li { + "Create a " + code { "input.css" } + " file in the root of your project with the following content:" + } + } + CodeBlock { contents: "
\n@tailwind base;\n@tailwind components;\n@tailwind utilities;
\n" } + ol { + li { + "Add " + Link { to: "https://github.com/DioxusLabs/manganis", "Manganis" } + " to your project to handle asset collection." + } + } + CodeBlock { contents: "
\ncargo add manganis
\n" } + ol { + li { + "Create a link to the " + code { "tailwind.css" } + " file using manganis somewhere in your rust code:" + } + } + CodeBlock { + contents: "
\n// Urls are relative to your Cargo.toml file\nconst _TAILWIND_URL: &str = manganis::mg!(file("public/tailwind.css"));
\n", + name: "tailwind.rs".to_string(), + } + h3 { id: "bonus-steps", + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::BonusSteps, + }, + class: "header", + "Bonus Steps" + } + } + ol { + li { "Install the tailwind css vs code extension" } + li { + "Go to the settings for the extension and find the experimental regex support section. Edit the setting.json file to look like this:" + } + } + CodeBlock { contents: "
\n"tailwindCSS.experimental.classRegex": ["class\\\\s*:\\\\s*\\"([^\\"]*)"],\n"tailwindCSS.includeLanguages": {{\n    "rust": "html"\n}},
\n" } + h2 { id: "development", + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Development, + }, + class: "header", + "Development" + } + } + ul { + li { + "Run the following command in the root of the project to start the tailwind css compiler:" + } + } + CodeBlock { contents: "
\nnpx tailwindcss -i ./input.css -o ./public/tailwind.css --watch
\n" } + h3 { id: "web", + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Web, + }, + class: "header", + "Web" + } + } + ul { + li { "Run the following command in the root of the project to start the dioxus dev server:" } + } + CodeBlock { contents: "
\ndx serve
\n" } + ul { + li { + "Open the browser to " + Link { to: "http://localhost:8080", "http://localhost:8080" } + "." + } + } + h3 { id: "desktop", + Link { + to: BookRoute::CookbookTailwind { + section: CookbookTailwindSection::Desktop, + }, + class: "header", + "Desktop" + } + } + ul { + li { "Launch the dioxus desktop app:" } + } + CodeBlock { contents: "
\ndx serve --platform desktop
\n" } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookCustomRendererSection { + #[default] + Empty, + CustomRenderer, + TheSpecifics, + Templates, + Mutations, + NodeStorage, + AnExample, + BuildingTemplates, + ApplyingMutations, + EventLoop, + CustomRawElements, + NativeCore, + TheRealdom, + Example, + Layout, + TextEditing, + Conclusion, +} +impl std::str::FromStr for CookbookCustomRendererSection { + type Err = CookbookCustomRendererSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "custom-renderer" => Ok(Self::CustomRenderer), + "the-specifics" => Ok(Self::TheSpecifics), + "templates" => Ok(Self::Templates), + "mutations" => Ok(Self::Mutations), + "node-storage" => Ok(Self::NodeStorage), + "an-example" => Ok(Self::AnExample), + "building-templates" => Ok(Self::BuildingTemplates), + "applying-mutations" => Ok(Self::ApplyingMutations), + "event-loop" => Ok(Self::EventLoop), + "custom-raw-elements" => Ok(Self::CustomRawElements), + "native-core" => Ok(Self::NativeCore), + "the-realdom" => Ok(Self::TheRealdom), + "example" => Ok(Self::Example), + "layout" => Ok(Self::Layout), + "text-editing" => Ok(Self::TextEditing), + "conclusion" => Ok(Self::Conclusion), + _ => Err(CookbookCustomRendererSectionParseError), + } + } +} +impl std::fmt::Display for CookbookCustomRendererSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::CustomRenderer => f.write_str("custom-renderer"), + Self::TheSpecifics => f.write_str("the-specifics"), + Self::Templates => f.write_str("templates"), + Self::Mutations => f.write_str("mutations"), + Self::NodeStorage => f.write_str("node-storage"), + Self::AnExample => f.write_str("an-example"), + Self::BuildingTemplates => f.write_str("building-templates"), + Self::ApplyingMutations => f.write_str("applying-mutations"), + Self::EventLoop => f.write_str("event-loop"), + Self::CustomRawElements => f.write_str("custom-raw-elements"), + Self::NativeCore => f.write_str("native-core"), + Self::TheRealdom => f.write_str("the-realdom"), + Self::Example => f.write_str("example"), + Self::Layout => f.write_str("layout"), + Self::TextEditing => f.write_str("text-editing"), + Self::Conclusion => f.write_str("conclusion"), + } + } +} +#[derive(Debug)] +pub struct CookbookCustomRendererSectionParseError; +impl std::fmt::Display for CookbookCustomRendererSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookCustomRendererSectioncustom-renderer, the-specifics, templates, mutations, node-storage, an-example, building-templates, applying-mutations, event-loop, custom-raw-elements, native-core, the-realdom, example, layout, text-editing, conclusion", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookCustomRendererSectionParseError {} +#[component(no_case_check)] +pub fn CookbookCustomRenderer(section: CookbookCustomRendererSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "custom-renderer", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::CustomRenderer, + }, + class: "header", + "Custom Renderer" + } + } + p { + "Dioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer." + } + p { + "Great news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require processing " + code { "Mutations" } + " and sending " + code { "UserEvents" } + "." + } + h2 { id: "the-specifics", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::TheSpecifics, + }, + class: "header", + "The specifics:" + } + } + p { "Implementing the renderer is fairly straightforward. The renderer needs to:" } + ol { + li { "Handle the stream of edits generated by updates to the virtual DOM" } + li { "Register listeners and pass events into the virtual DOM's event system" } + } + p { + "Essentially, your renderer needs to process edits and generate events to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen." + } + p { + "Internally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves." + } + p { + "For reference, check out the " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/v0.5/packages/interpreter", + "javascript interpreter" + } + " or " + Link { to: "https://github.com/DioxusLabs/blitz/tree/master/packages/dioxus-tui", + "tui renderer" + } + " as a starting point for your custom renderer." + } + h2 { id: "templates", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Templates, + }, + class: "header", + "Templates" + } + } + p { + "Dioxus is built around the concept of " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html", + "Templates" + } + ". Templates describe a UI tree known at compile time with dynamic parts filled at runtime. This is useful internally to make skip diffing static nodes, but it is also useful for the renderer to reuse parts of the UI tree. This can be useful for things like a list of items. Each item could contain some static parts and some dynamic parts. The renderer can use the template to create a static part of the UI once, clone it for each element in the list, and then fill in the dynamic parts." + } + h2 { id: "mutations", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Mutations, + }, + class: "header", + "Mutations" + } + } + p { + "The " + code { "Mutation" } + " type is a serialized enum that represents an operation that should be applied to update the UI. The variants roughly follow this set:" + } + CodeBlock { + contents: "
\nenum Mutation {{\n\tAppendChildren,\n\tAssignId,\n\tCreatePlaceholder,\n\tCreateTextNode,\n\tHydrateText,\n\tLoadTemplate,\n\tReplaceWith,\n\tReplacePlaceholder,\n\tInsertAfter,\n\tInsertBefore,\n\tSetAttribute,\n\tSetText,\n\tNewEventListener,\n\tRemoveEventListener,\n\tRemove,\n\tPushRoot,\n}}
\n", + } + p { + "The Dioxus diffing mechanism operates as a " + Link { to: "https://en.wikipedia.org/wiki/Stack_machine", "stack machine" } + " where the " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate", + "LoadTemplate" + } + ", " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.CreatePlaceholder", + "CreatePlaceholder" + } + ", and " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.CreateTextNode", + "CreateTextNode" + } + " mutations pushes a new \"real\" DOM node onto the stack and " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.AppendChildren", + "AppendChildren" + } + ", " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.InsertAfter", + "InsertAfter" + } + ", " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.InsertBefore", + "InsertBefore" + } + ", " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.ReplacePlaceholder", + "ReplacePlaceholder" + } + ", and " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.ReplaceWith", + "ReplaceWith" + } + " all remove nodes from the stack." + } + h2 { id: "node-storage", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::NodeStorage, + }, + class: "header", + "Node storage" + } + } + p { + "Dioxus saves and loads elements with IDs. Inside the VirtualDOM, this is just tracked as as a u64." + } + p { + "Whenever a " + code { "CreateElement" } + " edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when id is used in a mutation. Dioxus reclaims the IDs of elements when removed. To stay in sync with Dioxus you can use a sparse Vec (Vec" + " " + "<" + " " + "Option" + p { class: "inline-html-block", dangerous_inner_html: "" } + ">) with possibly unoccupied items. You can use the ids as indexes into the Vec for elements, and grow the Vec when an id does not exist." + } + h3 { id: "an-example", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::AnExample, + }, + class: "header", + "An Example" + } + } + p { + "For the sake of understanding, let's consider this example – a very simple UI declaration:" + } + CodeBlock { contents: "
\nrsx! {{\n\th1 {{ "count: {{x}}" }}\n}}
\n" } + h4 { id: "building-templates", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::BuildingTemplates, + }, + class: "header", + "Building Templates" + } + } + p { + "The above rsx will create a template that contains one static h1 tag and a placeholder for a dynamic text node. The template contains the static parts of the UI, and ids for the dynamic parts along with the paths to access them." + } + p { "The template will look something like this:" } + CodeBlock { + contents: "
\nTemplate {{\n\t// Some id that is unique for the entire project\n\tname: "main.rs:1:1:0",\n\t// The root nodes of the template\n\troots: &[\n\t\tTemplateNode::Element {{\n\t\t\ttag: "h1",\n\t\t\tnamespace: None,\n\t\t\tattrs: &[],\n\t\t\tchildren: &[\n\t\t\t\tTemplateNode::DynamicText {{\n\t\t\t\t\tid: 0\n\t\t\t\t}},\n\t\t\t],\n\t\t}}\n\t],\n\t// the path to each of the dynamic nodes\n\tnode_paths: &[\n\t\t// the path to dynamic node with a id of 0\n\t\t&[\n\t\t\t// on the first root node\n\t\t\t0,\n\t\t\t// the first child of the root node\n\t\t\t0,\n\t\t]\n\t],\n\t// the path to each of the dynamic attributes\n\tattr_paths: &'a [&'a [u8]],\n}}
\n", + } + blockquote { + p { + "For more detailed docs about the structure of templates see the " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/prelude/struct.Template.html", + "Template api docs" + } + } + } + p { + "This template will be sent to the renderer in the " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/struct.Mutations.html#structfield.templates", + "list of templates" + } + " supplied with the mutations the first time it is used. Any time the renderer encounters a " + Link { to: "https://docs.rs/dioxus-core/latest/dioxus_core/enum.Mutation.html#variant.LoadTemplate", + "LoadTemplate" + } + " mutation after this, it should clone the template and store it in the given id." + } + p { + "For dynamic nodes and dynamic text nodes, a placeholder node should be created and inserted into the UI so that the node can be modified later." + } + p { "In HTML renderers, this template could look like this:" } + CodeBlock { contents: "
\n<h1>""</h1>
\n" } + h4 { id: "applying-mutations", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::ApplyingMutations, + }, + class: "header", + "Applying Mutations" + } + } + p { + "After the renderer has created all of the new templates, it can begin to process the mutations." + } + p { + "When the renderer starts, it should contain the Root node on the stack and store the Root node with an id of 0. The Root node is the top-level node of the UI. In HTML, this is the " + code { "
" } + " element." + } + CodeBlock { contents: "
\ninstructions: []\nstack: [\n\tRootNode,\n]\nnodes: [\n\tRootNode,\n]
\n" } + p { + "The first mutation is a " + code { "LoadTemplate" } + " mutation. This tells the renderer to load a root from the template with the given id. The renderer will then push the root node of the template onto the stack and store it with an id for later. In this case, the root node is an h1 element." + } + CodeBlock { + contents: "
\ninstructions: [\n\tLoadTemplate {{\n\t\t// the id of the template\n\t\tname: "main.rs:1:1:0",\n\t\t// the index of the root node in the template\n\t\tindex: 0,\n\t\t// the id to store\n\t\tid: ElementId(1),\n\t}}\n]\nstack: [\n\tRootNode,\n\t<h1>""</h1>,\n]\nnodes: [\n\tRootNode,\n\t<h1>""</h1>,\n]
\n", + } + p { + "Next, Dioxus will create the dynamic text node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the Mutation " + code { "HydrateText" } + ". When the renderer receives this instruction, it will navigate to the placeholder text node in the template and replace it with the new text." + } + CodeBlock { + contents: "
\ninstructions: [\n\tLoadTemplate {{\n\t\tname: "main.rs:1:1:0",\n\t\tindex: 0,\n\t\tid: ElementId(1),\n\t}},\n\tHydrateText {{\n\t\t// the id to store the text node\n\t\tid: ElementId(2),\n\t\t// the text to set\n\t\ttext: "count: 0",\n\t}}\n]\nstack: [\n\tRootNode,\n\t<h1>"count: 0"</h1>,\n]\nnodes: [\n\tRootNode,\n\t<h1>"count: 0"</h1>,\n\t"count: 0",\n]
\n", + } + p { + "Remember, the h1 node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the h1 node to the Root. It depends on the situation, but in this case, we use " + code { "AppendChildren" } + ". This pops the text node off the stack, leaving the Root element as the next element on the stack." + } + CodeBlock { + contents: "
\ninstructions: [\n\tLoadTemplate {{\n\t\tname: "main.rs:1:1:0",\n\t\tindex: 0,\n\t\tid: ElementId(1),\n\t}},\n\tHydrateText {{\n\t\tid: ElementId(2),\n\t\ttext: "count: 0",\n\t}},\n\tAppendChildren {{\n\t\t// the id of the parent node\n\t\tid: ElementId(0),\n\t\t// the number of nodes to pop off the stack and append\n\t\tm: 1\n\t}}\n]\nstack: [\n\tRootNode,\n]\nnodes: [\n\tRootNode,\n\t<h1>"count: 0"</h1>,\n\t"count: 0",\n]
\n", + } + p { "Over time, our stack looked like this:" } + CodeBlock { contents: "
\n[Root]\n[Root, <h1>""</h1>]\n[Root, <h1>"count: 0"</h1>]\n[Root]
\n" } + p { + "Conveniently, this approach completely separates the Virtual DOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits make Dioxus independent of platform specifics." + } + p { + "Dioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing." + } + p { + "This little demo serves to show exactly how a Renderer would need to process a mutation stream to build UIs." + } + h2 { id: "event-loop", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::EventLoop, + }, + class: "header", + "Event loop" + } + } + p { + "Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important for your custom renderer can handle those too." + } + p { + "The code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is:" + } + CodeBlock { + contents: "
\npub async fn run(&mut self) -> dioxus_core::error::Result<()> {{\n\t// Push the body element onto the WebsysDom's stack machine\n\tlet mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom());\n\twebsys_dom.stack.push(root_node);\n\n\t// Rebuild or hydrate the virtualdom\n\tlet mutations = self.internal_dom.rebuild();\n\twebsys_dom.apply_mutations(mutations);\n\n\t// Wait for updates from the real dom and progress the virtual dom\n\tloop {{\n\t\tlet user_input_future = websys_dom.wait_for_event();\n\t\tlet internal_event_future = self.internal_dom.wait_for_work();\n\n\t\tmatch select(user_input_future, internal_event_future).await {{\n\t\t\tEither::Left((_, _)) => {{\n\t\t\t\tlet mutations = self.internal_dom.work_with_deadline(|| false);\n\t\t\t\twebsys_dom.apply_mutations(mutations);\n\t\t\t}},\n\t\t\tEither::Right((event, _)) => websys_dom.handle_event(event),\n\t\t}}\n\n\t\t// render\n\t}}\n}}
\n", + } + p { + "It's important to decode what the real events are for your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus " + code { "UserEvent" } + " type. Right now, the virtual event system is modeled almost entirely around the HTML spec, but we are interested in slimming it down." + } + CodeBlock { + contents: "
\nfn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {{\n\tmatch event.type_().as_str() {{\n\t\t"keydown" => {{\n\t\t\tlet event: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();\n\t\t\tUserEvent::KeyboardEvent(UserEvent {{\n\t\t\t\tscope_id: None,\n\t\t\t\tpriority: EventPriority::Medium,\n\t\t\t\tname: "keydown",\n\t\t\t\t// This should be whatever element is focused\n\t\t\t\telement: Some(ElementId(0)),\n\t\t\t\tdata: Arc::new(KeyboardData{{\n\t\t\t\t\tchar_code: event.char_code(),\n\t\t\t\t\tkey: event.key(),\n\t\t\t\t\tkey_code: event.key_code(),\n\t\t\t\t\talt_key: event.alt_key(),\n\t\t\t\t\tctrl_key: event.ctrl_key(),\n\t\t\t\t\tmeta_key: event.meta_key(),\n\t\t\t\t\tshift_key: event.shift_key(),\n\t\t\t\t\tlocation: event.location(),\n\t\t\t\t\trepeat: event.repeat(),\n\t\t\t\t\twhich: event.which(),\n\t\t\t\t}})\n\t\t\t}})\n\t\t}}\n\t\t_ => todo!()\n\t}}\n}}
\n", + } + h2 { id: "custom-raw-elements", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::CustomRawElements, + }, + class: "header", + "Custom raw elements" + } + } + p { + "If you need to go as far as relying on custom elements/attributes 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. You can drop in your elements any time you want, with little hassle. However, you must be sure your renderer can handle the new namespace." + } + p { + "For more examples and information on how to create custom namespaces, see the " + Link { to: "https://github.com/DioxusLabs/dioxus/blob/main/packages/html/README.md#how-to-extend-it", + code { "dioxus_html" } + " crate" + } + "." + } + h1 { id: "native-core", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::NativeCore, + }, + class: "header", + "Native Core" + } + } + p { + "If you are creating a renderer in rust, the " + Link { to: "https://github.com/DioxusLabs/blitz/tree/master/packages/native-core", + "native-core" + } + " crate provides some utilities to implement a renderer. It provides an abstraction over Mutations and Templates and contains helpers that can handle the layout and text editing for you." + } + h2 { id: "the-realdom", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::TheRealdom, + }, + class: "header", + "The RealDom" + } + } + p { + "The " + code { "RealDom" } + " is a higher-level abstraction over updating the Dom. It uses an entity component system to manage the state of nodes. This system allows you to modify insert and modify arbitrary components on nodes. On top of this, the RealDom provides a way to manage a tree of nodes, and the State trait provides a way to automatically add and update these components when the tree is modified. It also provides a way to apply " + code { "Mutations" } + " to the RealDom." + } + h3 { id: "example", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Example, + }, + class: "header", + "Example" + } + } + p { + "Let's build a toy renderer with borders, size, and text color." + " " + "Before we start let's take a look at an example element we can render:" + } + CodeBlock { contents: "
\nrsx!{{\n\tdiv{{\n\t\tcolor: "red",\n\t\tp{{\n\t\t\tborder: "1px solid black",\n\t\t\t"hello world"\n\t\t}}\n\t}}\n}}
\n" } + p { + "In this tree, the color depends on the parent's color. The layout depends on the children's layout, the current text, and the text size. The border depends on only the current node." + } + p { "In the following diagram arrows represent dataflow:" } + p { + Link { to: "https://mermaid.live/edit#pako:eNqllV1vgjAUhv8K6W4wkQVa2QdLdrHsdlfukmSptEhjoaSWqTH-9xVwONAKst70g5739JzzlO5BJAgFAYi52EQJlsr6fAszS7d1sVhKnCdWJDJFt6peLVs5-9owohK7HFrVcFJ_pxnpmK8VVvRkTJikkWIiaxy1dhP23bUwW1WW5WbPrrqJ4ziR4EJ6dtVN2ls5y1ZztePUcrWZFCvqVEcPPDffvlyS1XoLIQnVgnVvVPR6FU9Zc-6dV453ojjOPbuetRJ57gIeXQR3cez7rjtteZyZQ2j5MqmjqwE0ZW0VKx9RKtgpFewp1aw3sXXFy6TWgiYlv8mfq1scD8ofbBCAfQg8_AMBOAyBxzEIwA4CxgQ99QbQkjnD2KT7_CfxGF8_9WXQEsq5sDZCcjICOXRCri4h6r3NA38Q6Jdi1EOx5w3DGDYYI6MUvJFjM3VoGHUeGoMd6mBnDmh2E3fo7O4Yhf0x4OkBmIKUyhQzol_GfbkcApXQlIYg0EOC5SoEYXbQ-3ChxHyXRSBQsqBTUOREx_7OsAY3BUGM-VqvUsKUkB_1U6vf05gtweEHTk4_HQ", + img { + src: "https://mermaid.ink/img/pako:eNqllV1vgjAUhv8K6W4wkQVa2QdLdrHsdlfukmSptEhjoaSWqTH-9xVwONAKst70g5739JzzlO5BJAgFAYi52EQJlsr6fAszS7d1sVhKnCdWJDJFt6peLVs5-9owohK7HFrVcFJ_pxnpmK8VVvRkTJikkWIiaxy1dhP23bUwW1WW5WbPrrqJ4ziR4EJ6dtVN2ls5y1ZztePUcrWZFCvqVEcPPDffvlyS1XoLIQnVgnVvVPR6FU9Zc-6dV453ojjOPbuetRJ57gIeXQR3cez7rjtteZyZQ2j5MqmjqwE0ZW0VKx9RKtgpFewp1aw3sXXFy6TWgiYlv8mfq1scD8ofbBCAfQg8_AMBOAyBxzEIwA4CxgQ99QbQkjnD2KT7_CfxGF8_9WXQEsq5sDZCcjICOXRCri4h6r3NA38Q6Jdi1EOx5w3DGDYYI6MUvJFjM3VoGHUeGoMd6mBnDmh2E3fo7O4Yhf0x4OkBmIKUyhQzol_GfbkcApXQlIYg0EOC5SoEYXbQ-3ChxHyXRSBQsqBTUOREx_7OsAY3BUGM-VqvUsKUkB_1U6vf05gtweEHTk4_HQ?type=png", + alt: "", + title: "", + } + } + } + p { + "To help in building a Dom, native-core provides the State trait and a RealDom struct. The State trait provides a way to describe how states in a node depend on other states in its relatives. By describing how to update a single node from its relations, native-core will derive a way to update the states of all nodes for you. Once you have a state you can provide it as a generic to RealDom. RealDom provides all of the methods to interact and update your new dom." + } + p { + "Native Core cannot create all of the required methods for the State trait, but it can derive some of them. To implement the State trait, you must implement the following methods and let the " + code { "#[partial_derive_state]" } + " macro handle the rest:" + } + CodeBlock { + contents: "
\n
\n", + name: "custom_renderer.rs".to_string(), + } + p { "Lets take a look at how to implement the State trait for a simple renderer." } + CodeBlock { + contents: "
\n
\n", + name: "custom_renderer.rs".to_string(), + } + p { + "Now that we have our state, we can put it to use in our RealDom. We can update the RealDom with apply_mutations to update the structure of the dom (adding, removing, and changing properties of nodes) and then update_state to update the States for each of the nodes that changed." + } + CodeBlock { + contents: "
\n
\n", + name: "custom_renderer.rs".to_string(), + } + h2 { id: "layout", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Layout, + }, + class: "header", + "Layout" + } + } + p { + "For most platforms, the layout of the Elements will stay the same. The " + Link { to: "https://docs.rs/dioxus-native-core/latest/dioxus_native_core/layout_attributes/index.html", + "layout_attributes" + } + " module provides a way to apply HTML attributes a " + Link { to: "https://docs.rs/taffy/latest/taffy/index.html", "Taffy" } + " layout style." + } + h2 { id: "text-editing", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::TextEditing, + }, + class: "header", + "Text Editing" + } + } + p { + "To make it easier to implement text editing in rust renderers, " + code { "native-core" } + " also contains a renderer-agnostic cursor system. The cursor can handle text editing, selection, and movement with common keyboard shortcuts integrated." + } + CodeBlock { + contents: "
\n
\n", + name: "custom_renderer.rs".to_string(), + } + h2 { id: "conclusion", + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Conclusion, + }, + class: "header", + "Conclusion" + } + } + p { + "That should be it! You should have nearly all the knowledge required on how to implement your renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderers, video game UI, and even augmented reality! If you're interested in contributing to any of these projects, don't be afraid to reach out or join the " + Link { to: "https://discord.gg/XgGxMSkvUM", "community" } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CookbookOptimizingSection { + #[default] + Empty, + Optimizing, + BuildingInReleaseMode, + Upx, + BuildConfiguration, + Stable, + Unstable, + WasmOpt, + ImprovingDioxusCode, + OptimizingTheSizeOfAssets, +} +impl std::str::FromStr for CookbookOptimizingSection { + type Err = CookbookOptimizingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "optimizing" => Ok(Self::Optimizing), + "building-in-release-mode" => Ok(Self::BuildingInReleaseMode), + "upx" => Ok(Self::Upx), + "build-configuration" => Ok(Self::BuildConfiguration), + "stable" => Ok(Self::Stable), + "unstable" => Ok(Self::Unstable), + "wasm-opt" => Ok(Self::WasmOpt), + "improving-dioxus-code" => Ok(Self::ImprovingDioxusCode), + "optimizing-the-size-of-assets" => Ok(Self::OptimizingTheSizeOfAssets), + _ => Err(CookbookOptimizingSectionParseError), + } + } +} +impl std::fmt::Display for CookbookOptimizingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Optimizing => f.write_str("optimizing"), + Self::BuildingInReleaseMode => f.write_str("building-in-release-mode"), + Self::Upx => f.write_str("upx"), + Self::BuildConfiguration => f.write_str("build-configuration"), + Self::Stable => f.write_str("stable"), + Self::Unstable => f.write_str("unstable"), + Self::WasmOpt => f.write_str("wasm-opt"), + Self::ImprovingDioxusCode => f.write_str("improving-dioxus-code"), + Self::OptimizingTheSizeOfAssets => f.write_str("optimizing-the-size-of-assets"), + } + } +} +#[derive(Debug)] +pub struct CookbookOptimizingSectionParseError; +impl std::fmt::Display for CookbookOptimizingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CookbookOptimizingSectionoptimizing, building-in-release-mode, upx, build-configuration, stable, unstable, wasm-opt, improving-dioxus-code, optimizing-the-size-of-assets", + )?; + Ok(()) + } +} +impl std::error::Error for CookbookOptimizingSectionParseError {} +#[component(no_case_check)] +pub fn CookbookOptimizing(section: CookbookOptimizingSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "optimizing", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Optimizing, + }, + class: "header", + "Optimizing" + } + } + p { + em { + "Note: This is written primarily for the web, but the main optimizations will work on other platforms too." + } + } + p { + "You might have noticed that Dioxus binaries are pretty big." + " " + "The WASM binary of a " + Link { to: "https://github.com/tigerros/dioxus-todo-app", "TodoMVC app" } + " weighs in at 2.36mb!" + " " + "Don't worry; we can get it down to a much more manageable 234kb." + " " + "This will get obviously lower over time." + " " + "With nightly features, you can even reduce the binary size of a hello world app to less than 100kb!" + } + p { "We will also discuss ways to optimize your app for increased speed." } + p { + "However, certain optimizations will sacrifice speed for decreased binary size or the other way around." + " " + "That's what you need to figure out yourself. Does your app perform performance-intensive tasks, such as graphical processing or tons of DOM manipulations?" + " " + "You could go for increased speed. In most cases, though, decreased binary size is the better choice, especially because Dioxus WASM binaries are quite large." + } + p { + "To test binary sizes, we will use " + Link { to: "https://github.com/tigerros/dioxus-todo-app", "this" } + " repository as a sample app." + " " + "The " + code { "no-optimizations" } + " package will serve as the base, which weighs 2.36mb as of right now." + } + p { "Additional resources:" } + ul { + li { + Link { to: "https://rustwasm.github.io/docs/book/reference/code-size.html", + "WASM book - Shrinking " + code { ".wasm" } + " code size" + } + } + li { + Link { to: "https://github.com/johnthagen/min-sized-rust", "min-sized-rust" } + } + } + h2 { id: "building-in-release-mode", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::BuildingInReleaseMode, + }, + class: "header", + "Building in release mode" + } + } + p { + "This is the best way to optimize. In fact, the 2.36mb figure at the start of the guide is with release mode." + " " + "In debug mode, it's actually a whopping 32mb! It also increases the speed of your app." + } + p { + "Thankfully, no matter what tool you're using to build your app, it will probably have a " + code { "--release" } + " flag to do this." + } + p { + "Using the " + Link { to: "https://dioxuslabs.com/learn/0.5/CLI", "Dioxus CLI" } + " or " + Link { to: "https://trunkrs.dev/", "Trunk" } + ":" + } + ul { + li { + "Dioxus CLI: " + code { "dx build --release" } + } + li { + "Trunk: " + code { "trunk build --release" } + } + } + h2 { id: "upx", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Upx, + }, + class: "header", + "UPX" + } + } + p { + "If you're not targeting web, you can use the " + Link { to: "https://github.com/upx/upx", "UPX" } + " CLI tool to compress your executables." + } + p { "Setup:" } + ul { + li { + "Download a " + Link { to: "https://github.com/upx/upx/releases", "release" } + " and extract the directory inside to a sensible location." + } + li { "Add the executable located in the directory to your path variable." } + } + p { + "You can run " + code { "upx --help" } + " to get the CLI options, but you should also view " + code { "upx-doc.html" } + " for more detailed information." + " " + "It's included in the extracted directory." + } + p { + "An example command might be: " + code { "upx --best -o target/release/compressed.exe target/release/your-executable.exe" } + "." + } + h2 { id: "build-configuration", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::BuildConfiguration, + }, + class: "header", + "Build configuration" + } + } + p { + em { + "Note: Settings defined in " + code { ".cargo/config.toml" } + " will override settings in " + code { "Cargo.toml" } + "." + } + } + p { + "Other than the " + code { "--release" } + " flag, this is the easiest way to optimize your projects, and also the most effective way," + " " + "at least in terms of reducing binary size." + } + h3 { id: "stable", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Stable, + }, + class: "header", + "Stable" + } + } + p { + "This configuration is 100% stable and decreases the binary size from 2.36mb to 310kb." + " " + "Add this to your " + code { ".cargo/config.toml" } + ":" + } + CodeBlock { + contents: "
\n[profile.release]\nopt-level = "z"\ndebug = false\nlto = true\ncodegen-units = 1\npanic = "abort"\nstrip = true\nincremental = false
\n", + } + p { "Links to the documentation of each value:" } + ul { + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#opt-level", + code { "opt-level" } + } + } + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#debuginfo", + code { "debug" } + } + } + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#lto", + code { "lto" } + } + } + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#codegen-units", + code { "codegen-units" } + } + } + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#panic", + code { "panic" } + } + } + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#strip", + code { "strip" } + } + } + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#incremental", + code { "incremental" } + } + } + } + h3 { id: "unstable", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Unstable, + }, + class: "header", + "Unstable" + } + } + p { + "This configuration contains some unstable features, but it should work just fine." + " " + "It decreases the binary size from 310kb to 234kb." + " " + "Add this to your " + code { ".cargo/config.toml" } + ":" + } + CodeBlock { + contents: "
\n[unstable]\nbuild-std = ["std", "panic_abort", "core", "alloc"]\nbuild-std-features = ["panic_immediate_abort"]\n\n[build]\nrustflags = [\n    "-Clto",\n    "-Zvirtual-function-elimination",\n    "-Zlocation-detail=none"\n]\n\n# Same as in the Stable section\n[profile.release]\nopt-level = "z"\ndebug = false\nlto = true\ncodegen-units = 1\npanic = "abort"\nstrip = true\nincremental = false
\n", + } + p { + em { + "Note: The omitted space in each flag (e.g., " + code { "-Clto" } + ") is intentional. It is not a typo." + } + } + p { + "The values in " + code { "[profile.release]" } + " are documented in the " + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::Stable, + }, + "Stable" + } + " section. Links to the documentation of each value:" + } + ul { + li { + Link { to: "https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags", + code { "[build.rustflags]" } + } + } + li { + Link { to: "https://doc.rust-lang.org/rustc/codegen-options/index.html#lto", + code { "-C lto" } + } + } + li { + Link { to: "https://doc.rust-lang.org/stable/unstable-book/compiler-flags/virtual-function-elimination.html", + code { "-Z virtual-function-elimination" } + } + } + li { + Link { to: "https://doc.rust-lang.org/stable/unstable-book/compiler-flags/location-detail.html", + code { "-Z location-detail" } + } + } + } + h2 { id: "wasm-opt", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::WasmOpt, + }, + class: "header", + "wasm-opt" + } + } + p { + em { + "Note: In the future, " + code { "wasm-opt" } + " will be supported natively through the " + Link { to: "https://crates.io/crates/dioxus-cli", "Dioxus CLI" } + "." + } + } + p { + code { "wasm-opt" } + " is a tool from the " + Link { to: "https://github.com/WebAssembly/binaryen", "binaryen" } + " library that optimizes your WASM files." + " " + "To use it, install a " + Link { to: "https://github.com/WebAssembly/binaryen/releases", "binaryen release" } + " and run this command from the package directory:" + } + CodeBlock { contents: "
\nwasm-opt dist/assets/dioxus/APP_NAME_bg.wasm -o dist/assets/dioxus/APP_NAME_bg.wasm -Oz
\n" } + p { + "The " + code { "-Oz" } + " flag specifies that " + code { "wasm-opt" } + " should optimize for size. For speed, use " + code { "-O4" } + "." + } + h2 { id: "improving-dioxus-code", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::ImprovingDioxusCode, + }, + class: "header", + "Improving Dioxus code" + } + } + p { "Let's talk about how you can improve your Dioxus code to be more performant." } + p { + "It's important to minimize the number of dynamic parts in your " + code { "rsx" } + ", like conditional rendering." + " " + "When Dioxus is rendering your component, it will skip parts that are the same as the last render." + " " + "That means that if you keep dynamic rendering to a minimum, your app will speed up, and quite a bit if it's not just hello world." + " " + "To see an example of this, check out " + Link { + to: BookRoute::ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection::Empty, + }, + "Dynamic Rendering" + } + "." + } + p { + "Also check out " + Link { + to: BookRoute::CookbookAntipatterns { + section: CookbookAntipatternsSection::Empty, + }, + "Anti-patterns" + } + " for patterns that you should avoid." + " " + "Obviously, not all of them are just about performance, but some of them are." + } + h2 { id: "optimizing-the-size-of-assets", + Link { + to: BookRoute::CookbookOptimizing { + section: CookbookOptimizingSection::OptimizingTheSizeOfAssets, + }, + class: "header", + "Optimizing the size of assets" + } + } + p { + "Assets can be a significant part of your app's size. Dioxus includes alpha support for first party " + Link { + to: BookRoute::ReferenceAssets { + section: ReferenceAssetsSection::Empty, + }, + "assets" + } + ". Any assets you include with the " + code { "mg!" } + " macro will be optimized for production in release builds." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CliIndexSection { + #[default] + Empty, + Introduction, + Features, +} +impl std::str::FromStr for CliIndexSection { + type Err = CliIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "introduction" => Ok(Self::Introduction), + "features" => Ok(Self::Features), + _ => Err(CliIndexSectionParseError), + } + } +} +impl std::fmt::Display for CliIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Introduction => f.write_str("introduction"), + Self::Features => f.write_str("features"), + } + } +} +#[derive(Debug)] +pub struct CliIndexSectionParseError; +impl std::fmt::Display for CliIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of CliIndexSectionintroduction, features")?; + Ok(()) + } +} +impl std::error::Error for CliIndexSectionParseError {} +#[component(no_case_check)] +pub fn CliIndex(section: CliIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "introduction", + Link { + to: BookRoute::CliIndex { + section: CliIndexSection::Introduction, + }, + class: "header", + "Introduction" + } + } + p { + "The ✨" + strong { "Dioxus CLI" } + "✨ is a tool to get Dioxus projects off the ground." + } + p { + "There's no documentation for commands here, but you can see all commands using " + code { "dx --help" } + " once you've installed the CLI! Furthermore, you can run " + code { "dx --help" } + " to get help with a specific command." + } + h2 { id: "features", + Link { + to: BookRoute::CliIndex { + section: CliIndexSection::Features, + }, + class: "header", + "Features" + } + } + ul { + li { "Build and pack a Dioxus project." } + li { + "Format " + code { "rsx" } + " code." + } + li { "Hot Reload." } + li { "Create a Dioxus project from a template repository." } + li { "And more!" } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CliCreatingSection { + #[default] + Empty, + CreateAProject, + InitializingAProject, +} +impl std::str::FromStr for CliCreatingSection { + type Err = CliCreatingSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "create-a-project" => Ok(Self::CreateAProject), + "initializing-a-project" => Ok(Self::InitializingAProject), + _ => Err(CliCreatingSectionParseError), + } + } +} +impl std::fmt::Display for CliCreatingSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::CreateAProject => f.write_str("create-a-project"), + Self::InitializingAProject => f.write_str("initializing-a-project"), + } + } +} +#[derive(Debug)] +pub struct CliCreatingSectionParseError; +impl std::fmt::Display for CliCreatingSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CliCreatingSectioncreate-a-project, initializing-a-project", + )?; + Ok(()) + } +} +impl std::error::Error for CliCreatingSectionParseError {} +#[component(no_case_check)] +pub fn CliCreating(section: CliCreatingSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "create-a-project", + Link { + to: BookRoute::CliCreating { + section: CliCreatingSection::CreateAProject, + }, + class: "header", + "Create a Project" + } + } + p { "Once you have the Dioxus CLI installed, you can use it to create your own project!" } + h2 { id: "initializing-a-project", + Link { + to: BookRoute::CliCreating { + section: CliCreatingSection::InitializingAProject, + }, + class: "header", + "Initializing a project" + } + } + p { + "First, run the " + code { "dx new" } + " command to create a new project." + } + blockquote { + p { + "It clones this " + Link { to: "https://github.com/DioxusLabs/dioxus-template", "template" } + ", which is used to create dioxus apps." + } + p { + "You can create your project from a different template by passing the " + code { "template" } + " argument:" + } + CodeBlock { contents: "
\ndx new --template gh:dioxuslabs/dioxus-template
\n" } + } + p { + "Next, navigate into your new project using " + code { "cd project-name" } + ", or simply opening it in an IDE." + } + blockquote { + p { + "Make sure the WASM target is installed before running the projects." + " " + "You can install the WASM target for rust using rustup:" + } + CodeBlock { contents: "
\nrustup target add wasm32-unknown-unknown
\n" } + } + p { + "Finally, serve your project with " + code { "dx serve" } + "! The CLI will tell you the address it is serving on, along with additional" + " " + "info such as code warnings." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CliConfigureSection { + #[default] + Empty, + ConfigureProject, + Structure, + Application, + Webapp, + Webwatcher, + Webresource, + Webresourcedev, + Webproxy, + ConfigExample, +} +impl std::str::FromStr for CliConfigureSection { + type Err = CliConfigureSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "configure-project" => Ok(Self::ConfigureProject), + "structure" => Ok(Self::Structure), + "application-" => Ok(Self::Application), + "webapp-" => Ok(Self::Webapp), + "webwatcher-" => Ok(Self::Webwatcher), + "webresource-" => Ok(Self::Webresource), + "webresourcedev-" => Ok(Self::Webresourcedev), + "webproxy" => Ok(Self::Webproxy), + "config-example" => Ok(Self::ConfigExample), + _ => Err(CliConfigureSectionParseError), + } + } +} +impl std::fmt::Display for CliConfigureSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ConfigureProject => f.write_str("configure-project"), + Self::Structure => f.write_str("structure"), + Self::Application => f.write_str("application-"), + Self::Webapp => f.write_str("webapp-"), + Self::Webwatcher => f.write_str("webwatcher-"), + Self::Webresource => f.write_str("webresource-"), + Self::Webresourcedev => f.write_str("webresourcedev-"), + Self::Webproxy => f.write_str("webproxy"), + Self::ConfigExample => f.write_str("config-example"), + } + } +} +#[derive(Debug)] +pub struct CliConfigureSectionParseError; +impl std::fmt::Display for CliConfigureSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CliConfigureSectionconfigure-project, structure, application-, webapp-, webwatcher-, webresource-, webresourcedev-, webproxy, config-example", + )?; + Ok(()) + } +} +impl std::error::Error for CliConfigureSectionParseError {} +#[component(no_case_check)] +pub fn CliConfigure(section: CliConfigureSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "configure-project", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::ConfigureProject, + }, + class: "header", + "Configure Project" + } + } + p { + "This chapter will teach you how to configure the CLI with the " + code { "Dioxus.toml" } + " file. There's an " + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::ConfigExample, + }, + "example" + } + " which has comments to describe individual keys. You can copy that or view this documentation for a more complete learning experience." + } + p { + "\"🔒\" indicates a mandatory item. Some headers are mandatory, but none of the keys inside them are. In that case, you only need to include the header, but no keys. It might look weird, but it's normal." + } + h2 { id: "structure", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Structure, + }, + class: "header", + "Structure" + } + } + p { "Each header has its TOML form directly under it." } + h3 { id: "application-", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Application, + }, + class: "header", + "Application 🔒" + } + } + CodeBlock { contents: "
\n[application]
\n" } + p { "Application-wide configuration. Applies to both web and desktop." } + ul { + li { + strong { "name" } + " 🔒 - Project name & title." + CodeBlock { contents: "
\nname = "my_project"
\n" } + } + li { + strong { "default_platform" } + " 🔒 - The platform this project targets" + CodeBlock { contents: "
\n# Currently supported platforms: web, desktop\ndefault_platform = "web"
\n" } + } + li { + strong { "out_dir" } + " - The directory to place the build artifacts from " + code { "dx build" } + " or " + code { "dx serve" } + " into. This is also where the " + code { "assets" } + " directory will be copied into." + CodeBlock { contents: "
\nout_dir = "dist"
\n" } + } + li { + strong { "asset_dir" } + " - The directory with your static assets. The CLI will automatically copy these assets into the " + strong { "out_dir" } + " after a build/serve." + CodeBlock { contents: "
\nasset_dir = "public"
\n" } + } + li { + strong { "sub_package" } + " - The sub package in the workspace to build by default." + CodeBlock { contents: "
\nsub_package = "my-crate"
\n" } + } + } + h3 { id: "webapp-", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Webapp, + }, + class: "header", + "Web.App 🔒" + } + } + CodeBlock { contents: "
\n[web.app]
\n" } + p { "Web-specific configuration." } + ul { + li { + strong { "title" } + " - The title of the web page." + CodeBlock { contents: "
\n# HTML title tag content\ntitle = "project_name"
\n" } + } + li { + strong { "base_path" } + " - The base path to build the application for serving at. This can be useful when serving your application in a subdirectory under a domain. For example, when building a site to be served on GitHub Pages." + CodeBlock { contents: "
\n# The application will be served at domain.com/my_application/, so we need to modify the base_path to the path where the application will be served\nbase_path = "my_application"
\n" } + } + } + h3 { id: "webwatcher-", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Webwatcher, + }, + class: "header", + "Web.Watcher 🔒" + } + } + CodeBlock { contents: "
\n[web.watcher]
\n" } + p { "Development server configuration." } + ul { + li { + p { + strong { "reload_html" } + " - If this is true, the cli will rebuild the index.html file every time the application is rebuilt" + } + CodeBlock { contents: "
\nreload_html = true
\n" } + } + li { + p { + strong { "watch_path" } + " - The files & directories to monitor for changes" + } + CodeBlock { contents: "
\nwatch_path = ["src", "public"]
\n" } + } + li { + p { + strong { "index_on_404" } + " - If enabled, Dioxus will serve the root page when a route is not found." + em { "This is needed when serving an application that uses the router" } + ". However, when serving your app using something else than Dioxus (e.g. GitHub Pages), you will have to check how to configure it on that platform. In GitHub Pages, you can make a copy of " + code { "index.html" } + " named " + code { "404.html" } + " in the same directory." + } + CodeBlock { contents: "
\nindex_on_404 = true
\n" } + } + } + h3 { id: "webresource-", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Webresource, + }, + class: "header", + "Web.Resource 🔒" + } + } + CodeBlock { contents: "
\n[web.resource]
\n" } + p { "Static resource configuration." } + ul { + li { + p { + strong { "style" } + " - CSS files to include in your application." + } + CodeBlock { contents: "
\nstyle = [\n   # Include from public_dir.\n   "./assets/style.css",\n   # Or some asset from online cdn.\n   "https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css"\n]
\n" } + } + li { + p { + strong { "script" } + " - JavaScript files to include in your application." + } + CodeBlock { contents: "
\nscript = [\n    # Include from asset_dir.\n    "./public/index.js",\n    # Or from an online CDN.\n    "https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js"\n]
\n" } + } + } + h3 { id: "webresourcedev-", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Webresourcedev, + }, + class: "header", + "Web.Resource.Dev 🔒" + } + } + CodeBlock { contents: "
\n[web.resource.dev]
\n" } + p { + "This is the same as " + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Webresource, + }, + code { "[web.resource]" } + } + ", but it only works in development servers. For example, if you want to include a file in a " + code { "dx serve" } + " server, but not a " + code { "dx serve --release" } + " server, put it here." + } + h3 { id: "webproxy", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::Webproxy, + }, + class: "header", + "Web.Proxy" + } + } + CodeBlock { contents: "
\n[[web.proxy]]
\n" } + p { + "Configuration related to any proxies your application requires during development. Proxies will forward requests to a new service." + } + ul { + li { + strong { "backend" } + " - The URL to the server to proxy. The CLI will forward any requests under the backend relative route to the backend instead of returning 404" + CodeBlock { contents: "
\nbackend = "http://localhost:8000/api/"
\n" } + "This will cause any requests made to the dev server with prefix /api/ to be redirected to the backend server at http://localhost:8000. The path and query parameters will be passed on as-is (path rewriting is currently not supported)." + } + } + h2 { id: "config-example", + Link { + to: BookRoute::CliConfigure { + section: CliConfigureSection::ConfigExample, + }, + class: "header", + "Config example" + } + } + p { "This includes all fields, mandatory or not." } + CodeBlock { + contents: "
\n[application]\n\n# App name\nname = "project_name"\n\n# The Dioxus platform to default to\ndefault_platform = "web"\n\n# `build` & `serve` output path\nout_dir = "dist"\n\n# The static resource path\nasset_dir = "public"\n\n[web.app]\n\n# HTML title tag content\ntitle = "project_name"\n\n[web.watcher]\n\n# When watcher is triggered, regenerate the `index.html`\nreload_html = true\n\n# Which files or dirs will be monitored\nwatch_path = ["src", "public"]\n\n# Include style or script assets\n[web.resource]\n\n# CSS style file\nstyle = []\n\n# Javascript code file\nscript = []\n\n[web.resource.dev]\n\n# Same as [web.resource], but for development servers\n\n# CSS style file\nstyle = []\n\n# JavaScript files\nscript = []\n\n[[web.proxy]]\nbackend = "http://localhost:8000/api/"
\n", + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum CliTranslateSection { + #[default] + Empty, + TranslatingExistingHtml, + Usage, +} +impl std::str::FromStr for CliTranslateSection { + type Err = CliTranslateSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "translating-existing-html" => Ok(Self::TranslatingExistingHtml), + "usage" => Ok(Self::Usage), + _ => Err(CliTranslateSectionParseError), + } + } +} +impl std::fmt::Display for CliTranslateSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::TranslatingExistingHtml => f.write_str("translating-existing-html"), + Self::Usage => f.write_str("usage"), + } + } +} +#[derive(Debug)] +pub struct CliTranslateSectionParseError; +impl std::fmt::Display for CliTranslateSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of CliTranslateSectiontranslating-existing-html, usage", + )?; + Ok(()) + } +} +impl std::error::Error for CliTranslateSectionParseError {} +#[component(no_case_check)] +pub fn CliTranslate(section: CliTranslateSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "translating-existing-html", + Link { + to: BookRoute::CliTranslate { + section: CliTranslateSection::TranslatingExistingHtml, + }, + class: "header", + "Translating existing HTML" + } + } + p { + "Dioxus uses a custom format called RSX to represent the HTML because it is more concise and looks more like Rust code. However, it can be a pain to convert existing HTML to RSX. That's why Dioxus comes with a tool called " + code { "dx translate" } + " that can automatically convert HTML to RSX!" + } + p { + "Dx translate can make converting large chunks of HTML to RSX much easier! Lets try translating some of the HTML from the Dioxus homepage:" + } + CodeBlock { + contents: "
\ndx translate --raw  "<div class=\\"relative w-full mx-4 sm:mx-auto text-gray-600\\"><div class=\\"text-[3em] md:text-[5em] font-semibold dark:text-white text-ghdarkmetal font-sans py-12 flex flex-col\\"><span>Fullstack, crossplatform,</span><span>lightning fast, fully typed.</span></div><h3 class=\\"text-[2em] dark:text-white font-extralight text-ghdarkmetal pt-4 max-w-screen-md mx-auto\\">Dioxus is a Rust library for building apps that run on desktop, web, mobile, and more.</h3><div class=\\"pt-12 text-white text-[1.2em] font-sans font-bold flex flex-row justify-center space-x-4\\"><a href=\\"/learn/0.5/getting_started\\" dioxus-prevent-default=\\"onclick\\" class=\\"bg-red-600 py-2 px-8 hover:-translate-y-2 transition-transform duration-300\\" data-dioxus-id=\\"216\\">Quickstart</a><a href=\\"/learn/0.5/reference\\" dioxus-prevent-default=\\"onclick\\" class=\\"bg-blue-500 py-2 px-8 hover:-translate-y-2 transition-transform duration-300\\" data-dioxus-id=\\"214\\">Read the docs</a></div><div class=\\"max-w-screen-2xl mx-auto pt-36\\"><h1 class=\\"text-md\\">Trusted by top companies</h1><div class=\\"pt-4 flex flex-row flex-wrap justify-center\\"><div class=\\"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\\"><img src=\\"static/futurewei_bw.png\\"></div><div class=\\"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\\"><img src=\\"static/airbuslogo.svg\\"></div><div class=\\"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\\"><img src=\\"static/ESA_logo.svg\\"></div><div class=\\"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\\"><img src=\\"static/yclogo.svg\\"></div><div class=\\"h-12 w-40 bg-black p-2 m-4 flex justify-center items-center\\"><img src=\\"static/satellite.webp\\"></div></div></div></div>"
\n", + } + p { "We get the following RSX you can easily copy and paste into your code:" } + CodeBlock { + contents: "
\ndiv {{ class: "relative w-full mx-4 sm:mx-auto text-gray-600",\n   div {{ class: "text-[3em] md:text-[5em] font-semibold dark:text-white text-ghdarkmetal font-sans py-12 flex flex-col",\n      span {{ "Fullstack, crossplatform," }}\n      span {{ "lightning fast, fully typed." }}\n   }}\n   h3 {{ class: "text-[2em] dark:text-white font-extralight text-ghdarkmetal pt-4 max-w-screen-md mx-auto",\n      "Dioxus is a Rust library for building apps that run on desktop, web, mobile, and more."\n   }}\n   div {{ class: "pt-12 text-white text-[1.2em] font-sans font-bold flex flex-row justify-center space-x-4",\n      a {{\n         href: "/learn/0.5/getting_started",\n         data_dioxus_id: "216",\n         dioxus_prevent_default: "onclick",\n         class: "bg-red-600 py-2 px-8 hover:-translate-y-2 transition-transform duration-300",\n         "Quickstart"\n      }}\n      a {{\n         dioxus_prevent_default: "onclick",\n         href: "/learn/0.5/reference",\n         data_dioxus_id: "214",\n         class: "bg-blue-500 py-2 px-8 hover:-translate-y-2 transition-transform duration-300",\n         "Read the docs"\n      }}\n   }}\n   div {{ class: "max-w-screen-2xl mx-auto pt-36",\n      h1 {{ class: "text-md", "Trusted by top companies" }}\n      div {{ class: "pt-4 flex flex-row flex-wrap justify-center",\n         div {{ class: "h-12 w-40 p-2 m-4 flex justify-center items-center",\n            img {{ src: "static/futurewei_bw.png" }}\n         }}\n         div {{ class: "h-12 w-40 p-2 m-4 flex justify-center items-center",\n            img {{ src: "static/airbuslogo.svg" }}\n         }}\n         div {{ class: "h-12 w-40 p-2 m-4 flex justify-center items-center",\n            img {{ src: "static/ESA_logo.svg" }}\n         }}\n         div {{ class: "h-12 w-40 p-2 m-4 flex justify-center items-center",\n            img {{ src: "static/yclogo.svg" }}\n         }}\n         div {{ class: "h-12 w-40 p-2 m-4 flex justify-center items-center",\n            img {{ src: "static/satellite.webp" }}\n         }}\n      }}\n   }}\n}}
\n", + } + h2 { id: "usage", + Link { + to: BookRoute::CliTranslate { + section: CliTranslateSection::Usage, + }, + class: "header", + "Usage" + } + } + p { + "The " + code { "dx translate" } + " command has several flags you can use to control your html input and rsx output." + } + p { + "You can use the " + code { "--file" } + " flag to translate an HTML file to RSX:" + } + CodeBlock { contents: "
\ndx translate --file index.html
\n" } + p { + "Or you can use the " + code { "--raw" } + " flag to translate a string of HTML to RSX:" + } + CodeBlock { contents: "
\ndx translate --raw "<div>Hello world</div>"
\n" } + p { "Both of those commands will output the following RSX:" } + CodeBlock { contents: "
\ndiv {{ "Hello world" }}
\n" } + p { + "The " + code { "dx translate" } + " command will output the RSX to stdout. You can use the " + code { "--output" } + " flag to write the RSX to a file instead." + } + CodeBlock { contents: "
\ndx translate --raw "<div>Hello world</div>" --output index.rs
\n" } + p { + "You can automatically create a component with the " + code { "--component" } + " flag." + } + CodeBlock { contents: "
\ndx translate --raw "<div>Hello world</div>" --component
\n" } + p { "This will output the following component:" } + CodeBlock { contents: "
\nfn component() -> Element {{\n   rsx! {{\n      div {{ "Hello world" }}\n   }}\n}}
\n" } + p { + "To learn more about the different flags " + code { "dx translate" } + " supports, run " + code { "dx translate --help" } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ContributingIndexSection { + #[default] + Empty, + Contributing, + ImprovingDocs, + WorkingOnTheEcosystem, + BugsFeatures, + BeforeYouContribute, + HowToTestDioxusWithLocalCrate, +} +impl std::str::FromStr for ContributingIndexSection { + type Err = ContributingIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "contributing" => Ok(Self::Contributing), + "improving-docs" => Ok(Self::ImprovingDocs), + "working-on-the-ecosystem" => Ok(Self::WorkingOnTheEcosystem), + "bugs--features" => Ok(Self::BugsFeatures), + "before-you-contribute" => Ok(Self::BeforeYouContribute), + "how-to-test-dioxus-with-local-crate" => Ok(Self::HowToTestDioxusWithLocalCrate), + _ => Err(ContributingIndexSectionParseError), + } + } +} +impl std::fmt::Display for ContributingIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Contributing => f.write_str("contributing"), + Self::ImprovingDocs => f.write_str("improving-docs"), + Self::WorkingOnTheEcosystem => f.write_str("working-on-the-ecosystem"), + Self::BugsFeatures => f.write_str("bugs--features"), + Self::BeforeYouContribute => f.write_str("before-you-contribute"), + Self::HowToTestDioxusWithLocalCrate => { + f.write_str("how-to-test-dioxus-with-local-crate") + } + } + } +} +#[derive(Debug)] +pub struct ContributingIndexSectionParseError; +impl std::fmt::Display for ContributingIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ContributingIndexSectioncontributing, improving-docs, working-on-the-ecosystem, bugs--features, before-you-contribute, how-to-test-dioxus-with-local-crate", + )?; + Ok(()) + } +} +impl std::error::Error for ContributingIndexSectionParseError {} +#[component(no_case_check)] +pub fn ContributingIndex(section: ContributingIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "contributing", + Link { + to: BookRoute::ContributingIndex { + section: ContributingIndexSection::Contributing, + }, + class: "header", + "Contributing" + } + } + p { + "Development happens in the " + Link { to: "https://github.com/DioxusLabs/dioxus", "Dioxus GitHub repository" } + ". If you've found a bug or have an idea for a feature, please submit an issue (but first check if someone hasn't " + Link { to: "https://github.com/DioxusLabs/dioxus/issues", "done it already" } + ")." + } + p { + Link { to: "https://github.com/DioxusLabs/dioxus/discussions", "GitHub discussions" } + " can be used as a place to ask for help or talk about features. You can also join " + Link { to: "https://discord.gg/XgGxMSkvUM", "our Discord channel" } + " where some development discussion happens." + } + h2 { id: "improving-docs", + Link { + to: BookRoute::ContributingIndex { + section: ContributingIndexSection::ImprovingDocs, + }, + class: "header", + "Improving Docs" + } + } + p { + "If you'd like to improve the docs, PRs are welcome! The Rust docs (" + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages", "source" } + ") and this guide (" + Link { to: "https://github.com/DioxusLabs/docsite/tree/main/docs-src/0.5/en", + "source" + } + ") can be found in their respective GitHub repos." + } + h2 { id: "working-on-the-ecosystem", + Link { + to: BookRoute::ContributingIndex { + section: ContributingIndexSection::WorkingOnTheEcosystem, + }, + class: "header", + "Working on the Ecosystem" + } + } + p { + "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 many people would benefit from, it will be appreciated. You can " + Link { to: "https://www.npmjs.com/search?q=keywords:react-component", "browse npm.js" } + " for inspiration. Once you are done, add your library to the " + Link { to: "https://github.com/DioxusLabs/awesome-dioxus", "awesome dioxus" } + " list or share it in the " + code { "#I-made-a-thing" } + " channel on " + Link { to: "https://discord.gg/XgGxMSkvUM", "Discord" } + "." + } + h2 { id: "bugs--features", + Link { + to: BookRoute::ContributingIndex { + section: ContributingIndexSection::BugsFeatures, + }, + class: "header", + "Bugs & Features" + } + } + p { + "If you've fixed " + Link { to: "https://github.com/DioxusLabs/dioxus/issues", "an open issue" } + ", feel free to submit a PR! You can also take a look at " + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Empty, + }, + "the roadmap" + } + " and work on something in there. Consider " + Link { to: "https://discord.gg/XgGxMSkvUM", "reaching out" } + " to the team first to make sure everyone's on the same page, and you don't do useless work!" + } + p { + "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." + } + h2 { id: "before-you-contribute", + Link { + to: BookRoute::ContributingIndex { + section: ContributingIndexSection::BeforeYouContribute, + }, + class: "header", + "Before you contribute" + } + } + p { + "You might be surprised that a lot of checks fail when making your first PR." + " " + "That's why you should first run these commands before contributing, and it will save you " + em { "lots" } + " of time, because the" + " " + "GitHub CI is much slower at executing all of these than your PC." + } + ul { + li { + "Format code with " + Link { to: "https://github.com/rust-lang/rustfmt", "rustfmt" } + ":" + } + } + CodeBlock { contents: "
\ncargo fmt -- src/**/**.rs
\n" } + ul { + li { + "You might need to install some packages on Linux (Ubuntu/deb) before the following commands will complete successfully (there is also a Nix flake in the repo root):" + } + } + CodeBlock { + contents: "
\nsudo apt install libgdk3.0-cil libatk1.0-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libsoup-3.0-dev libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev
\n", + } + ul { + li { + "Check all code " + Link { to: "https://doc.rust-lang.org/cargo/commands/cargo-check.html", + "cargo check" + } + ":" + } + } + CodeBlock { contents: "
\ncargo check --workspace --examples --tests
\n" } + ul { + li { + "Check if " + Link { to: "https://doc.rust-lang.org/clippy/", "Clippy" } + " generates any warnings. Please fix these!" + } + } + CodeBlock { contents: "
\ncargo clippy --workspace --examples --tests -- -D warnings
\n" } + ul { + li { + "Test all code with " + Link { to: "https://doc.rust-lang.org/cargo/commands/cargo-test.html", + "cargo-test" + } + ":" + } + } + CodeBlock { contents: "
\ncargo test --all --tests
\n" } + ul { + li { + "More tests, this time with " + Link { to: "https://sagiegurari.github.io/cargo-make/", "cargo-make" } + ". Here are all steps, including installation:" + } + } + CodeBlock { contents: "
\ncargo install --force cargo-make\ncargo make tests
\n" } + ul { + li { + "Test unsafe crates with " + Link { to: "https://github.com/rust-lang/miri", "MIRI" } + ". Currently, this is used for the two MIRI tests in " + code { "dioxus-core" } + " and " + code { "dioxus-native-core" } + ":" + } + } + CodeBlock { contents: "
\ncargo miri test --package dioxus-core --test miri_stress\ncargo miri test --package dioxus-native-core --test miri_native
\n" } + ul { + li { + "Test with Playwright. This tests the UI itself, right in a browser. Here are all steps, including installation:" + strong { + "Disclaimer: This might inexplicably fail on your machine without it being your fault." + } + " Make that PR anyway!" + } + } + CodeBlock { contents: "
\ncd playwright-tests\nnpm ci\nnpm install -D @playwright/test\nnpx playwright install --with-deps\nnpx playwright test
\n" } + h2 { id: "how-to-test-dioxus-with-local-crate", + Link { + to: BookRoute::ContributingIndex { + section: ContributingIndexSection::HowToTestDioxusWithLocalCrate, + }, + class: "header", + "How to test dioxus with local crate" + } + } + p { + "If you are developing a feature, you should test it in your local setup before raising a PR. This process makes sure you are aware of your code functionality before being reviewed by peers." + } + ul { + li { "Fork the following github repo (DioxusLabs/dioxus):" } + } + p { + code { "https://github.com/DioxusLabs/dioxus" } + } + ul { + li { + "Create a new or use an existing rust crate (ignore this step if you will use an existing rust crate):" + " " + "This is where we will be testing the features of the forked" + } + } + CodeBlock { contents: "
\ncargo new --bin demo
\n" } + ul { + li { "Add the dioxus dependency to your rust crate (new/existing) in Cargo.toml:" } + } + CodeBlock { contents: "
\ndioxus = {{ path = "<path to forked dioxus project>/dioxus/packages/dioxus", features = ["web", "router"] }}
\n" } + p { + "This above example is for dioxus-web, with dioxus-router. To know about the dependencies for different renderer visit " + Link { to: "https://dioxuslabs.com/learn/0.5/getting_started", "here" } + "." + } + ul { + li { "Run and test your feature" } + } + CodeBlock { contents: "
\ndx serve
\n" } + p { + "If this is your first time with dioxus, please read " + Link { to: "https://dioxuslabs.com/learn/0.5/guide", "the guide" } + " to get familiar with dioxus." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ContributingProjectStructureSection { + #[default] + Empty, + ProjectStructure, + Renderers, + StateManagementhooks, + CoreUtilities, + NativeRendererUtilities, + WebRendererTooling, + DeveloperTooling, +} +impl std::str::FromStr for ContributingProjectStructureSection { + type Err = ContributingProjectStructureSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "project-structure" => Ok(Self::ProjectStructure), + "renderers" => Ok(Self::Renderers), + "state-managementhooks" => Ok(Self::StateManagementhooks), + "core-utilities" => Ok(Self::CoreUtilities), + "native-renderer-utilities" => Ok(Self::NativeRendererUtilities), + "web-renderer-tooling" => Ok(Self::WebRendererTooling), + "developer-tooling" => Ok(Self::DeveloperTooling), + _ => Err(ContributingProjectStructureSectionParseError), + } + } +} +impl std::fmt::Display for ContributingProjectStructureSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::ProjectStructure => f.write_str("project-structure"), + Self::Renderers => f.write_str("renderers"), + Self::StateManagementhooks => f.write_str("state-managementhooks"), + Self::CoreUtilities => f.write_str("core-utilities"), + Self::NativeRendererUtilities => f.write_str("native-renderer-utilities"), + Self::WebRendererTooling => f.write_str("web-renderer-tooling"), + Self::DeveloperTooling => f.write_str("developer-tooling"), + } + } +} +#[derive(Debug)] +pub struct ContributingProjectStructureSectionParseError; +impl std::fmt::Display for ContributingProjectStructureSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ContributingProjectStructureSectionproject-structure, renderers, state-managementhooks, core-utilities, native-renderer-utilities, web-renderer-tooling, developer-tooling", + )?; + Ok(()) + } +} +impl std::error::Error for ContributingProjectStructureSectionParseError {} +#[component(no_case_check)] +pub fn ContributingProjectStructure( + section: ContributingProjectStructureSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "project-structure", + Link { + to: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::ProjectStructure, + }, + class: "header", + "Project Structure" + } + } + p { + "There are many packages in the Dioxus organization. This document will help you understand the purpose of each package and how they fit together." + } + h2 { id: "renderers", + Link { + to: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::Renderers, + }, + class: "header", + "Renderers" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/desktop", + "Desktop" + } + ": A Render that Runs Dioxus applications natively, but renders them with the system webview" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/mobile", + "Mobile" + } + ": A Render that Runs Dioxus applications natively, but renders them with the system webview. This is currently a copy of the desktop render" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/web", + "Web" + } + ": Renders Dioxus applications in the browser by compiling to WASM and manipulating the DOM" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/liveview", + "Liveview" + } + ": A Render that Runs on the server, and renders using a websocket proxy in the browser" + } + li { + Link { to: "https://github.com/DioxusLabs/blitz/tree/master/packages/plasmo", + "Plasmo" + } + ": A Renderer that renders a HTML-like tree into a terminal" + } + li { + Link { to: "https://github.com/DioxusLabs/blitz/tree/master/packages/dioxus-tui", + "TUI" + } + ": A Renderer that uses Plasmo to render a Dioxus application in a terminal" + } + li { + Link { to: "https://github.com/DioxusLabs/blitz/tree/master/packages/blitz-core", + "Blitz-Core" + } + ": An experimental native renderer that renders a HTML-like tree using WGPU." + } + li { + Link { to: "https://github.com/DioxusLabs/blitz", "Blitz" } + ": An experimental native renderer that uses Blitz-Core to render a Dioxus application using WGPU." + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/ssr", + "SSR" + } + ": A Render that Runs Dioxus applications on the server, and renders them to HTML" + } + } + h2 { id: "state-managementhooks", + Link { + to: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::StateManagementhooks, + }, + class: "header", + "State Management/Hooks" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/hooks", + "Hooks" + } + ": A collection of common hooks for Dioxus applications" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/signals", + "Signals" + } + ": A experimental state management library for Dioxus applications. This currently contains a " + code { "Copy" } + " version of Signal" + } + li { + Link { to: "https://github.com/DioxusLabs/sdk", "SDK" } + ": A collection of platform agnostic hooks to interact with system interfaces (The clipboard, camera, etc.)." + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/fermi", + "Fermi" + } + ": A global state management library for Dioxus applications." + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/router", + "Router" + } + ": A client-side router for Dioxus applications" + } + } + h2 { id: "core-utilities", + Link { + to: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::CoreUtilities, + }, + class: "header", + "Core utilities" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/core", + "core" + } + ": The core virtual dom implementation every Dioxus application uses" + ul { + li { + "You can read more about the architecture of the core " + Link { to: "https://dioxuslabs.com/blog/templates-diffing/", + "in this blog post" + } + " and the " + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::Empty, + }, + "custom renderer section of the guide" + } + } + } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/rsx", + "RSX" + } + ": The core parsing for RSX used for hot reloading, autoformatting, and the macro" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/core-macro", + "core-macro" + } + ": The rsx! macro used to write Dioxus applications. (This is a wrapper over the RSX crate)" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus-html-macro", "HTML macro" } + ": A html-like alternative to the RSX macro" + } + } + h2 { id: "native-renderer-utilities", + Link { + to: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::NativeRendererUtilities, + }, + class: "header", + "Native Renderer Utilities" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/blitz/tree/main/packages/native-core", + "native-core" + } + ": Incrementally computed tree of states (mostly styles)" + ul { + li { + "You can read more about how native-core can help you build native renderers in the " + Link { + to: BookRoute::CookbookCustomRenderer { + section: CookbookCustomRendererSection::NativeCore, + }, + "custom renderer section of the guide" + } + } + } + } + li { + Link { to: "https://github.com/DioxusLabs/blitz/tree/main/packages/native-core-macro", + "native-core-macro" + } + ": A helper macro for native core" + } + li { + Link { to: "https://github.com/DioxusLabs/taffy", "Taffy" } + ": Layout engine powering Blitz-Core, Plasmo, and Bevy UI" + } + } + h2 { id: "web-renderer-tooling", + Link { + to: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::WebRendererTooling, + }, + class: "header", + "Web renderer tooling" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/html", + "HTML" + } + ": defines html specific elements, events, and attributes" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/interpreter", + "Interpreter" + } + ": defines browser bindings used by the web and desktop renderers" + } + } + h2 { id: "developer-tooling", + Link { + to: BookRoute::ContributingProjectStructure { + section: ContributingProjectStructureSection::DeveloperTooling, + }, + class: "header", + "Developer tooling" + } + } + ul { + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/hot-reload", + "hot-reload" + } + ": Macro that uses the RSX crate to hot reload static parts of any rsx! macro. This macro works with any non-web renderer with an " + Link { to: "https://crates.io/crates/dioxus-hot-reload", "integration" } + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/autofmt", + "autofmt" + } + ": Formats RSX code" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/rsx-rosetta", + "rsx-rosetta" + } + ": Handles conversion between HTML and RSX" + } + li { + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/cli", + "CLI" + } + ": A Command Line Interface and VSCode extension to assist with Dioxus usage" + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ContributingGuidingPrinciplesSection { + #[default] + Empty, + OverallGoals, + CrossPlatform, + Performance, + TypeSafety, + DeveloperExperience, +} +impl std::str::FromStr for ContributingGuidingPrinciplesSection { + type Err = ContributingGuidingPrinciplesSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "overall-goals" => Ok(Self::OverallGoals), + "cross-platform" => Ok(Self::CrossPlatform), + "performance" => Ok(Self::Performance), + "type-safety" => Ok(Self::TypeSafety), + "developer-experience" => Ok(Self::DeveloperExperience), + _ => Err(ContributingGuidingPrinciplesSectionParseError), + } + } +} +impl std::fmt::Display for ContributingGuidingPrinciplesSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::OverallGoals => f.write_str("overall-goals"), + Self::CrossPlatform => f.write_str("cross-platform"), + Self::Performance => f.write_str("performance"), + Self::TypeSafety => f.write_str("type-safety"), + Self::DeveloperExperience => f.write_str("developer-experience"), + } + } +} +#[derive(Debug)] +pub struct ContributingGuidingPrinciplesSectionParseError; +impl std::fmt::Display for ContributingGuidingPrinciplesSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ContributingGuidingPrinciplesSectionoverall-goals, cross-platform, performance, type-safety, developer-experience", + )?; + Ok(()) + } +} +impl std::error::Error for ContributingGuidingPrinciplesSectionParseError {} +#[component(no_case_check)] +pub fn ContributingGuidingPrinciples( + section: ContributingGuidingPrinciplesSection, +) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "overall-goals", + Link { + to: BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::OverallGoals, + }, + class: "header", + "Overall Goals" + } + } + p { + "This document outlines some of the overall goals for Dioxus. These goals are not set in stone, but they represent general guidelines for the project." + } + p { + "The goal of Dioxus is to make it easy to build " + strong { "cross-platform applications that scale" } + "." + } + h2 { id: "cross-platform", + Link { + to: BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::CrossPlatform, + }, + class: "header", + "Cross-Platform" + } + } + p { + "Dioxus is designed to be cross-platform by default. This means that it should be easy to build applications that run on the web, desktop, and mobile. However, Dioxus should also be flexible enough to allow users to opt into platform-specific features when needed. The " + code { "use_eval" } + " is one example of this. By default, Dioxus does not assume that the platform supports JavaScript, but it does provide a hook that allows users to opt into JavaScript when needed." + } + h2 { id: "performance", + Link { + to: BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::Performance, + }, + class: "header", + "Performance" + } + } + p { + "As Dioxus applications grow, they should remain relatively performant without the need for manual optimizations. There will be cases where manual optimizations are needed, but Dioxus should try to make these cases as rare as possible." + } + p { + "One of the benefits of the core architecture of Dioxus is that it delivers reasonable performance even when components are rerendered often. It is based on a Virtual Dom which performs diffing which should prevent unnecessary re-renders even when large parts of the component tree are rerun. On top of this, Dioxus groups static parts of the RSX tree together to skip diffing them entirely." + } + h2 { id: "type-safety", + Link { + to: BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::TypeSafety, + }, + class: "header", + "Type Safety" + } + } + p { + "As teams grow, the Type safety of Rust is a huge advantage. Dioxus should leverage this advantage to make it easy to build applications with large teams." + } + p { + "To take full advantage of Rust's type system, Dioxus should try to avoid exposing public " + code { "Any" } + " types and string-ly typed APIs where possible." + } + h2 { id: "developer-experience", + Link { + to: BookRoute::ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection::DeveloperExperience, + }, + class: "header", + "Developer Experience" + } + } + p { "Dioxus should be easy to learn and ergonomic to use." } + ul { + li { + p { + "The API of Dioxus attempts to remain close to React's API where possible. This makes it easier for people to learn Dioxus if they already know React" + } + } + li { + p { + "We can avoid the tradeoff between simplicity and flexibility by providing multiple layers of API: One for the very common use case, one for low-level control" + } + ul { + li { + "Hooks: the hooks crate has the most common use cases, but " + code { "use_hook" } + " provides a way to access the underlying persistent value if needed." + } + li { + "The builder pattern in platform Configs: The builder pattern is used to default to the most common use case, but users can change the defaults if needed." + } + } + } + li { + p { "Documentation:" } + ul { + li { "All public APIs should have rust documentation" } + li { + "Examples should be provided for all public features. These examples both serve as documentation and testing. They are checked by CI to ensure that they continue to compile" + } + li { "The most common workflows should be documented in the guide" } + } + } + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum ContributingRoadmapSection { + #[default] + Empty, + RoadmapFeatureSet, + Features, + Roadmap, + Core, + Ssr, + Desktop, + Mobile, + BundlingCli, + EssentialHooks, + WorkInProgress, + BuildTool, + ServerComponentSupport, + NativeRendering, +} +impl std::str::FromStr for ContributingRoadmapSection { + type Err = ContributingRoadmapSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "roadmap--feature-set" => Ok(Self::RoadmapFeatureSet), + "features" => Ok(Self::Features), + "roadmap" => Ok(Self::Roadmap), + "core" => Ok(Self::Core), + "ssr" => Ok(Self::Ssr), + "desktop" => Ok(Self::Desktop), + "mobile" => Ok(Self::Mobile), + "bundling-cli" => Ok(Self::BundlingCli), + "essential-hooks" => Ok(Self::EssentialHooks), + "work-in-progress" => Ok(Self::WorkInProgress), + "build-tool" => Ok(Self::BuildTool), + "server-component-support" => Ok(Self::ServerComponentSupport), + "native-rendering" => Ok(Self::NativeRendering), + _ => Err(ContributingRoadmapSectionParseError), + } + } +} +impl std::fmt::Display for ContributingRoadmapSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::RoadmapFeatureSet => f.write_str("roadmap--feature-set"), + Self::Features => f.write_str("features"), + Self::Roadmap => f.write_str("roadmap"), + Self::Core => f.write_str("core"), + Self::Ssr => f.write_str("ssr"), + Self::Desktop => f.write_str("desktop"), + Self::Mobile => f.write_str("mobile"), + Self::BundlingCli => f.write_str("bundling-cli"), + Self::EssentialHooks => f.write_str("essential-hooks"), + Self::WorkInProgress => f.write_str("work-in-progress"), + Self::BuildTool => f.write_str("build-tool"), + Self::ServerComponentSupport => f.write_str("server-component-support"), + Self::NativeRendering => f.write_str("native-rendering"), + } + } +} +#[derive(Debug)] +pub struct ContributingRoadmapSectionParseError; +impl std::fmt::Display for ContributingRoadmapSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of ContributingRoadmapSectionroadmap--feature-set, features, roadmap, core, ssr, desktop, mobile, bundling-cli, essential-hooks, work-in-progress, build-tool, server-component-support, native-rendering", + )?; + Ok(()) + } +} +impl std::error::Error for ContributingRoadmapSectionParseError {} +#[component(no_case_check)] +pub fn ContributingRoadmap(section: ContributingRoadmapSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "roadmap--feature-set", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::RoadmapFeatureSet, + }, + class: "header", + "Roadmap & Feature-set" + } + } + p { + "This feature set and roadmap can help you decide if what Dioxus can do today works for you." + } + p { + "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 " + Link { to: "https://discord.gg/XgGxMSkvUM", "joining the discord" } + "." + } + p { "Generally, here's the status of each platform:" } + ul { + li { + p { + strong { "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." + } + } + li { + p { + strong { "SSR" } + ": Dioxus is a great choice for pre-rendering, hydration, and rendering HTML on a web endpoint. Be warned – the VirtualDom is not (currently) " + code { "Send + Sync" } + "." + } + } + li { + p { + strong { "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." + } + } + li { + p { + strong { "Mobile" } + ": Mobile support is very young. You'll be figuring things out as you go and there are not many support crates for peripherals." + } + } + li { + p { + strong { "LiveView" } + ": LiveView support is very young. You'll be figuring things out as you go. Thankfully, none of it is too hard and any work can be upstreamed into Dioxus." + } + } + } + h2 { id: "features", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Features, + }, + class: "header", + "Features" + } + } + hr {} + table { + thead { + th { "Feature" } + th { "Status" } + th { "Description" } + } + tr { + th { "Conditional Rendering" } + th { "x" } + th { "if/then to hide/show component" } + } + tr { + th { "Map, Iterator" } + th { "x" } + th { "map/filter/reduce to produce rsx!" } + } + tr { + th { "Keyed Components" } + th { "x" } + th { "advanced diffing with keys" } + } + tr { + th { "Web" } + th { "x" } + th { "renderer for web browser" } + } + tr { + th { "Desktop (webview)" } + th { "x" } + th { "renderer for desktop" } + } + tr { + th { "Shared State (Context)" } + th { "x" } + th { "share state through the tree" } + } + tr { + th { "Hooks" } + th { "x" } + th { "memory cells in components" } + } + tr { + th { "SSR" } + th { "x" } + th { "render directly to string" } + } + tr { + th { "Component Children" } + th { "x" } + th { "cx.children() as a list of nodes" } + } + tr { + th { "Headless components" } + th { "x" } + th { "components that don't return real elements" } + } + tr { + th { "Fragments" } + th { "x" } + th { "multiple elements without a real root" } + } + tr { + th { "Manual Props" } + th { "x" } + th { "Manually pass in props with spread syntax" } + } + tr { + th { "Controlled Inputs" } + th { "x" } + th { "stateful wrappers around inputs" } + } + tr { + th { "CSS/Inline Styles" } + th { "x" } + th { "syntax for inline styles/attribute groups" } + } + tr { + th { "Custom elements" } + th { "x" } + th { "Define new element primitives" } + } + tr { + th { "Suspense" } + th { "x" } + th { "schedule future render from future/promise" } + } + tr { + th { "Integrated error handling" } + th { "x" } + th { "Gracefully handle errors with ? syntax" } + } + tr { + th { "NodeRef" } + th { "x" } + th { "gain direct access to nodes" } + } + tr { + th { "Re-hydration" } + th { "x" } + th { "Pre-render to HTML to speed up first contentful paint" } + } + tr { + th { "Jank-Free Rendering" } + th { "x" } + th { "Large diffs are segmented across frames for silky-smooth transitions" } + } + tr { + th { "Effects" } + th { "x" } + th { "Run effects after a component has been committed to render" } + } + tr { + th { "Portals" } + th { "*" } + th { "Render nodes outside of the traditional tree structure" } + } + tr { + th { "Cooperative Scheduling" } + th { "*" } + th { "Prioritize important events over non-important events" } + } + tr { + th { "Server Components" } + th { "*" } + th { "Hybrid components for SPA and Server" } + } + tr { + th { "Bundle Splitting" } + th { "i" } + th { "Efficiently and asynchronously load the app" } + } + tr { + th { "Lazy Components" } + th { "i" } + th { "Dynamically load the new components as the page is loaded" } + } + tr { + th { "1st class global state" } + th { "x" } + th { "redux/recoil/mobx on top of context" } + } + tr { + th { "Runs natively" } + th { "x" } + th { "runs as a portable binary w/o a runtime (Node)" } + } + tr { + th { "Subtree Memoization" } + th { "x" } + th { "skip diffing static element subtrees" } + } + tr { + th { "High-efficiency templates" } + th { "x" } + th { "rsx! calls are translated to templates on the DOM's side" } + } + tr { + th { "Compile-time correct" } + th { "x" } + th { "Throw errors on invalid template layouts" } + } + tr { + th { "Heuristic Engine" } + th { "x" } + th { "track component memory usage to minimize future allocations" } + } + tr { + th { "Fine-grained reactivity" } + th { "i" } + th { "Skip diffing for fine-grain updates" } + } + } + ul { + li { "x = implemented and working" } + li { "* = actively being worked on" } + li { "i = not yet implemented or being worked on" } + } + h2 { id: "roadmap", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Roadmap, + }, + class: "header", + "Roadmap" + } + } + p { "These Features are planned for the future of Dioxus:" } + h3 { id: "core", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Core, + }, + class: "header", + "Core" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Release of Dioxus Core" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Upgrade documentation to include more theory and be more comprehensive" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Support for HTML-side templates for lightning-fast dom manipulation" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Support for multiple renderers for same virtualdom (subtrees)" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Support for ThreadSafe (Send + Sync)" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Support for Portals" + } + } + h3 { id: "ssr", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Ssr, + }, + class: "header", + "SSR" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "SSR Support + Hydration" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Integrated suspense support for SSR" + } + } + h3 { id: "desktop", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Desktop, + }, + class: "header", + "Desktop" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Declarative window management" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Templates for building/bundling" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Access to Canvas/WebGL context natively" + } + } + h3 { id: "mobile", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::Mobile, + }, + class: "header", + "Mobile" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Mobile standard library" + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "GPS" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Camera" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "filesystem" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Biometrics" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "WiFi" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Bluetooth" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Notifications" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Clipboard" + } + } + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Animations" + } + } + h3 { id: "bundling-cli", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::BundlingCli, + }, + class: "header", + "Bundling (CLI)" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Translation from HTML into RSX" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Dev server" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Live reload" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Translation from JSX into RSX" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Hot module replacement" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Code splitting" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Asset macros" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Css pipeline" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Image pipeline" + } + } + h3 { id: "essential-hooks", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::EssentialHooks, + }, + class: "header", + "Essential hooks" + } + } + ul { + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Router" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "true", + } + "Global state management" + } + li { + input { + r#type: "checkbox", + readonly: true, + class: "mdbook-checkbox", + value: "false", + } + "Resize observer" + } + } + h2 { id: "work-in-progress", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::WorkInProgress, + }, + class: "header", + "Work in Progress" + } + } + h3 { id: "build-tool", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::BuildTool, + }, + class: "header", + "Build Tool" + } + } + p { + "We are currently working on our own build tool called " + Link { to: "https://github.com/DioxusLabs/dioxus/tree/main/packages/cli", + "Dioxus CLI" + } + " which will support:" + } + ul { + li { "an interactive TUI" } + li { "on-the-fly reconfiguration" } + li { "hot CSS reloading" } + li { "two-way data binding between browser and source code" } + li { + "an interpreter for " + code { "rsx!" } + } + li { "ability to publish to github/netlify/vercel" } + li { "bundling for iOS/Desktop/etc" } + } + h3 { id: "server-component-support", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::ServerComponentSupport, + }, + class: "header", + "Server Component Support" + } + } + p { + "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." + } + h3 { id: "native-rendering", + Link { + to: BookRoute::ContributingRoadmap { + section: ContributingRoadmapSection::NativeRendering, + }, + class: "header", + "Native rendering" + } + } + p { + "We are currently working on a native renderer for Dioxus using WGPU called " + Link { to: "https://github.com/DioxusLabs/blitz/", "Blitz" } + ". This will allow you to build apps that are rendered natively for iOS, Android, and Desktop." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum MigrationIndexSection { + #[default] + Empty, + HowToUpgradeToDioxus05, + CheatSheet, + Scope, + Props, + Futures, + StateHooks, + Fermi, +} +impl std::str::FromStr for MigrationIndexSection { + type Err = MigrationIndexSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "how-to-upgrade-to-dioxus-05" => Ok(Self::HowToUpgradeToDioxus05), + "cheat-sheet" => Ok(Self::CheatSheet), + "scope" => Ok(Self::Scope), + "props" => Ok(Self::Props), + "futures" => Ok(Self::Futures), + "state-hooks" => Ok(Self::StateHooks), + "fermi" => Ok(Self::Fermi), + _ => Err(MigrationIndexSectionParseError), + } + } +} +impl std::fmt::Display for MigrationIndexSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::HowToUpgradeToDioxus05 => f.write_str("how-to-upgrade-to-dioxus-05"), + Self::CheatSheet => f.write_str("cheat-sheet"), + Self::Scope => f.write_str("scope"), + Self::Props => f.write_str("props"), + Self::Futures => f.write_str("futures"), + Self::StateHooks => f.write_str("state-hooks"), + Self::Fermi => f.write_str("fermi"), + } + } +} +#[derive(Debug)] +pub struct MigrationIndexSectionParseError; +impl std::fmt::Display for MigrationIndexSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of MigrationIndexSectionhow-to-upgrade-to-dioxus-05, cheat-sheet, scope, props, futures, state-hooks, fermi", + )?; + Ok(()) + } +} +impl std::error::Error for MigrationIndexSectionParseError {} +#[component(no_case_check)] +pub fn MigrationIndex(section: MigrationIndexSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "how-to-upgrade-to-dioxus-05", + Link { + to: BookRoute::MigrationIndex { + section: MigrationIndexSection::HowToUpgradeToDioxus05, + }, + class: "header", + "How to Upgrade to Dioxus 0.5" + } + } + p { + "This guide will outline the API changes between the " + code { "0.4" } + " and " + code { "0.5" } + " releases." + } + p { + code { "0.5" } + " has includes significant changes to hooks, props, and global state." + } + h2 { id: "cheat-sheet", + Link { + to: BookRoute::MigrationIndex { + section: MigrationIndexSection::CheatSheet, + }, + class: "header", + "Cheat Sheet" + } + } + p { "Here is a quick cheat sheet for the changes:" } + h3 { id: "scope", + Link { + to: BookRoute::MigrationIndex { + section: MigrationIndexSection::Scope, + }, + class: "header", + "Scope" + } + } + p { "Dioxus 0.4:" } + CodeBlock { + contents: "
\nfn app(cx: Scope) -> Element {{\n    cx.use_hook(|| {{\n        /*...*/\n    }});\n    cx.provide_context({{\n        /*...*/\n    }});\n    cx.spawn(async move {{\n        /*...*/\n    }});\n    cx.render(rsx! {{\n        /*...*/\n    }})\n}}
\n", + } + p { "Dioxus 0.5:" } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\n// In dioxus 0.5, the scope is no longer passed as an argument to the function\nfn app() -> Element {{\n    // Hooks, context, and spawn are now called directly\n    use_hook(|| {{ /*...*/ }});\n    provide_context({{ /*...*/ }});\n    spawn(async move {{ /*...*/ }});\n    rsx! {{\n        /*...*/\n    }}\n}}
\n", + name: "migration.rs".to_string(), + } + h3 { id: "props", + Link { + to: BookRoute::MigrationIndex { + section: MigrationIndexSection::Props, + }, + class: "header", + "Props" + } + } + p { "Dioxus 0.4:" } + CodeBlock { + contents: "
\n#[component]\nfn Comp(cx: Scope, name: String) -> Element {{\n    // You pass in an owned prop, but inside the component, it is borrowed (name is the type &String inside the function)\n    let owned_name: String = name.clone();\n\n    cx.render(rsx! {{\n        "Hello {{owned_name}}"\n        BorrowedComp {{\n            "{{name}}"\n        }}\n        ManualPropsComponent {{\n            name: name\n        }}\n    }})\n}}\n\n#[component]\nfn BorrowedComp<'a>(cx: Scope<'a>, name: &'a str) -> Element<'a> {{\n    cx.render(rsx! {{\n        "Hello {{name}}"\n    }})\n}}\n\n#[derive(Props, PartialEq)]\nstruct ManualProps {{\n    name: String\n}}\n\nfn ManualPropsComponent(cx: Scope<ManualProps>) -> Element {{\n    cx.render(rsx! {{\n        "Hello {{cx.props.name}}"\n    }})\n}}
\n", + } + p { "Dioxus 0.5:" } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\n// In dioxus 0.5, props are always owned. You pass in owned props and you get owned props in the body of the component\n#[component]\nfn Comp(name: String) -> Element {{\n    // Name is owned here already (name is the type String inside the function)\n    let owned_name: String = name;\n\n    rsx! {{\n        "Hello {{owned_name}}"\n        BorrowedComp {{\n            name: "other name"\n        }}\n        ManualPropsComponent {{\n            name: "other name 2"\n        }}\n    }}\n}}\n\n// Borrowed props are removed in dioxus 0.5. Mapped signals can act similarly to borrowed props if your props are borrowed from state\n// ReadOnlySignal is a copy wrapper over a state that will be automatically converted to\n#[component]\nfn BorrowedComp(name: ReadOnlySignal<String>) -> Element {{\n    rsx! {{\n        "Hello {{name}}"\n    }}\n}}\n\n// In dioxus 0.5, props need to implement Props, Clone, and PartialEq\n#[derive(Props, Clone, PartialEq)]\nstruct ManualProps {{\n    name: String,\n}}\n\n// Functions accept the props directly instead of the scope\nfn ManualPropsComponent(props: ManualProps) -> Element {{\n    rsx! {{\n        "Hello {{props.name}}"\n    }}\n}}
\n", + name: "migration.rs".to_string(), + } + p { + "You can read more about the new props API in the " + Link { + to: BookRoute::MigrationProps { + section: MigrationPropsSection::Empty, + }, + "Props Migration" + } + " guide." + } + h3 { id: "futures", + Link { + to: BookRoute::MigrationIndex { + section: MigrationIndexSection::Futures, + }, + class: "header", + "Futures" + } + } + p { "Dioxus 0.4:" } + CodeBlock { contents: "
\nuse_future((dependency1, dependency2,), move |(dependency1, dependency2,)| async move {{\n\t/*use dependency1 and dependency2*/\n}});
\n" } + p { "Dioxus 0.5:" } + CodeBlock { + contents: "
\n// dependency1 and dependency2 must be Signal-like types like Signal, ReadOnlySignal, GlobalSignal, or another Resource\nuse_resource(|| async move {{ /*use dependency1 and dependency2*/ }});\n\nlet non_reactive_state = 0;\n// You can also add non-reactive state to the resource hook with the use_reactive macro\nuse_resource(use_reactive!(|(non_reactive_state,)| async move {{\n    tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n    non_reactive_state + 1\n}}));
\n", + name: "migration.rs".to_string(), + } + p { + "Read more about the " + code { "use_resource" } + " hook in the " + Link { + to: BookRoute::MigrationHooks { + section: MigrationHooksSection::Empty, + }, + "Hook Migration" + } + " guide." + } + h3 { id: "state-hooks", + Link { + to: BookRoute::MigrationIndex { + section: MigrationIndexSection::StateHooks, + }, + class: "header", + "State Hooks" + } + } + p { "Dioxus 0.4:" } + CodeBlock { + contents: "
\nlet copy_state = use_state(cx, || 0);\nlet clone_local_state = use_ref(cx, || String::from("Hello"));\nuse_shared_state_provider(cx, || String::from("Hello"));\nlet clone_shared_state = use_shared_state::<String>(cx);\n\nlet copy_state_value = **copy_state;\nlet clone_local_state_value = clone_local_state.read();\nlet clone_shared_state_value = clone_shared_state.read();\n\ncx.render(rsx!{{\n\t"{{copy_state_value}}"\n\t"{{clone_shared_state_value}}"\n\t"{{clone_local_state_value}}"\n\tbutton {{\n\t\tonclick: move |_| {{\n\t\t\tcopy_state.set(1);\n\t\t\t*clone_local_state.write() = "World".to_string();\n\t\t\t*clone_shared_state.write() = "World".to_string();\n\t\t}},\n\t\t"Set State"\n\t}}\n}})
\n", + } + p { "Dioxus 0.5:" } + CodeBlock { + contents: "
\n// You can now use signals for local copy state, local clone state, and shared state with the same API\nlet mut copy_state = use_signal(|| 0);\nlet mut clone_shared_state = use_context_provider(|| Signal::new(String::from("Hello")));\nlet mut clone_local_state = use_signal(|| String::from("Hello"));\n\n// Call the signal like a function to clone the current value\nlet copy_state_value = copy_state();\n// Or use the read method to borrow the current value\nlet clone_local_state_value = clone_local_state.read();\nlet clone_shared_state_value = clone_shared_state.read();\n\nrsx! {{\n    "{{copy_state_value}}"\n    "{{clone_shared_state_value}}"\n    "{{clone_local_state_value}}"\n    button {{\n        onclick: move |_| {{\n            // All three states have the same API for updating the state\n            copy_state.set(1);\n            clone_shared_state.set("World".to_string());\n            clone_local_state.set("World".to_string());\n        }},\n        "Set State"\n    }}\n}}
\n", + name: "migration.rs".to_string(), + } + p { + "Read more about the " + code { "use_signal" } + " hook in the " + Link { + to: BookRoute::MigrationState { + section: MigrationStateSection::Empty, + }, + "State Migration" + } + " guide." + } + h3 { id: "fermi", + Link { + to: BookRoute::MigrationIndex { + section: MigrationIndexSection::Fermi, + }, + class: "header", + "Fermi" + } + } + p { "Dioxus 0.4:" } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\nuse fermi::*;\n\nstatic NAME: Atom<String> = Atom(|_| "world".to_string());\n\nfn app(cx: Scope) -> Element {{\n    use_init_atom_root(cx);\n    let name = use_read(cx, &NAME);\n\n    cx.render(rsx! {{\n        div {{ "hello {{name}}!" }}\n        Child {{}}\n        ChildWithRef {{}}\n    }})\n}}\n\nfn Child(cx: Scope) -> Element {{\n    let set_name = use_set(cx, &NAME);\n\n    cx.render(rsx! {{\n        button {{\n            onclick: move |_| set_name("dioxus".to_string()),\n            "reset name"\n        }}\n    }})\n}}\n\nstatic NAMES: AtomRef<Vec<String>> = AtomRef(|_| vec!["world".to_string()]);\n\nfn ChildWithRef(cx: Scope) -> Element {{\n    let names = use_atom_ref(cx, &NAMES);\n\n    cx.render(rsx! {{\n        div {{\n            ul {{\n                names.read().iter().map(|f| rsx!{{\n                    li {{ "hello: {{f}}" }}\n                }})\n            }}\n            button {{\n                onclick: move |_| {{\n                    let names = names.clone();\n                    cx.spawn(async move {{\n                        names.write().push("asd".to_string());\n                    }})\n                }},\n                "Add name"\n            }}\n        }}\n    }})\n}}
\n", + } + p { "Dioxus 0.5:" } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\n// Atoms and AtomRefs have been replaced with GlobalSignals\nstatic NAME: GlobalSignal<String> = Signal::global(|| "world".to_string());\n\nfn app() -> Element {{\n    rsx! {{\n        // You can use global state directly without the use_read or use_set hooks\n        div {{ "hello {{NAME}}!" }}\n        Child {{}}\n        ChildWithRef {{}}\n    }}\n}}\n\nfn Child() -> Element {{\n    rsx! {{\n        button {{\n            onclick: move |_| *NAME.write() = "dioxus".to_string(),\n            "reset name"\n        }}\n    }}\n}}\n\n// Atoms and AtomRefs have been replaced with GlobalSignals\nstatic NAMES: GlobalSignal<Vec<String>> = Signal::global(|| vec!["world".to_string()]);\n\nfn ChildWithRef() -> Element {{\n    rsx! {{\n        div {{\n            ul {{\n                for name in NAMES.read().iter() {{\n                    li {{ "hello: {{name}}" }}\n                }}\n            }}\n            button {{\n                onclick: move |_| {{\n                    // No need to clone the signal into futures, you can use it directly\n                    async move {{\n                        NAMES.write().push("asd".to_string());\n                    }}\n                }},\n                "Add name"\n            }}\n        }}\n    }}\n}}
\n", + name: "migration.rs".to_string(), + } + p { + "You can read more about global signals in the " + Link { + to: BookRoute::MigrationFermi { + section: MigrationFermiSection::Empty, + }, + "Fermi migration guide" + } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum MigrationHooksSection { + #[default] + Empty, + Hooks, + StateHooks, + AsyncHooks, + Dependencies, +} +impl std::str::FromStr for MigrationHooksSection { + type Err = MigrationHooksSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "hooks" => Ok(Self::Hooks), + "state-hooks" => Ok(Self::StateHooks), + "async-hooks" => Ok(Self::AsyncHooks), + "dependencies" => Ok(Self::Dependencies), + _ => Err(MigrationHooksSectionParseError), + } + } +} +impl std::fmt::Display for MigrationHooksSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Hooks => f.write_str("hooks"), + Self::StateHooks => f.write_str("state-hooks"), + Self::AsyncHooks => f.write_str("async-hooks"), + Self::Dependencies => f.write_str("dependencies"), + } + } +} +#[derive(Debug)] +pub struct MigrationHooksSectionParseError; +impl std::fmt::Display for MigrationHooksSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of MigrationHooksSectionhooks, state-hooks, async-hooks, dependencies", + )?; + Ok(()) + } +} +impl std::error::Error for MigrationHooksSectionParseError {} +#[component(no_case_check)] +pub fn MigrationHooks(section: MigrationHooksSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "hooks", + Link { + to: BookRoute::MigrationHooks { + section: MigrationHooksSection::Hooks, + }, + class: "header", + "Hooks" + } + } + p { + "Dioxus now uses signals as the backing for its state management. Signals are a smarter, more flexible version of the " + code { "use_ref" } + " hook. Signals now back many hooks in dioxus to provide a more consistent and flexible API." + } + h3 { id: "state-hooks", + Link { + to: BookRoute::MigrationHooks { + section: MigrationHooksSection::StateHooks, + }, + class: "header", + "State Hooks" + } + } + p { + "State hooks are now backed by signals. " + code { "use_state" } + ", " + code { "use_ref" } + ", and " + code { "use_shared_state" } + " have been replaced with the " + code { "use_signal" } + " hook. The " + code { "use_signal" } + " hook is a more flexible and powerful version of the " + code { "use_ref" } + " hook with smarter scopes that only subscribe to a signal if that signal is read within the scope. You can read more about the " + code { "use_signal" } + " hook in the " + Link { + to: BookRoute::MigrationState { + section: MigrationStateSection::Empty, + }, + "State Migration" + } + " guide." + } + h3 { id: "async-hooks", + Link { + to: BookRoute::MigrationHooks { + section: MigrationHooksSection::AsyncHooks, + }, + class: "header", + "Async Hooks" + } + } + p { + "The " + code { "use_future" } + " hook has been replaced with the " + code { "use_resource" } + " hook. " + code { "use_resource" } + " automatically subscribes to any signals that are read within the closure instead of using a tuple of dependencies." + } + p { "Dioxus 0.4:" } + CodeBlock { + contents: "
\nfn MyComponent(cx: Scope) -> Element {{\n\tlet state = use_state(cx, || 0);\n\tlet my_resource = use_future(cx, (**state,), |(state,)| async move {{\n\t\t// start a request that depends on the state\n\t\tprintln!("{{state}}");\n\t}});\n\trender! {{\n\t\t"{{state}}"\n\t}}\n}}
\n", + } + p { "Dioxus 0.5:" } + CodeBlock { + contents: "
\nfn MyComponent() -> Element {{\n    let state = use_signal(|| 0);\n    // No need to manually set the dependencies, the use_resource hook will automatically detect signal dependencies\n    let my_resource = use_resource(move || async move {{\n        // start a request that depends on the state\n        // Because we read from the state signal, this future will be re-run whenever the state changes\n        println!("{{state}}");\n    }});\n    rsx! {{"{{state}}"}}\n}}
\n", + name: "migration_hooks.rs".to_string(), + } + h3 { id: "dependencies", + Link { + to: BookRoute::MigrationHooks { + section: MigrationHooksSection::Dependencies, + }, + class: "header", + "Dependencies" + } + } + p { + "Some hooks including " + code { "use_effect" } + " and " + code { "use_resource" } + " now take a single closure with automatic subscriptions instead of a tuple of dependencies. You can read more about the " + code { "use_resource" } + " hook in the " + Link { + to: BookRoute::MigrationHooks { + section: MigrationHooksSection::Empty, + }, + "Hook Migration" + } + " guide." + } + p { "Dioxus 0.4:" } + CodeBlock { + contents: "
\nfn HasDependencies(cx: Scope) -> Element {{\n\tlet state = use_state(cx, || 0);\n\tlet my_resource = use_resource(cx, (**state,), |(state,)| async move {{\n\t\tprintln!("{{state}}");\n\t}});\n\tlet state_plus_one = use_memo(cx, (**state,), |(state,)| {{\n\t\tstate() + 1\n\t}});\n\trender! {{\n\t\t"{{state_plus_one}}"\n\t}}\n}}
\n", + } + p { "Dioxus 0.5:" } + CodeBlock { + contents: "
\nfn HasDependencies() -> Element {{\n    let state = use_signal(|| 0);\n    // No need to manually set the dependencies, the use_resource hook will automatically detect signal dependencies\n    let my_resource = use_resource(move || async move {{\n        // Because we read from the state signal, this future will be re-run whenever the state changes\n        println!("{{state}}");\n    }});\n    let state_plus_one = use_memo(move || {{\n        // Because we read from the state signal, this future will be re-run whenever the state changes\n        state() + 1\n    }});\n    rsx! {{"{{state_plus_one}}"}}\n}}
\n", + name: "migration_hooks.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum MigrationStateSection { + #[default] + Empty, + StateMigration, + ContextBasedState, + OptingOutOfSubscriptions, + GlobalState, +} +impl std::str::FromStr for MigrationStateSection { + type Err = MigrationStateSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "state-migration" => Ok(Self::StateMigration), + "context-based-state" => Ok(Self::ContextBasedState), + "opting-out-of-subscriptions" => Ok(Self::OptingOutOfSubscriptions), + "global-state" => Ok(Self::GlobalState), + _ => Err(MigrationStateSectionParseError), + } + } +} +impl std::fmt::Display for MigrationStateSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::StateMigration => f.write_str("state-migration"), + Self::ContextBasedState => f.write_str("context-based-state"), + Self::OptingOutOfSubscriptions => f.write_str("opting-out-of-subscriptions"), + Self::GlobalState => f.write_str("global-state"), + } + } +} +#[derive(Debug)] +pub struct MigrationStateSectionParseError; +impl std::fmt::Display for MigrationStateSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of MigrationStateSectionstate-migration, context-based-state, opting-out-of-subscriptions, global-state", + )?; + Ok(()) + } +} +impl std::error::Error for MigrationStateSectionParseError {} +#[component(no_case_check)] +pub fn MigrationState(section: MigrationStateSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "state-migration", + Link { + to: BookRoute::MigrationState { + section: MigrationStateSection::StateMigration, + }, + class: "header", + "State Migration" + } + } + p { + "The " + code { "use_state" } + " and " + code { "use_ref" } + " hooks have been replaced with the " + code { "use_signal" } + " hook. The " + code { "use_signal" } + " hook is a more flexible and powerful version of the " + code { "use_ref" } + " hook with smarter scopes that only subscribe to a signal if that signal is read within the scope." + } + p { + "With " + code { "use_state" } + ", if you had this code:" + } + CodeBlock { + contents: "
\nfn Parent(cx: Scope) -> Element {{\n\tlet state = use_state(cx, || 0);\n\n\trender! {{\n\t\tChild {{\n\t\t\tstate: state.clone()\n\t\t}}\n\t}}\n}}\n\n#[component]\nfn Child(cx: Scope, state: UseState<i32>) -> Element {{\n\trender! {{\n\t\t"{{state}}"\n\t}}\n}}
\n", + } + p { + "Parent would re-render every time the state changed even though only the child component was using the state. With the new " + code { "use_signal" } + " hook, the parent would only re-render if the state was changed within the parent component:" + } + CodeBlock { + contents: "
\nfn Parent() -> Element {{\n    let state = use_signal(|| 0);\n\n    rsx! {{ Child {{ state }} }}\n}}\n\n#[component]\nfn Child(state: Signal<i32>) -> Element {{\n    rsx! {{"{{state}}"}}\n}}
\n", + name: "migration_state.rs".to_string(), + } + p { + "Only the child component will re-render when the state changes because only the child component is reading the state." + } + h2 { id: "context-based-state", + Link { + to: BookRoute::MigrationState { + section: MigrationStateSection::ContextBasedState, + }, + class: "header", + "Context Based State" + } + } + p { + "The " + code { "use_shared_state_provider" } + " and " + code { "use_shared_state" } + " hooks have been replaced with using the " + code { "use_context_provider" } + " and " + code { "use_context" } + " hooks with a " + code { "Signal" } + ":" + } + CodeBlock { + contents: "
\nfn Parent() -> Element {{\n    // Create a new signal and provide it to the context API\n    let state = use_context_provider(|| Signal::new(0));\n\n    rsx! {{ Child {{}} }}\n}}\n\nfn Child() -> Element {{\n    // Get the state from the context API\n    let state = use_context::<Signal<i32>>();\n\n    rsx! {{"{{state}}"}}\n}}
\n", + name: "migration_state.rs".to_string(), + } + p { + "Signals are smart enough to handle subscribing to the right scopes without a special shared state hook." + } + h2 { id: "opting-out-of-subscriptions", + Link { + to: BookRoute::MigrationState { + section: MigrationStateSection::OptingOutOfSubscriptions, + }, + class: "header", + "Opting Out of Subscriptions" + } + } + p { + "Some state hooks including " + code { "use_shared_state" } + " and " + code { "use_ref" } + " hooks had a function called " + code { "write_silent" } + " in " + code { "0.4" } + ". This function allowed you to update the state without triggering a re-render any subscribers. This function has been removed in " + code { "0.5" } + "." + } + p { + "Instead, you can use the " + code { "peek" } + " function to read the current value of a signal without subscribing to it. This inverts the subscription model so that you can opt out of subscribing to a signal instead of opting all subscribers out of updates:" + } + CodeBlock { + contents: "
\nfn Parent() -> Element {{\n    let state = use_signal(|| 0);\n\n    // Even though we are reading the state, we don't need to subscribe to it\n    let read_without_subscribing = state.peek();\n    println!("{{}}", state.peek());\n\n    rsx! {{ Child {{ state }} }}\n}}\n\n#[component]\nfn Child(state: Signal<i32>) -> Element {{\n    rsx! {{\n        button {{ onclick: move |_| {{\n                state += 1;\n            }}, "count is {{state}}" }}\n    }}\n}}
\n", + name: "migration_state.rs".to_string(), + } + p { + code { "peek" } + " gives you more fine-grained control over when you want to subscribe to a signal. This can be useful for performance optimizations and for updating state without re-rendering components." + } + h2 { id: "global-state", + Link { + to: BookRoute::MigrationState { + section: MigrationStateSection::GlobalState, + }, + class: "header", + "Global State" + } + } + p { + "In " + code { "0.4" } + ", the fermi crate provided a separate global state API called atoms. In " + code { "0.5" } + ", the " + code { "Signal" } + " type has been extended to provide a global state API. You can use the " + code { "Signal::global" } + " function to create a global signal:" + } + CodeBlock { + contents: "
\nstatic COUNT: GlobalSignal<i32> = Signal::global(|| 0);\n\nfn Parent() -> Element {{\n    rsx! {{\n        div {{ "{{COUNT}}" }}\n        button {{\n            onclick: move |_| {{\n                *COUNT.write() += 1;\n            }},\n            "Increment"\n        }}\n    }}\n}}
\n", + name: "migration_state.rs".to_string(), + } + p { + "You can read more about global signals in the " + Link { + to: BookRoute::MigrationFermi { + section: MigrationFermiSection::Empty, + }, + "Fermi migration guide" + } + "." + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum MigrationFermiSection { + #[default] + Empty, + Fermi, + Memos, +} +impl std::str::FromStr for MigrationFermiSection { + type Err = MigrationFermiSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "fermi" => Ok(Self::Fermi), + "memos" => Ok(Self::Memos), + _ => Err(MigrationFermiSectionParseError), + } + } +} +impl std::fmt::Display for MigrationFermiSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::Fermi => f.write_str("fermi"), + Self::Memos => f.write_str("memos"), + } + } +} +#[derive(Debug)] +pub struct MigrationFermiSectionParseError; +impl std::fmt::Display for MigrationFermiSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Invalid section name. Expected one of MigrationFermiSectionfermi, memos")?; + Ok(()) + } +} +impl std::error::Error for MigrationFermiSectionParseError {} +#[component(no_case_check)] +pub fn MigrationFermi(section: MigrationFermiSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "fermi", + Link { + to: BookRoute::MigrationFermi { + section: MigrationFermiSection::Fermi, + }, + class: "header", + "Fermi" + } + } + p { + "In dioxus 0.5, fermi atoms have been replaced with global signals and included in the main dioxus library." + } + p { + "The new global signals can be used directly without hooks and include additional functionality like global memos." + } + p { "Dioxus 0.4:" } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\nuse fermi::*;\n\nstatic NAME: Atom<String> = Atom(|_| "world".to_string());\nstatic NAMES: AtomRef<Vec<String>> = AtomRef(|_| vec!["world".to_string()]);\n\nfn app(cx: Scope) -> Element {{\n    use_init_atom_root(cx);\n    let set_name = use_set(cx, &NAME);\n\tlet names = use_atom_ref(cx, &NAMES);\n\n    cx.render(rsx! {{\n        button {{\n\t\t\tonclick: move |_| set_name("dioxus".to_string()),\n\t\t\t"reset name"\n\t\t}}\n\t\t"{{names.read():?}}"\n    }})\n}}
\n", + } + p { "Dioxus 0.5:" } + CodeBlock { + contents: "
\nuse dioxus::prelude::*;\n\nstatic NAME: GlobalSignal<String> = Signal::global(|| "world".to_string());\n// Global signals work for copy and clone types in the same way\nstatic NAMES: GlobalSignal<Vec<String>> = Signal::global(|| vec!["world".to_string()]);\n\nfn app() -> Element {{\n    // No need to use use_init_atom_root, use_set, or use_atom_ref. Just use the global signal directly\n    rsx! {{\n        button {{ onclick: move |_| *NAME.write() = "reset name".to_string(), "reset name" }}\n        "{{NAMES:?}}"\n    }}\n}}
\n", + name: "migration_fermi.rs".to_string(), + } + h2 { id: "memos", + Link { + to: BookRoute::MigrationFermi { + section: MigrationFermiSection::Memos, + }, + class: "header", + "Memos" + } + } + p { "Dioxus 0.5 introduces global memos which can be used to store computed values globally." } + CodeBlock { + contents: "
\nstatic COUNT: GlobalSignal<u32> = Signal::global(|| 0);\nstatic MEMO: GlobalMemo<u32> = Signal::global_memo(|| COUNT() + 1);\n\nfn GlobalMemo() -> Element {{\n    rsx! {{\n        button {{ onclick: move |_| *COUNT.write() += 1, "increment" }}\n        // Global memos can be used like signals\n        "{{MEMO}}"\n    }}\n}}
\n", + name: "migration_fermi.rs".to_string(), + } + } +} +#[derive( + Clone, Copy, PartialEq, Eq, Hash, Debug, Default, serde::Serialize, serde::Deserialize, +)] +pub enum MigrationPropsSection { + #[default] + Empty, + PropsMigration, + OwnedProps, + BorrowedProps, + ManualProps, +} +impl std::str::FromStr for MigrationPropsSection { + type Err = MigrationPropsSectionParseError; + fn from_str(s: &str) -> Result { + match s { + "" => Ok(Self::Empty), + "props-migration" => Ok(Self::PropsMigration), + "owned-props" => Ok(Self::OwnedProps), + "borrowed-props" => Ok(Self::BorrowedProps), + "manual-props" => Ok(Self::ManualProps), + _ => Err(MigrationPropsSectionParseError), + } + } +} +impl std::fmt::Display for MigrationPropsSection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Empty => f.write_str(""), + Self::PropsMigration => f.write_str("props-migration"), + Self::OwnedProps => f.write_str("owned-props"), + Self::BorrowedProps => f.write_str("borrowed-props"), + Self::ManualProps => f.write_str("manual-props"), + } + } +} +#[derive(Debug)] +pub struct MigrationPropsSectionParseError; +impl std::fmt::Display for MigrationPropsSectionParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str( + "Invalid section name. Expected one of MigrationPropsSectionprops-migration, owned-props, borrowed-props, manual-props", + )?; + Ok(()) + } +} +impl std::error::Error for MigrationPropsSectionParseError {} +#[component(no_case_check)] +pub fn MigrationProps(section: MigrationPropsSection) -> dioxus::prelude::Element { + use dioxus::prelude::*; + rsx! { + h1 { id: "props-migration", + Link { + to: BookRoute::MigrationProps { + section: MigrationPropsSection::PropsMigration, + }, + class: "header", + "Props Migration" + } + } + p { + "In dioxus 0.4, props are passed into the component through the scope. In dioxus 0.5, props are passed into the component through the props struct directly." + } + h2 { id: "owned-props", + Link { + to: BookRoute::MigrationProps { + section: MigrationPropsSection::OwnedProps, + }, + class: "header", + "Owned Props" + } + } + p { + "The props were borrowed with the lifetime from the scope. The props are cloned every render, and passed into the component as an owned value." + } + p { "Dioxus 0.4:" } + CodeBlock { + contents: "
\n#[component]\nfn Comp(cx: Scope, name: String) -> Element {{\n    // You pass in an owned prop, but inside the component, it is borrowed (name is the type &String inside the function)\n    let owned_name: String = name.clone();\n\n    cx.render(rsx! {{\n        "Hello {{owned_name}}"\n    }})\n}}
\n", + } + p { "Dioxus 0.5:" } + CodeBlock { + contents: "
\n// In dioxus 0.5, props are always owned. You pass in owned props and you get owned props in the body of the component\n#[component]\nfn Comp(name: String) -> Element {{\n    // Name is owned here already (name is the type String inside the function)\n    let owned_name: String = name;\n\n    rsx! {{"Hello {{owned_name}}"}}\n}}
\n", + name: "migration_props.rs".to_string(), + } + p { + "Because props are cloned every render, making props Copy is recommended. You can easily make a field Copy by accepting " + code { "ReadOnlySignal" } + " instead of " + code { "T" } + " in the props struct:" + } + CodeBlock { + contents: "
\n// In dioxus 0.5, props are always owned. You pass in owned props and you get owned props in the body of the component\n#[component]\nfn CopyPropsComp(name: ReadOnlySignal<String>) -> Element {{\n    rsx! {{\n        button {{\n            // You can easily copy the value of a signal into a closure\n            onclick: move |_| {{\n                println!("Hello {{name}}");\n                async move {{\n                    println!("Hello {{name}}");\n                }}\n            }},\n            "Click me"\n        }}\n    }}\n}}\n\nfn CopyPropsCompParent() -> Element {{\n    rsx! {{ CopyPropsComp {{ name: "World" }} }}\n}}
\n", + name: "migration_props.rs".to_string(), + } + h2 { id: "borrowed-props", + Link { + to: BookRoute::MigrationProps { + section: MigrationPropsSection::BorrowedProps, + }, + class: "header", + "Borrowed Props" + } + } + p { + "Borrowed props are removed in dioxus 0.5. Mapped signals can act similarly to borrowed props if your props are borrowed from state." + } + p { "Dioxus 0.4:" } + CodeBlock { + contents: "
\nfn Parent(cx: Scope) -> Element {{\n    let state = use_state(cx, || (1, "World".to_string()));\n    rsx! {{\n        BorrowedComp {{\n            name: &state.get().1\n        }}\n    }}\n}}\n\n#[component]\nfn BorrowedComp<'a>(cx: Scope<'a>, name: &'a str) -> Element<'a> {{\n    rsx! {{\n        "Hello {{name}}"\n    }}\n}}
\n", + } + p { "Dioxus 0.5:" } + CodeBlock { + contents: "
\nfn Parent() -> Element {{\n    let state = use_signal(|| (1, "World".to_string()));\n\n    rsx! {{ BorrowedComp {{ name: state.map(|s| &s.1) }} }}\n}}\n\n#[component]\nfn BorrowedComp(name: MappedSignal<String>) -> Element {{\n    rsx! {{"Hello {{name}}"}}\n}}
\n", + name: "migration_props.rs".to_string(), + } + h2 { id: "manual-props", + Link { + to: BookRoute::MigrationProps { + section: MigrationPropsSection::ManualProps, + }, + class: "header", + "Manual Props" + } + } + p { + "Manual prop structs in dioxus 0.5 need to derive " + code { "Clone" } + " in addition to " + code { "Props" } + " and " + code { "PartialEq" } + ":" + } + p { "Dioxus 0.4:" } + CodeBlock { + contents: "
\n#[derive(Props, PartialEq)]\nstruct ManualProps {{\n    name: String,\n}}\n\n// Functions accept the props directly instead of the scope\nfn ManualPropsComponent(cx: Scope<ManualProps>) -> Element {{\n    render! {{\n        "Hello {{cx.props.name}}"\n    }}\n}}
\n", + } + p { "Dioxus 0.5:" } + CodeBlock { + contents: "
\n#[derive(Props, Clone, PartialEq)]\nstruct ManualProps {{\n    name: String,\n}}\n\n// Functions accept the props directly instead of the component\nfn ManualPropsComponent(props: ManualProps) -> Element {{\n    rsx! {{"Hello {{props.name}}"}}\n}}
\n", + name: "migration_props.rs".to_string(), + } + } +} + +use super::*; diff --git a/packages/docs-router/src/docs/router_06.rs b/packages/docs-router/src/docs/router_06.rs new file mode 100644 index 000000000..3423d79a9 --- /dev/null +++ b/packages/docs-router/src/docs/router_06.rs @@ -0,0 +1,26179 @@ +use dioxus::prelude::*; +#[derive( + Clone, + Copy, + dioxus_router::prelude::Routable, + PartialEq, + Eq, + Hash, + Debug, + serde::Serialize, + serde::Deserialize, +)] +pub enum BookRoute { + #[route("/#:section")] + Index { section: IndexSection }, + #[route("/getting_started/#:section")] + GettingStartedIndex { section: GettingStartedIndexSection }, + #[route("/guide/#:section")] + GuideIndex { section: GuideIndexSection }, + #[route("/guide/tooling#:section")] + GuideTooling { section: GuideToolingSection }, + #[route("/guide/new_app#:section")] + GuideNewApp { section: GuideNewAppSection }, + #[route("/guide/component#:section")] + GuideComponent { section: GuideComponentSection }, + #[route("/guide/rsx#:section")] + GuideRsx { section: GuideRsxSection }, + #[route("/guide/assets#:section")] + GuideAssets { section: GuideAssetsSection }, + #[route("/guide/state#:section")] + GuideState { section: GuideStateSection }, + #[route("/guide/data_fetching#:section")] + GuideDataFetching { section: GuideDataFetchingSection }, + #[route("/guide/backend#:section")] + GuideBackend { section: GuideBackendSection }, + #[route("/guide/databases#:section")] + GuideDatabases { section: GuideDatabasesSection }, + #[route("/guide/routing#:section")] + GuideRouting { section: GuideRoutingSection }, + #[route("/guide/bundle#:section")] + GuideBundle { section: GuideBundleSection }, + #[route("/guide/deploy#:section")] + GuideDeploy { section: GuideDeploySection }, + #[route("/guide/next_steps#:section")] + GuideNextSteps { section: GuideNextStepsSection }, + #[route("/essentials/#:section")] + EssentialsIndex { section: EssentialsIndexSection }, + #[route("/essentials/rsx/#:section")] + EssentialsRsxIndex { section: EssentialsRsxIndexSection }, + #[route("/essentials/lifecycle/#:section")] + EssentialsLifecycleIndex { + section: EssentialsLifecycleIndexSection, + }, + #[route("/essentials/state/#:section")] + EssentialsStateIndex { + section: EssentialsStateIndexSection, + }, + #[route("/essentials/async/#:section")] + EssentialsAsyncIndex { + section: EssentialsAsyncIndexSection, + }, + #[route("/essentials/breaking/#:section")] + EssentialsBreakingIndex { + section: EssentialsBreakingIndexSection, + }, + #[route("/essentials/error_handling/#:section")] + EssentialsErrorHandlingIndex { + section: EssentialsErrorHandlingIndexSection, + }, + #[route("/guides/#:section")] + GuidesIndex { section: GuidesIndexSection }, + #[route("/guides/rules_of_hooks#:section")] + GuidesRulesOfHooks { section: GuidesRulesOfHooksSection }, + #[route("/router/#:section")] + RouterIndex { section: RouterIndexSection }, + #[route("/router/example/#:section")] + RouterExampleIndex { section: RouterExampleIndexSection }, + #[route("/router/example/first-route#:section")] + RouterExampleFirstRoute { + section: RouterExampleFirstRouteSection, + }, + #[route("/router/example/building-a-nest#:section")] + RouterExampleBuildingANest { + section: RouterExampleBuildingANestSection, + }, + #[route("/router/example/navigation-targets#:section")] + RouterExampleNavigationTargets { + section: RouterExampleNavigationTargetsSection, + }, + #[route("/router/example/redirection-perfection#:section")] + RouterExampleRedirectionPerfection { + section: RouterExampleRedirectionPerfectionSection, + }, + #[route("/router/example/full-code#:section")] + RouterExampleFullCode { + section: RouterExampleFullCodeSection, + }, + #[route("/router/reference/#:section")] + RouterReferenceIndex { + section: RouterReferenceIndexSection, + }, + #[route("/router/reference/routes/#:section")] + RouterReferenceRoutesIndex { + section: RouterReferenceRoutesIndexSection, + }, + #[route("/router/reference/routes/nested#:section")] + RouterReferenceRoutesNested { + section: RouterReferenceRoutesNestedSection, + }, + #[route("/router/reference/layouts#:section")] + RouterReferenceLayouts { + section: RouterReferenceLayoutsSection, + }, + #[route("/router/reference/navigation/#:section")] + RouterReferenceNavigationIndex { + section: RouterReferenceNavigationIndexSection, + }, + #[route("/router/reference/navigation/programmatic#:section")] + RouterReferenceNavigationProgrammatic { + section: RouterReferenceNavigationProgrammaticSection, + }, + #[route("/router/reference/history-providers#:section")] + RouterReferenceHistoryProviders { + section: RouterReferenceHistoryProvidersSection, + }, + #[route("/router/reference/history-buttons#:section")] + RouterReferenceHistoryButtons { + section: RouterReferenceHistoryButtonsSection, + }, + #[route("/router/reference/routing-update-callback#:section")] + RouterReferenceRoutingUpdateCallback { + section: RouterReferenceRoutingUpdateCallbackSection, + }, + #[route("/guides/assets#:section")] + GuidesAssets { section: GuidesAssetsSection }, + #[route("/guides/web/#:section")] + GuidesWebIndex { section: GuidesWebIndexSection }, + #[route("/guides/desktop/#:section")] + GuidesDesktopIndex { section: GuidesDesktopIndexSection }, + #[route("/guides/mobile/#:section")] + GuidesMobileIndex { section: GuidesMobileIndexSection }, + #[route("/guides/mobile/apis#:section")] + GuidesMobileApis { section: GuidesMobileApisSection }, + #[route("/guides/ssr#:section")] + GuidesSsr { section: GuidesSsrSection }, + #[route("/guides/fullstack/#:section")] + GuidesFullstackIndex { + section: GuidesFullstackIndexSection, + }, + #[route("/guides/fullstack/hydration#:section")] + GuidesFullstackHydration { + section: GuidesFullstackHydrationSection, + }, + #[route("/guides/fullstack/managing_dependencies#:section")] + GuidesFullstackManagingDependencies { + section: GuidesFullstackManagingDependenciesSection, + }, + #[route("/guides/fullstack/server_functions#:section")] + GuidesFullstackServerFunctions { + section: GuidesFullstackServerFunctionsSection, + }, + #[route("/guides/fullstack/extractors#:section")] + GuidesFullstackExtractors { + section: GuidesFullstackExtractorsSection, + }, + #[route("/guides/fullstack/middleware#:section")] + GuidesFullstackMiddleware { + section: GuidesFullstackMiddlewareSection, + }, + #[route("/guides/fullstack/authentication#:section")] + GuidesFullstackAuthentication { + section: GuidesFullstackAuthenticationSection, + }, + #[route("/guides/fullstack/routing#:section")] + GuidesFullstackRouting { + section: GuidesFullstackRoutingSection, + }, + #[route("/guides/fullstack/static_site_generation#:section")] + GuidesFullstackStaticSiteGeneration { + section: GuidesFullstackStaticSiteGenerationSection, + }, + #[route("/guides/fullstack/axum#:section")] + GuidesFullstackAxum { section: GuidesFullstackAxumSection }, + #[route("/cookbook/publishing#:section")] + CookbookPublishing { section: CookbookPublishingSection }, + #[route("/cookbook/antipatterns#:section")] + CookbookAntipatterns { + section: CookbookAntipatternsSection, + }, + #[route("/cookbook/integrations/#:section")] + CookbookIntegrationsIndex { + section: CookbookIntegrationsIndexSection, + }, + #[route("/cookbook/integrations/logging#:section")] + CookbookIntegrationsLogging { + section: CookbookIntegrationsLoggingSection, + }, + #[route("/cookbook/integrations/internationalization#:section")] + CookbookIntegrationsInternationalization { + section: CookbookIntegrationsInternationalizationSection, + }, + #[route("/cookbook/state/#:section")] + CookbookStateIndex { section: CookbookStateIndexSection }, + #[route("/cookbook/state/external/#:section")] + CookbookStateExternalIndex { + section: CookbookStateExternalIndexSection, + }, + #[route("/cookbook/state/custom_hooks/#:section")] + CookbookStateCustomHooksIndex { + section: CookbookStateCustomHooksIndexSection, + }, + #[route("/cookbook/bundling#:section")] + CookbookBundling { section: CookbookBundlingSection }, + #[route("/cookbook/testing#:section")] + CookbookTesting { section: CookbookTestingSection }, + #[route("/cookbook/tailwind#:section")] + CookbookTailwind { section: CookbookTailwindSection }, + #[route("/cookbook/optimizing#:section")] + CookbookOptimizing { section: CookbookOptimizingSection }, + #[route("/migration/#:section")] + MigrationIndex { section: MigrationIndexSection }, + #[route("/reference/#:section")] + ReferenceIndex { section: ReferenceIndexSection }, + #[route("/reference/hotreload#:section")] + ReferenceHotreload { section: ReferenceHotreloadSection }, + #[route("/reference/rsx#:section")] + ReferenceRsx { section: ReferenceRsxSection }, + #[route("/reference/components#:section")] + ReferenceComponents { section: ReferenceComponentsSection }, + #[route("/reference/component_props#:section")] + ReferenceComponentProps { + section: ReferenceComponentPropsSection, + }, + #[route("/reference/event_handlers#:section")] + ReferenceEventHandlers { + section: ReferenceEventHandlersSection, + }, + #[route("/reference/hooks#:section")] + ReferenceHooks { section: ReferenceHooksSection }, + #[route("/reference/user_input#:section")] + ReferenceUserInput { section: ReferenceUserInputSection }, + #[route("/reference/context#:section")] + ReferenceContext { section: ReferenceContextSection }, + #[route("/reference/dynamic_rendering#:section")] + ReferenceDynamicRendering { + section: ReferenceDynamicRenderingSection, + }, + #[route("/reference/router#:section")] + ReferenceRouter { section: ReferenceRouterSection }, + #[route("/reference/use_resource#:section")] + ReferenceUseResource { + section: ReferenceUseResourceSection, + }, + #[route("/reference/use_coroutine#:section")] + ReferenceUseCoroutine { + section: ReferenceUseCoroutineSection, + }, + #[route("/reference/spawn#:section")] + ReferenceSpawn { section: ReferenceSpawnSection }, + #[route("/contributing/#:section")] + ContributingIndex { section: ContributingIndexSection }, + #[route("/contributing/project_structure#:section")] + ContributingProjectStructure { + section: ContributingProjectStructureSection, + }, + #[route("/contributing/guiding_principles#:section")] + ContributingGuidingPrinciples { + section: ContributingGuidingPrinciplesSection, + }, + #[route("/CLI/#:section")] + CliIndex { section: CliIndexSection }, + #[route("/CLI/creating#:section")] + CliCreating { section: CliCreatingSection }, + #[route("/CLI/configure#:section")] + CliConfigure { section: CliConfigureSection }, + #[route("/CLI/translate#:section")] + CliTranslate { section: CliTranslateSection }, +} +impl BookRoute { + /// Get the markdown for a page by its ID + pub const fn page_markdown(id: use_mdbook::mdbook_shared::PageId) -> &'static str { + match id.0 { + 14usize => { + "# Deploying\n\nWe're *finally* ready to deploy our bundled apps into the world. Congrats on making it this far!\n\nThis step is optional for the tutorial but worth covering to understand the process. Feel free to skip ahead to [next steps](next_steps.md) if you're not interested in deploying.\n\n## Dioxus Deploy\n\nAs mentioned in the [introduction](../index.md#whos-funding-dioxus), Dioxus is an independent project with aspirations to fund itself through a paid deploy platform. Hopefully, one day, enough people ship apps with [Dioxus Deploy](https://dioxuslabs.com/deploy) to fund development on Dioxus itself!\n\nCurrently, Dioxus does not provide its own deploy platform. If you want to sign-up for the beta and help us design the ideal \"end-to-end app-development experience,\" please [join the waitlist!](https://forms.gle/zeBZmrjSkajqg7hUA)\n\n![Deploy](/assets/06_docs/deploy_screenshot.png)\n\n## Deploying your Desktop and Mobile apps\n\nGenerally, deploying a desktop app is as simple as distributing the bundle directly. Simply upload your app bundles to a host like GitHub or S3. With a download link, your users can easily download and install your apps.\n\n > \n > 📣 When shipping fullstack apps to production, you'll want to make sure to set your backend API URL properly as [covered later](#fullstack-desktop-and-mobile).\n\nIf you'd like to distribute your app through app stores, you'll need to follow some additional steps.\n\n* [iOS](https://developer.apple.com/ios/submit/): Directly publish to the Apple App Store\n* [macOS](https://developer.apple.com/macos/submit/): Directly publish to the Apple App Store\n* [Android](https://developer.android.com/studio/publish): Directly publish to the Google Play Store\n\nTauri provides some [helpful guides](https://tauri.app/distribute/) for deploying Tauri apps which, while not Dioxus apps, need to follow many of the same steps for deploying to app stores.\n\nMaking native app distribution easier is a top priority for Dioxus Deploy!\n\n## Deploy Requirements\n\nDioxus web apps are structured as a Client bundle and a Server executable. Generally, any deploy provider that exposes a simple container will be sufficient for a Dioxus fullstack web application.\n\nSome providers like [Cloudflare Workers](http://workers.cloudflare.com) and [Fermyon Spin](https://www.fermyon.com/spin) provide WASM-based containers for apps. WASM runtimes are typically cheaper to operate and can horizontally scale better than a traditional virtual-machine based container. When deploying on WASM runtimes, you will need to create a WASM build of your server manually.\n\nRunning the webserver is as simple as executing `./server`. Make sure to set the IP and PORT environment variables correctly:\n\n![Serving a Server](/assets/06_docs/serving_server.png)\n\n## Choosing a deploy provider\n\nThere are *many* deploy providers! We're not going to get too deep into the pros/cons of any particular provider. Generally, providers are good at one of a few categories: price, performance, UI/UX, advanced features, and enterprise requirements.\n\nDepending on your app, you might have strict requirements like SOC2 or HIPAA compliance. Make sure to do your own research for your own use-case.\n\n* [AWS](http://aws.amazon.com): Full-featured cloud provider powered by Amazon.\n* [GCP](https://cloud.google.com): Full-featured cloud provider powered by Google.\n* [Azure](http://azure.microsoft.com): Full-featured cloud provider powered by Microsoft.\n* [Fly.io](http://fly.io): Simple scale-to-zero micro-vm-based cloud with integrated wireguard.\n* [Vercel](https://vercel.com): Developer-focused cloud built on AWS cloud functions popular with JavaScript frameworks.\n* [Render](http://render.com): A \"Modern Heroku\" focused on developer experience and simplicity.\n* [Digital Ocean](https://www.digitalocean.com): A cloud built around virtual machines, databases, and storage.\n\nFor *HotDog* we're going to deploy on [Fly.io](http://fly.io). We like [Fly.io](http://fly.io) for a number of reasons. Most importantly, Fly is built on Amazon's [Firecracker](https://firecracker-microvm.github.io) project which is entirely written in Rust!\n\nFly is also quite simple to get started - just log in with either your GitHub account or Google account.\n\n## Building a Dockerfile\n\nSome deploy providers have prebuilt solutions for various runtimes. For example, some have dedicated NodeJS and Python runtimes with strict requirements.\n\nWith Rust apps, there generally isn't a prebuilt \"pack\" to target. In these cases, we need to write a simple Dockerfile which compiles and starts our apps.\n\nOur Dockerfile will have three phases. The first phase downloads and caches dependencies so incremental builds stay fast:\n\n````dockerfile\nFROM rust:1 AS chef\nRUN cargo install cargo-chef\nWORKDIR /app\n\nFROM chef AS planner\nCOPY . .\nRUN cargo chef prepare --recipe-path recipe.json\n````\n\nIn the second phase, we use cargo chef to load cached dependencies and preform the build:\n\n````dockerfile\nFROM chef AS builder\nCOPY --from=planner /app/recipe.json recipe.json\nRUN cargo chef cook --release --recipe-path recipe.json\nCOPY . .\n\n# Install `dx`\nRUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash\nRUN cargo binstall dioxus-cli --root /.cargo -y --force\nENV PATH=\"/.cargo/bin:$PATH\"\n\n# Create the final bundle folder. Bundle always executes in release mode with optimizations enabled\nRUN dx bundle --platform web\n````\n\nFinally, we copy the built \"web\" folder to the \"slim\" runtime that serves our app.\n\n````dockerfile\nFROM chef AS runtime\nCOPY --from=builder /app/target/dx/hot_dog/release/web/ /usr/local/app\n\n# set our port and make sure to listen for all connections\nENV PORT=8080\nENV IP=0.0.0.0\n\n# expose the port 8080\nEXPOSE 8080\n\nWORKDIR /usr/local/app\nENTRYPOINT [ \"/usr/local/app/server\" ]\n````\n\nIt's also a smart idea to set up a `.dockerignore` file:\n\n````\n**/target\n**/dist\nLICENSES\nLICENSE\ntemp\nREADME.md\n````\n\n## Deploying to Fly\n\nTo get started with Fly, we need to go through the [Sign Up flow](https://fly.io/app/sign-up) and enter our details. This shouldn't take too long.\n\nWe'll add the dockerfile from above along with the dockerignore. We'll want to [install `flyctl`](https://fly.io/docs/flyctl/install/) which also installs the `fly` CLI.\n\nLet's call [`fly launch`](https://fly.io/docs/flyctl/launch/) which will automatically initialize our `fly.toml`.\n\n![Fly Launch](/assets/06_docs/fly_launch.png)\n\n`fly launch` will spin up a build machine for us and build our app. In a minute or two, our app should be fully built and deployed.\n\nIf we ever want to re-deploy our code, we can run `fly deploy`.\n\n![Running fly deploy](/assets/06_docs/fly_deploy.mp4)\n\nWe can also add a volume to our app to persist our Sqlite database by adding a `[mounts]` section to our Fly.toml:\n\n````toml\n[mounts]\n source = \"hotdogdb\"\n destination = \"/usr/local/app/hotdogdb\"\n````\n\nOnce the build is complete, Fly will assign our app a URL that we can customize later. With any luck, our app should be live!\n\n![Live App](/assets/06_docs/fly-deployed.png)\n\n## Continuous Deployment\n\nFly also supports [continuous deployment](https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/). Whenever we push to our GitHub repository, we can execute `fly deploy` automatically. This can serve as a foundation for staging environments and automatic releases.\n\nOur app just needs a `.github/workflows/fly-deploy.yml`.\n\n````yml\nname: Fly Deploy\non:\n push:\n branches:\n - main\njobs:\n deploy:\n name: Deploy app\n runs-on: ubuntu-latest\n concurrency: deploy-group\n steps:\n - uses: actions/checkout@v4\n - uses: superfly/flyctl-actions/setup-flyctl@master\n - run: flyctl deploy --remote-only\n env:\n FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}\n````\n\n## Fullstack Desktop and Mobile\n\nNow that our backend is live, we can wire up the API to our native apps. By default, Dioxus doesn't know where to find your API, so you'll need to specify the URL manually by calling `server_fn::client::set_server_url`.\n\n````rust@guide_deploy.rs\nfn main() {\n #[cfg(not(feature = \"server\"))]\n server_fn::client::set_server_url(\"https://hot-dog.fly.dev\");\n\n dioxus::launch(App);\n}\n````\n\nNote that as our app changes, the \"true\" endpoint of our server functions might change. The `#[server]` macro generates an API endpoint with the form of `/api/fetch_dogs-jkhj12` where the trailing data is a unique hash. As we update our server functions, the hash will change.\n\nTo make server functions maintain a stable endpoint, we can manually name them with the `endpoint = \"xyz\"` attribute.\n\n````rust@guide_deploy.rs\n#[server(endpoint = \"list_dogs\")]\npub async fn list_dogs() -> Result, ServerFnError> {\n todo!()\n}\n\n#[server(endpoint = \"remove_dog\")]\npub async fn remove_dog(id: usize) -> Result<(), ServerFnError> {\n todo!()\n}\n\n#[server(endpoint = \"save_dog\")]\npub async fn save_dog(image: String) -> Result<(), ServerFnError> {\n todo!()\n}\n````\n\nLet's re-deploy our web app with `fly deploy`. This deploy should complete faster thanks to `cargo chef` caching our build.\n\nNow, with `dx serve --platform desktop`, we should be able to interact with the same backend across web and desktop.\n\nAmazing! Our startup is coming along nicely.\n\n![Full Cross Build](/assets/06_docs/full-crossplatform.png)\n\n## Next Steps\n\nOur app isn't done yet, but this guide has become pretty long!\n\nThere's so much extra to do:\n\n* Adding users, login, and auth.\n* Protecting our site from DDOS with tools Cloudflare.\n* Adding more features\n* Marketing and sharing with friends!" + } + 7usize => { + "# Styling and Assets\n\nUnfortunately, our HotDog app isn't quite ready to show off - it's completely unstyled!\n\nIn this chapter we'll cover adding assets and styles to our app.\n\n## Dioxus uses CSS for Styling\n\nAs mentioned earlier, Dioxus apps use HTML and CSS as the core markup and styling technology. Instead of re-inventing the wheel like Flutter and React-Native, we designed Dioxus to use HTML and CSS on every platform.\n\nCSS is by-far the most popular styling system and is extremely capable. For example, here's a screenshot of [ebou](https://github.com/terhechte/Ebou), a very beautiful Mastodon client built with Dioxus.\n\n![Ebou](/assets/06_docs/ebou-following.png)\n\nHTML and CSS are very powerful - don't worry about being too limited!\n\n## Adding the CSS File with asset!()\n\nThe bare-bones template already includes a base `main.css` in the `assets` folder.\n\n````sh\n├── Cargo.toml\n├── assets\n│\u{a0}\u{a0} └── main.css\n└── src\n └── main.rs\n````\n\nTo include the CSS in our app, we can use the `asset!()` macro. This macro ensures the asset will be included in the final app bundle.\n\n````rust@guide_assets.rs\nstatic CSS: Asset = asset!(\"/assets/main.css\");\n````\n\nWe also need to load the asset into our app using the `document::Stylesheet` component. This component is equivalent to the `` HTML element but also ensures the CSS will be pre-loaded during server-side-rendering.\n\n````rust@guide_assets.rs\nfn App() -> Element {\n rsx! {\n document::Stylesheet { href: CSS }\n }\n}\n````\n\nUnlike Rust's `include_str!()` macro, the `asset!()` macro does not actually include the *contents* of the asset in our final executable. Instead, it generates a unique path so that the asset can be loaded at runtime. This is ideal for web apps where assets are loaded in parallel through different HTTP requests.\n\n > \n > 📣 The `asset!()` macro generates a unique name that won't exactly match the input name. This helps prevents name collisions and improves caching.\n\n## Hot-Reloading\n\nAll assets in Dioxus participate in hot-reloading. Try editing your app's `main.css` and watch changes propagate in real time.\n\n![CSS Hot-reloading](/assets/06_docs/dog-asset-hotreload.mp4)\n\n## Including Images\n\nIn Dioxus, you can include images in two ways:\n\n* Dynamically with a URL\n* Statically with the `asset!()` macro.\n\nWhen including assets with a URL, simply fill the `src` attribute of `img {}`. Note that when the app is offline, URL-based images won't download.\n\n````rust@guide_assets.rs\nrsx! {\n // ...\n div {\n img { src: \"https://images.dog.ceo/breeds/pitbull/dog-3981540_1280.jpg\" }\n }\n}\n````\n\nFor static images, you can use the same `asset!()` macro that we used to include the app's CSS.\n\n````rust@guide_assets.rs\nstatic ICON: Asset = asset!(\"/assets/icon.png\");\n\nrsx! {\n img { src: ICON }\n}\n````\n\n## Optimizations\n\nBy default, the `asset!()` macro will lightly optimize CSS, JavaScript, JSON, and images. The name of the asset will also be modified to include a content hash.\n\n````rust@guide_assets.rs\n// would output main-j1238nask123.css\nasset!(\"/assets/main.css\").to_string();\n````\n\nYou can optimize assets even further, with an optional `Options` struct. For example, `dx` can automatically convert `.png` images to a more optimized `.avif` format:\n\n````rust@guide_assets.rs\n// outputs icon-j1238jd2.avif\nasset!(\"/assets/icon.png\", ImageAssetOptions::new().with_avif());\n````\n\nFor many apps, asset optimization is the most effective way of improving load times. As developers, we frequently overlook the size of images and accidentally make our sites load slower.\n\nCheck out the [assets guide](../guides/assets.md) for a more in-depth explanation of how the Dioxus asset system works.\n\n## The Final CSS\n\nWe can use the asset hot-reload system of `dx` and our knowledge of CSS to create a beautiful app:\n\n![Styled Dog App](/assets/06_docs/dog_app_styled.png)\n\nThe final CSS is here for reference:\n\n````css\n/* App-wide styling */\nhtml, body {\n background-color: #0e0e0e;\n color: white;\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n height: 100%;\n width: 100%;\n overflow: hidden;\n margin: 0;\n}\n\n#main {\n display: flex;\n flex-direction: column;\n height: 100%;\n justify-content: space-between;\n}\n\n#dogview {\n max-height: 80vh;\n flex-grow: 1;\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n}\n\n#dogview img {\n display: block;\n max-width: 50%;\n max-height: 50%;\n transform: scale(1.8);\n border-radius: 5px;\n border: 1px solid rgb(233, 233, 233);\n box-shadow: 0px 0px 5px 1px rgb(216, 216, 216, 0.5);\n}\n\n#title {\n text-align: center;\n padding-top: 10px;\n border-bottom: 1px solid #a8a8a8;\n display: flex;\n flex-direction: row;\n justify-content: space-evenly;\n align-items: center;\n}\n\n#title a {\n text-decoration: none;\n color: white;\n}\n\na#heart {\n background-color: white;\n color: red;\n padding: 5px;\n border-radius: 5px;\n}\n\n#title span {\n width: 20px;\n}\n\n#title h1 {\n margin: 0.25em;\n font-style: italic;\n}\n\n#buttons {\n display: flex;\n flex-direction: row;\n justify-content: center;\n gap: 20px;\n /* padding-top: 20px; */\n padding-bottom: 20px;\n}\n\n#skip { background-color: gray }\n#save { background-color: green; }\n\n#skip, #save {\n padding: 5px 30px 5px 30px;\n border-radius: 3px;\n font-size: 2rem;\n font-weight: bold;\n color: rgb(230, 230, 230)\n}\n\n#navbar {\n border: 1px solid rgb(233, 233, 233);\n border-width: 1px 0px 0px 0px;\n display: flex;\n flex-direction: row;\n justify-content: space-evenly;\n padding: 20px;\n gap: 20px;\n}\n\n#navbar a {\n background-color: #a8a8a8;\n border-radius: 5px;\n border: 1px solid black;\n text-decoration: none;\n color: black;\n padding: 10px 30px 10px 30px;\n}\n\n#favorites {\n flex-grow: 1;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n padding: 10px;\n}\n\n#favorites-container {\n overflow-y: auto;\n overflow-x: hidden;\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n justify-content: center;\n gap: 10px;\n padding: 10px;\n}\n\n.favorite-dog {\n max-height: 180px;\n max-width: 60%;\n position: relative;\n}\n\n.favorite-dog img {\n max-height: 150px;\n border-radius: 5px;\n margin: 5px;\n}\n\n.favorite-dog:hover button {\n display: block;\n}\n\n.favorite-dog button {\n display: none;\n position: absolute;\n bottom: 10px;\n left: 10px;\n z-index: 10;\n}\n````" + } + 9usize => { + "# Fetching Data\n\nOur *HotDog* app has some basic interactivity but does not yet fetch new dog images. In this chapter, we'll interact with async and fetching data from an API.\n\n## Adding Dependencies\n\nDioxus does not provide any built-in utilities for fetching data. Crates like [dioxus-query](https://github.com/marc2332/dioxus-query) exist, but for this tutorial we'll implement data-fetching from scratch.\n\nFirst, we need to add two new dependencies to our app: [serde](https://crates.io/crates/serde) and [reqwest](https://crates.io/crates/reqwest).\n\n* Reqwest provides an HTTP client for fetching.\n* Serde will let us derive a JSON Deserializer to decode the response.\n\nIn a new terminal window, add these crates to your app with `cargo add`.\n\n````bash\ncargo add reqwest --features json\ncargo add serde --features derive\n````\n\n## Defining a Response Type\n\nWe'll be using the amazing [dog.ceo/dog-api](https://dog.ceo/dog-api/) to fetch images of dogs for *HotDog*. Fortunately, the API response is quite simple to deserialize.\n\nLet's create a new Rust struct that matches the format of the API and derive `Deserialize` for it.\n\nThe Dog API docs outline a sample API response:\n\n````json\n{\n \"message\": \"https://images.dog.ceo/breeds/leonberg/n02111129_974.jpg\",\n \"status\": \"success\"\n}\n````\n\nOur Rust struct needs to match that format, though for now we'll only include the \"message\" field.\n\n````rust@guide_data_fetching.rs\n#[derive(serde::Deserialize)]\nstruct DogApi {\n message: String,\n}\n````\n\n## Using `reqwest` and `async`\n\nDioxus has stellar support for asynchronous Rust. We can simply convert our `onclick` handler to be `async` and then set the `img_src` after the future has resolved.\n\n![Dog Fetching](/assets/06_docs/fetch-dog.mp4)\n\nThe changes to our code are quite simple - just add the `reqwest::get` call and then call `.set()` on `img_src` with the result.\n\n````rust@guide_data_fetching.rs\n#[component]\nfn DogView() -> Element {\n let mut img_src = use_signal(|| \"\".to_string());\n\n let fetch_new = move |_| async move {\n let response = reqwest::get(\"https://dog.ceo/api/breeds/image/random\")\n .await\n .unwrap()\n .json::()\n .await\n .unwrap();\n\n img_src.set(response.message);\n };\n\n // ..\n\n rsx! {\n div { id: \"dogview\",\n img { src: \"{img_src}\" }\n }\n div { id: \"buttons\",\n // ..\n button { onclick: fetch_new, id: \"save\", \"save!\" }\n }\n }\n}\n````\n\nDioxus automatically calls `spawn` on asynchronous closures. You can also use `spawn` to perform async work *without* async closures - just call `spawn()` on any async block.\n\n````rust@guide_data_fetching.rs\nrsx! {\n button {\n onclick: move |_| {\n spawn(async move {\n // do some async work...\n });\n }\n }\n}\n````\n\nThe futures passed to `spawn` must not contain latent references to data outside the async block. Data that is `Copy` *can* be captured by async blocks, but all other data must be *moved*, usually by calling `.clone()`.\n\n## Managing Data Fetching with use_resource\n\nEventually, using bare `async` calls might lead to race conditions and weird state bugs. For example, if the user clicks the *fetch* button too quickly, then two requests will be made in parallel. If the request is updating data somewhere else, the wrong request might finish early and causes a race condition.\n\nIn Dioxus, *Resources* are pieces of state whose value is dependent on the completion of some asynchronous work. The `use_resource` hook provides a `Resource` object with helpful methods to start, stop, pause, and modify the asynchronous state.\n\nLet's change our component to use a resource instead:\n\n````rust@guide_data_fetching.rs\n#[component]\nfn DogView() -> Element {\n let mut img_src = use_resource(|| async move {\n reqwest::get(\"https://dog.ceo/api/breeds/image/random\")\n .await\n .unwrap()\n .json::()\n .await\n .unwrap()\n .message\n });\n\n rsx! {\n div { id: \"dogview\",\n img { src: img_src.cloned().unwrap_or_default() }\n }\n div { id: \"buttons\",\n button { onclick: move |_| img_src.restart(), id: \"skip\", \"skip\" }\n button { onclick: move |_| img_src.restart(), id: \"save\", \"save!\" }\n }\n }\n}\n````\n\nResources are very powerful: they integrate with Suspense, Streaming HTML, reactivity, and more.\n\nThe details of the `Resource` API are not terribly important right now, but you'll be using Resources frequently in larger apps, so it's a good idea to [read the docs](../reference/use_resource.md)." + } + 22usize => { + "# Error handling\n\nA selling point of Rust for web development is the reliability of always knowing where errors can occur and being forced to handle them. Dioxus provides ErrorBoundarys to help you handle errors in a declarative way. This guide will teach you how to use ErrorBoundaries and other error handling strategies in Dioxus.\n\n## Returning Errors from Components\n\nAstute observers might have noticed that `Element` is actually a type alias for `Result`. The `RenderError` type can be created from an error type that implements `Error`. You can use `?` to bubble up any errors you encounter while rendering to the nearest error boundary:\n\n````rust@error_handling.rs\n#[component]\nfn ThrowsError() -> Element {\n // You can return any type that implements `Error`\n let number: i32 = use_hook(|| \"1.234\").parse()?;\n\n todo!()\n}\n````\n\n## Capturing errors with ErrorBoundaries\n\nWhen you return an error from a component, it gets sent to the nearest error boundary. That error boundary can then handle the error and render a fallback UI with the handle_error closure:\n\n````rust@error_handling.rs\n#[component]\nfn Parent() -> Element {\n rsx! {\n ErrorBoundary {\n // The error boundary accepts a closure that will be rendered when an error is thrown in any\n // of the children\n handle_error: |_| {\n rsx! { \"Oops, we encountered an error. Please report this to the developer of this application\" }\n },\n ThrowsError {}\n }\n }\n}\n````\n\n## Throwing Errors from Event Handlers\n\nIn addition to components, you can throw errors from event handlers. If you throw an error from an event handler, it will bubble up to the nearest error boundary just like a component:\n\n````rust@error_handling.rs\n#[component]\nfn ThrowsError() -> Element {\n rsx! {\n button {\n onclick: move |_| {\n // Event handlers can return errors just like components\n let number: i32 = \"1...234\".parse()?;\n\n tracing::info!(\"Parsed number: {number}\");\n\n Ok(())\n },\n \"Throw error\"\n }\n }\n}\n````\n\n## Adding context to errors\n\nYou can add additional context to your errors with the [`Context`](https://docs.rs/dioxus/0.6/dioxus/prelude/trait.Context.html) trait. Calling `context` on a `Result` will add the context to the error variant of the `Result`:\n\n````rust, no_run@error_handling.rs\n#[component]\nfn ThrowsError() -> Element {\n // You can call the context method on results to add more information to the error\n let number: i32 = use_hook(|| \"1.234\")\n .parse()\n .context(\"Failed to parse name\")?;\n\n todo!()\n}\n````\n\nIf you need some custom UI for the error message, you can call `show` on a result to attach an Element to the error variant. The parent error boundary can choose to render this element instead of the default error message:\n\n````rust, no_run@error_handling.rs\n#[component]\nfn Parent() -> Element {\n rsx! {\n ErrorBoundary {\n // The error boundary accepts a closure that will be rendered when an error is thrown in any\n // of the children\n handle_error: |error: ErrorContext| {\n if let Some(error_ui) = error.show() {\n rsx! {\n {error_ui}\n }\n } else {\n rsx! {\n div {\n \"Oops, we encountered an error. Please report this to the developer of this application\"\n }\n }\n }\n },\n ThrowsError {}\n }\n }\n}\n````\n\n## Local Error Handling\n\nIf you need more fine-grained control over error states, you can store errors in reactive hooks and use them just like any other value. For example, if you need to show a phone number validation error, you can store the error in a memo and show it below the input field if it is invalid:\n\n````rust, no_run@error_handling.rs\n#[component]\npub fn PhoneNumberValidation() -> Element {\n let mut phone_number = use_signal(|| String::new());\n let parsed_phone_number = use_memo(move || phone_number().parse::());\n\n rsx! {\n input {\n class: \"border border-gray-300 rounded-md p-2 mb-4\",\n placeholder: \"Phone number\",\n value: \"{phone_number}\",\n oninput: move |e| {\n phone_number.set(e.value());\n },\n }\n\n match parsed_phone_number() {\n Ok(phone_number) => rsx! {\n div {\n \"Parsed phone number: {phone_number}\"\n }\n },\n Err(error) => rsx! {\n div {\n \"Phone number is invalid: {error}\"\n }\n }\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n error_handling::PhoneNumberValidation {}\n}\n````" + } + 23usize => { + "# Dioxus Guides\n\nThese guides contains more detailed explanations for some concepts covered in the [`guide`](../guide/index.md) and more.\n\n## State\n\n* [Rules of hooks](rules_of_hooks.md) Overview of the rules of hooks\n\n## Assets\n\n* [`Assets`](assets.md) Overview of how to include assets in your application\n\n## Platforms\n\n* [`Desktop`](desktop/index.md) Overview of desktop specific APIS\n* [`Web`](web/index.md) Overview of web specific APIS\n* [`Fullstack`](fullstack/index.md) Overview of Fullstack specific APIS\n * [`Server Functions`](fullstack/server_functions.md) Server functions make it easy to communicate between your server and client\n * [`Extractors`](fullstack/extractors.md) Extractors allow you to get extra information out of the headers of a request\n * [`Middleware`](fullstack/middleware.md) Middleware allows you to wrap a server function request or response\n * [`Authentication`](fullstack/authentication.md) An overview of how to handle authentication with server functions\n * [`Routing`](fullstack/routing.md) An overview of how to work with the router in the fullstack renderer\n* [`SSR`](ssr.md) Overview of the SSR renderer" + } + 25usize => { + "# Introduction\n\n > \n > If you are not familiar with Dioxus itself, check out the [Dioxus guide](../guide/index.md) first.\n\nWhether you are building a website, desktop app, or mobile app, splitting your app's views into \"pages\" can be an effective method for organization and maintainability.\n\nFor this purpose, Dioxus provides a router. Use the `cargo add` command to add the dependency:\n\n````sh\ncargo add dioxus --features router\n````\n\nThen, add this to your `Dioxus.toml` (learn more about configuration [here](../CLI/configure)):\n\n````toml\n[web.watcher]\nindex_on_404 = true\n````\n\n > \n > This configuration only works when using `dx serve`. If you host your app in a different way (which you most likely do in production), you need to find out how to add a fallback 404 page to your app, and make it a copy of the generated `dist/index.html`.\n\nThis will instruct `dx serve` to redirect any unknown route to the index, to then be resolved by the router.\nThe router works on the client. If we connect through the index route (e.g., `localhost:8080`, then click a link to go to `localhost:8080/contact`), the app renders the new route without reloading.\nHowever, when we go to a route *before* going to the index (go straight to `localhost:8080/contact`), we are trying to access a static route from the server, but the only static route on our server is the index (because the Dioxus frontend is a Single Page Application) and it will fail unless we redirect all missing routes to the index.\n\nThis book is intended to get you up to speed with Dioxus Router. It is split\ninto two sections:\n\n1. The [reference](reference/index.md) section explains individual features in\n depth. You can read it from start to finish, or you can read individual chapters\n in whatever order you want.\n1. If you prefer a learning-by-doing approach, you can check out the\n *[example project](example/index.md)*. It guides you through\n creating a dioxus app, setting up the router, and using some of its\n functionality.\n\n > \n > Please note that this is not the only documentation for the Dioxus Router. You can also check out the [API Docs](https://docs.rs/dioxus-router/)." + } + 49usize => { + "# Managing Fullstack Dependencies\n\nFullstack applications build to at least two different binaries:\n\n* The client application that runs the desktop, mobile, or web application\n* The server that renders the initial HTML and runs server functions\n\nThose binaries tend to have different dependencies and those dependencies often are only compatible with a specific target platform. This guide will cover how fullstack manages each binary's dependencies and how to add dependencies that are only compatible with one binary/target.\n\n## Client and Server Feature Flags\n\nDioxus uses feature flags to differentiate between the different binaries a single library can produce. Each target binary should have a feature flag in your `Cargo.toml` file that enables the corresponding feature in dioxus. For example, if you are targeting `web` and `desktop` with a fullstack server, you would add the following to your `Cargo.toml`:\n\n````toml\n[dependencies]\n# Don't include any renderer features in your dioxus dependency directly. They will be added in feature flags.\n# The fullstack feature enables the bindings between the server and client without enabling a specific binary target.\ndioxus = { version = \"0.6\", features = [\"fullstack\"] }\n\n[features]\n# The web feature enables the web renderer. Dioxus will automatically enable the feature you define that activates `dioxus/web` when building the client WASM bundle.\nweb = [\"dioxus/web\"]\n# The desktop feature enables the desktop renderer. Dioxus will automatically enable the feature you define that activates `dioxus/desktop` when building the client native bundle.\ndesktop = [\"dioxus/desktop\"]\n# The server feature enables server functions and server-side rendering. Dioxus will automatically enable the feature you define that activates `dioxus/server` when building the server binary.\nserver = [\"dioxus/server\"]\n````\n\nFeature flags like these for the client and server are automatically generated by the CLI when you run `dx new` with fullstack enabled. If you are creating a project from scratch, you will need to add the feature flags manually.\n\n > \n > If you are not familiar with features in rust, you can read more about feature flags in the [cargo reference](https://doc.rust-lang.org/cargo/reference/features.html).\n\n## Adding Server Only Dependencies\n\nMany dependencies like [`tokio`](https://docs.rs/tokio/latest/tokio/index.html) and [`axum`](https://docs.rs/axum/latest/axum/index.html) are only compatible with the server. If these dependencies are enabled when building a WASM bundle for the browser client, you will get a compilation error. For example, if we want to interact with the filesystem in a server function, we might want to add `tokio`. `tokio` has utilities for working with async IO like [`tokio::fs::File`](https://docs.rs/tokio/latest/tokio/fs/struct.File.html). Let's try it as a dependency to our fullstack project:\n\n````toml\n[dependencies]\n# ...\n# ❌ If tokio is added as a required dependency, it will be included in both the server\n# and the web bundle. The web bundle will fail to build because tokio is not\n# compatible with wasm\ntokio = { version = \"1\", features = [\"full\"] }\n````\n\nIf we try to compile with tokio as a required dependency, we will get a compilation error like this:\n\n````shell\nerror[E0432]: unresolved import `crate::sys::IoSourceState`\n --> /Users/user/.cargo/registry/src/index.crates.io-6f17d22bba15001f/mio-1.0.2/src |source.rs:14:5\n14 | use crate::sys::IoSourceState;\n | ^^^^^^^^^^^^^^^^^^^^^^^^^ no `IoSourceState` in `sys`\n...\n````\n\nSince we added `tokio` as a dependency for all three binaries, cargo tries to compile it for each target. This fails because `tokio` is not compatible with the `wasm32-unknown-unknown` target.\n\nTo fix the issue, we can **make the dependency optional and only enable it in the server feature**:\n\n````toml\n[dependencies]\n# ...\n# ✅ Since the tokio dependency is optional, it is not included in the web and desktop\n# bundles.\ntokio = { version = \"1\", features = [\"full\"], optional = true }\n\n[features]\n# ...\n# ✅ Since the tokio dependency is enabled in the server feature, it is included in\n# the server binary.\nserver = [\"dioxus/server\", \"dep:tokio\"]\n````\n\nNow when we build with `dx serve`, the project compiles successfully.\n\n## Adding Client Only Dependencies\n\nMany dependencies like [`wasm-bindgen`](https://docs.rs/wasm-bindgen/latest/wasm_bindgen/index.html) and [`web-sys`](https://docs.rs/web-sys/latest/web_sys/index.html) are only compatible with the client. Unlike server-only dependencies, these dependencies can generally compile on native targets, but they will panic when used outside of the browser.\n\nYou can cut down on build times for your server and native binaries by only including web dependencies in the browser client binary.\n\nInstead of adding web only dependencies every binary in your project like this:\n\n````toml\n[dependencies]\n# ...\n# ❌ If web-sys is added as a required dependency, it will be included in the server,\n# native, and the web bundle which makes build times longer.\nweb-sys = { version = \"0.3.60\", features = [\"console\"] }\n````\n\nYou can make the dependency optional and only enable it in the `web` feature in your `Cargo.toml`:\n\n````toml\n[dependencies]\n# ...\n# ✅ Since the web-sys dependency is optional, it is not included in the server and\n# native bundles.\nweb-sys = { version = \"0.3.60\", features = [\"console\"], optional = true }\n\n[features]\n# ...\n# ✅ Since the web-sys dependency is enabled in the web feature, it is included in\n# the web bundle.\nweb = [\"dioxus/web\", \"dep:web-sys\"]\n````\n\n## Managing Binary Specific Imports\n\nOnce you have set up binary specific dependencies, you need to adjust any of your imports to only import the dependencies when building for the binary that includes those dependencies.\n\nFor example, if `tokio` is only enabled in the server feature, you will need to import it like this:\n\n````rust@server_tokio_import.rs\n// Since the tokio dependency is only enabled in the server feature,\n// we need to only import it when the server feature is enabled.\n#[cfg(feature = \"server\")]\nuse tokio::fs::File;\n#[cfg(feature = \"server\")]\nuse tokio::io::AsyncReadExt;\n````\n\nYou also need to only compile any usage of the dependency when the feature is enabled:\n\n````rust@server_tokio_import.rs\n// Since the tokio dependency is only enabled in the server feature,\n// we need to only compile any usage of the dependency when the server feature is enabled.\n#[cfg(feature = \"server\")]\nasync fn read_file() -> Result {\n let mut file = File::open(\"path/to/file\").await?;\n let mut contents = String::new();\n file.read_to_string(&mut contents).await?;\n Ok(contents)\n}\n\n// The bodies of server functions automatically only compile when the server feature is enabled.\n#[server]\nasync fn get_file_contents() -> Result {\n let mut file = File::open(\"path/to/file\").await?;\n let mut contents = String::new();\n file.read_to_string(&mut contents).await?;\n Ok(contents)\n}\n````\n\nIt may be more convenient to group server or client specific code into a module that is only compiled when the feature is enabled:\n\n````rust@server_tokio_import.rs\n// Instead of configuring each item that is only used in the server, you can group\n// them into a module that is only compiled when the server feature is enabled.\n#[cfg(feature = \"server\")]\nmod tokio_utilities {\n use std::path::PathBuf;\n use tokio::fs::File;\n use tokio::io::AsyncReadExt;\n\n pub async fn read_file(path: PathBuf) -> Result {\n let mut file = File::open(path).await?;\n let mut contents = String::new();\n file.read_to_string(&mut contents).await?;\n Ok(contents)\n }\n}\n\n// Then you can define your server functions using shared utilities you defined for\n// server only code.\n#[server]\nasync fn get_file_contents() -> Result {\n let file = tokio_utilities::read_file(PathBuf::from(\"path/to/file\")).await?;\n Ok(file)\n}\n\n#[server]\nasync fn get_other_file_contents() -> Result {\n let file = tokio_utilities::read_file(PathBuf::from(\"path/to/other/file\")).await?;\n Ok(file)\n}\n````\n\n > \n > The [rust reference](https://doc.rust-lang.org/reference/conditional-compilation.html) has more information about conditional compilation in rust." + } + 59usize => { + "This section of the guide provides getting started guides for common tools used with Dioxus.\n\n* [Logging](./logging.md)\n* [Internationalization](./internationalization.md)" + } + 80usize => { + "# Router\n\nIn 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. For a desktop app, these scenes might be different views in your app.\n\nTo unify these platforms, Dioxus provides a first-party solution for scene management called Dioxus Router.\n\n## What is it?\n\nFor an app like the Dioxus landing page (https://dioxuslabs.com), we want to have several different scenes:\n\n* Homepage\n* Blog\n\nEach of these scenes is independent – we don't want to render both the homepage and blog at the same time.\n\nThe Dioxus router makes it easy to create these scenes. To make sure we're using the router, add the `router` feature to your `dioxus` dependency:\n\n````shell\ncargo add dioxus --features router\n````\n\n## Using the router\n\nUnlike other routers in the Rust ecosystem, our router is built declaratively at compile time. This makes it possible to compose our app layout simply by defining an enum.\n\n````rust@router_reference.rs\n// All of our routes will be a variant of this Route enum\n#[derive(Routable, PartialEq, Clone)]\nenum Route {\n // if the current location is \"/home\", render the Home component\n #[route(\"/home\")]\n Home {},\n // if the current location is \"/blog\", render the Blog component\n #[route(\"/blog\")]\n Blog {},\n}\n\nfn Home() -> Element {\n todo!()\n}\n\nfn Blog() -> Element {\n todo!()\n}\n````\n\nWhenever we visit this app, we will get either the Home component or the Blog component rendered depending on which route we enter at. If neither of these routes match the current location, then nothing will render.\n\nWe can fix this one of two ways:\n\n* A fallback 404 page\n\n````rust@router_reference.rs\n// All of our routes will be a variant of this Route enum\n#[derive(Routable, PartialEq, Clone)]\nenum Route {\n #[route(\"/home\")]\n Home {},\n #[route(\"/blog\")]\n Blog {},\n // if the current location doesn't match any of the above routes, render the NotFound component\n #[route(\"/:..segments\")]\n NotFound { segments: Vec },\n}\n\nfn Home() -> Element {\n todo!()\n}\n\nfn Blog() -> Element {\n todo!()\n}\n\n#[component]\nfn NotFound(segments: Vec) -> Element {\n todo!()\n}\n````\n\n* Redirect 404 to home\n\n````rust@router_reference.rs\n// All of our routes will be a variant of this Route enum\n#[derive(Routable, PartialEq, Clone)]\nenum Route {\n #[route(\"/home\")]\n // if the current location doesn't match any of the other routes, redirect to \"/home\"\n #[redirect(\"/:..segments\", |segments: Vec| Route::Home {})]\n Home {},\n #[route(\"/blog\")]\n Blog {},\n}\n````\n\n## Links\n\nFor our app to navigate these routes, we can provide clickable elements called Links. These simply wrap `
` elements that, when clicked, navigate the app to the given location. Because our route is an enum of valid routes, if you try to link to a page that doesn't exist, you will get a compiler error.\n\n````rust@router_reference.rs\nrsx! {\n Link { to: Route::Home {}, \"Go home!\" }\n}\n````\n\n## More reading\n\nThis page is just a very brief overview of the router. For more information, check out the [router book](../router/index.md) or some of the [router examples](https://github.com/DioxusLabs/dioxus/blob/master/examples/router.rs)." + } + 16usize => { + "# Core Topics\n\nThis section will guide you through key concepts in Dioxus:\n\n* [Building UIs with RSX](rsx/index.md) will teach you how to define html inside your Dioxus app with rsx.\n\n* [Component Lifecycle](lifecycle/index.md) teaches you about the lifecycle of components along with the hooks you need to run code when the component is first created, mounted, and removed.\n\n* [Managing State](state/index.md) guides you through how state works in Dioxus. It will teach you how to create state with `use_signal`, derive state with `use_memo`, and integrate state with asynchronous tasks with `use_resource`. Along the way, you will learn about you can use reactivity to declaratively describe your UI.\n\n* [Breaking Out](breaking/index.md) will teach you how to break out of Dioxus' rendering model to run JavaScript or interact with the DOM directly with `web-sys`.\n\n* [Async](async/index.md) will teach you how to integrate async tasks with Dioxus and how to handle loading states while waiting for async tasks to finish.\n\n* [Error Handling](error_handling/index.md) will teach you how to throw and handle errors in Dioxus." + } + 6usize => { + "# Describing the UI\n\nDioxus is a *declarative* framework. This means that instead of telling Dioxus what to do (e.g. to \"create an element\" or \"set the color to red\") we simply *declare* what we want the UI to look like using RSX.\n\n````rust, no_run@hello_world_desktop.rs\n// define a component that renders a div with the text \"Hello, world!\"\nfn App() -> Element {\n rsx! {\n div { \"Hello, world!\" }\n }\n}\n````\n\nHere, 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 user interface from it.\n\n## Editing RSX with Hot-Reloading\n\nWhen using `dx serve`, your app's RSX is automatically hot-reloaded whenever you edit and save the file. You can edit RSX structure, add new elements, and style your markup without a full rebuild.\n\nWhenever you edit *Rust* code, then `dx` will automatically force a \"full rebuild\" of your app.\n\n![Dog App Hotreloading](/assets/06_docs/dog_app_hotreload.mp4)\n\nFor an in-depth guide in what can and can't be hot-reloaded, check the [hot-reload guide](../reference/hotreload.md) in the reference.\n\n## RSX is just HTML\n\nDioxus provides the `rsx! {}` macro for assembling `Element`s in your app. The `rsx! {}` macro primarily speaks HTML: the web, desktop, and mobile Dioxus first-party renderers all use HTML and CSS as the layout and styling technologies.\n\nThis means you can reuse your knowledge of the web and build your app using `div`, `span`, `img`, `style`, `button`, and more.\n\nThe RSX syntax is a \"strict\" form of Rust that uses Rust's `Struct` syntax for assembling elements:\n\n````rust@guide_rsx.rs\nrsx! {\n div {\n class: \"bg-red-100\"\n }\n}\n````\n\nElements in RSX differ slightly from Rust struct syntax: they can also contain child structs placed immediately after the final attribute.\n\n````rust@guide_rsx.rs\nrsx! {\n div { class: \"bg-red-100\",\n button {\n onclick: move |_| info!(\"Clicked\"),\n \"Click me!\"\n }\n }\n}\n````\n\nAdditionally, all quoted strings in RSX imply `format!()` automatically, so you can define a variable outside your markup and use it in your strings without an explicit format call:\n\n````rust@guide_rsx.rs\nrsx! {\n div { \"Breed: {breed}\" }\n}\n````\n\nAny expression that can be rendered to a String can be included directly in RSX. RSX also accepts `Option` and iterators of Elements:\n\n````rust@guide_rsx.rs\nrsx! {\n // Anything that's `Display`\n {\"Something\"}\n\n // Optionals\n {show_title.then(|| rsx! { \"title!\" } )}\n\n // And iterators\n ul {\n {(0..5).map(|i| rsx! { \"{i}\" })}\n }\n}\n````\n\nDioxus provides two items of syntax sugar for these common cases: `for` loops and `if` chains. These blocks return the contained RSX directly.\n\n````rust@guide_rsx.rs\nrsx! {\n if show_title {\n \"title!\"\n }\n\n ul {\n for item in 0..5 {\n \"{item}\"\n }\n }\n}\n````\n\nFor lists, Dioxus uses the `key` attribute to ensure it's comparing the right elements between renders. If you forget to add a `key` attribute to your list item, you might run into performance and state management issues. Usually you can find a unique key to differentiate your list items:\n\n````rust@guide_rsx.rs\nrsx! {\n for user in users {\n div {\n key: \"{user.id}\",\n \"{user.name}\"\n }\n }\n}\n````\n\n## Adding UI to our *HotDog* App\n\nLet's add a basic UI to our app. We'll add a header, a body image for the dog photo, and some basic buttons.\n\n````rust@guide_rsx.rs\n#[component]\nfn App() -> Element {\n rsx! {\n div { id: \"title\",\n h1 { \"HotDog! 🌭\" }\n }\n div { id: \"dogview\",\n img { src: \"https://images.dog.ceo/breeds/pitbull/dog-3981540_1280.jpg\" }\n }\n div { id: \"buttons\",\n button { id: \"skip\", \"skip\" }\n button { id: \"save\", \"save!\" }\n }\n }\n}\n````\n\nOur app is coming together!\n\n![Unstyled Dog App](/assets/06_docs/unstyled_dog_app.png)\n\nUnfortunately, it's not very beautiful yet. Let's move on to [styling our app](assets.md)." + } + 57usize => { + "# Publishing\n\nAfter you have build your application, you will need to publish it somewhere. This reference will outline different methods of publishing your desktop or web application.\n\n## Web: Publishing with GitHub Pages\n\nEdit your `Dioxus.toml` to point your `out_dir` to the `docs` folder and the `base_path` to the name of your repo:\n\n````toml\n[application]\n# ...\n[web.app]\nbase_path = \"your_repo\"\n````\n\nThen build your app and publish it to Github:\n\n* Make sure GitHub Pages is set up for your repo to publish any static files in the docs directory\n* Build your app into the `docs` directory with:\n\n````sh\ndx bundle --out-dir docs\n````\n\n* Move the static content from `docs/public` to `docs`\n\n````sh\nmv docs/public/* docs\n````\n\n* Make a copy of your `docs/index.html` file and rename the copy to `docs/404.html` so that your app will work with client-side routing:\n\n````sh\ncp docs/index.html docs/404.html\n````\n\n* Add and commit with git\n* Push to GitHub\n\n## Desktop: Creating an installer\n\nDioxus desktop app uses your operating system's WebView library, so it's portable to be distributed for other platforms.\n\nIn this section, we'll cover how to bundle your app for macOS, Windows, and Linux.\n\n## Preparing your application for bundling\n\nDepending on your platform, you may need to add some additional code to your `main.rs` file to make sure your app is ready for bundling. On Windows, you'll need to add the `#![windows_subsystem = \"windows\"]` attribute to your `main.rs` file to hide the terminal window that pops up when you run your app. **If you're developing on Windows, only use this when bundling.** It will disable the terminal, so you will not get logs of any kind. You can gate it behind a feature, like so:\n\n````toml\n# Cargo.toml\n[features]\nbundle = []\n````\n\nAnd then your `main.rs`:\n\n````rust\n#![cfg_attr(feature = \"bundle\", windows_subsystem = \"windows\")]\n````\n\n## Adding assets to your application\n\nIf you want to bundle assets with your application, you can either use them with the `manganis` crate (covered more in the [assets](../guides/assets.md) page), or you can include them in your `Dioxus.toml` file:\n\n````toml\n[bundle]\n# The list of files to include in the bundle. These can contain globs.\nresources = [\"main.css\", \"header.svg\", \"**/*.png\"]\n````\n\n## Install `dioxus CLI`\n\nThe first thing we'll do is install the [dioxus-cli](https://github.com/DioxusLabs/dioxus/tree/main/packages/cli). This extension to cargo will make it very easy to package our app for the various platforms.\n\nTo install, simply run\n\n`cargo install dioxus-cli`\n\n## Building\n\nTo bundle your application you can simply run `dx bundle --release` (also add `--features bundle` if you're using that, see the [this](#preparing-your-application-for-bundling) for more) to produce a final app with all the optimizations and assets builtin.\n\nOnce you've ran the command, your app should be accessible in `dist/bundle/`.\n\nFor example, a macOS app would look like this:\n\n![Published App](/assets/static/publish.png)\n\nNice! 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.\n\n > \n > 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." + } + 20usize => { + "# Handling Asynchronous Tasks\n\nAsynchronous tasks are a core part of any modern application. Dioxus provides a few different methods to handle asynchronous tasks. This guide will cover how to use each of them. If you already know what kind of asynchronous task you need, you can skip to the section for that task:\n\n* [spawn](#running-futures-with-spawn) is great for futures you need to run in the background that don't return a value\n* [use_resource](#asynchronous-state-with-use-resource) handles asynchronous state while retaining control of exactly what happens while the future is running\n* It can be combined with [Suspense](#unified-loading-views-with-suspense) to handle many pending tasks with the same loading view\n\n## Running Futures with `spawn`\n\nThe [`spawn`](https://docs.rs/dioxus/0.6.2/dioxus/prelude/fn.spawn.html) method spawns a future in the background and returns a `Task` that you can use to cancel the future. Spawn is great for futures you want to start and then forget about like sending analytics data to a server:\n\n````rust@asynchronous.rs\nlet mut response = use_signal(|| \"Click to start a request\".to_string());\n\nrsx! {\n button {\n onclick: move |_| {\n response.set(\"...\".into());\n // Spawn will start a task running in the background\n spawn(async move {\n let resp = reqwest::Client::new()\n .get(\"https://dioxuslabs.com\")\n .send()\n .await;\n\n if resp.is_ok() {\n response.set(\"dioxuslabs.com responded!\".into());\n } else {\n response.set(\"failed to fetch response!\".into());\n }\n });\n },\n \"{response}\"\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n asynchronous::SpawnButton {}\n}\n````\n\nSince spawning in event handlers is very common, Dioxus provides a more concise syntax for async event handlers. If you return a future from an event handler, Dioxus will automatically `spawn` it:\n\n````rust@asynchronous.rs\nlet mut response = use_signal(|| \"Click to start a request\".to_string());\n\nrsx! {\n button {\n // Async closures passed to event handlers are automatically spawned\n onclick: move |_| async move {\n response.set(\"...\".into());\n let resp = reqwest::Client::new()\n .get(\"https://dioxuslabs.com\")\n .send()\n .await;\n\n if resp.is_ok() {\n response.set(\"dioxuslabs.com responded!\".into());\n } else {\n response.set(\"failed to fetch response!\".into());\n }\n },\n \"{response}\"\n }\n}\n````\n\n
\n\nThe future you pass to the `spawn` will automatically be cancelled when the component is unmounted. If you need to keep the future running until it is finished, you can use [`spawn_forever`](https://docs.rs/dioxus/0.6.2/dioxus/prelude/fn.spawn_forever.html) instead.\n\n
\n\n## Asynchronous State with `use_resource`\n\nThe [`use_resource`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_resource.html) can be used to derive asynchronous state. It takes an async closure to calculate the state and returns a tracked value with the current state of the future. Any time a dependency of the resource changes, the resource will rerun:\n\n````rust@asynchronous.rs\nlet mut breed = use_signal(|| \"hound\".to_string());\nlet dogs = use_resource(move || async move {\n reqwest::Client::new()\n // Since breed is read inside the async closure, the resource will subscribe to the signal\n // and rerun when the breed is written to\n .get(format!(\"https://dog.ceo/api/breed/{breed}/images\"))\n .send()\n .await?\n .json::()\n .await\n});\n\nrsx! {\n input {\n value: \"{breed}\",\n // When the input is changed and the breed is set, the resource will rerun\n oninput: move |evt| breed.set(evt.value()),\n }\n\n div {\n display: \"flex\",\n flex_direction: \"row\",\n // You can read resource just like a signal. If the resource is still\n // running, it will return None\n if let Some(response) = &*dogs.read() {\n match response {\n Ok(urls) => rsx! {\n for image in urls.iter().take(3) {\n img {\n src: \"{image}\",\n width: \"100px\",\n height: \"100px\",\n }\n }\n },\n Err(err) => rsx! { \"Failed to fetch response: {err}\" },\n }\n } else {\n \"Loading...\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n asynchronous::UseResource {}\n}\n````\n\nThe `use_resource` hook might look similar to the `use_memo` hook, but unlike `use_memo`, the resource's output is not memoized with `PartialEq`. That means any components/reactive hooks that read the output will rerun if the future reruns even if the value it returns is the same:\n\n````rust@asynchronous.rs\nlet mut number = use_signal(|| 0);\n\n// Resources rerun any time their dependencies change. They will\n// rerun any reactive scopes that read the resource when they finish\n// even if the value hasn't changed\nlet halved_resource = use_resource(move || async move { number() / 2 });\n\nlog!(\"Component reran\");\n\nrsx! {\n button {\n onclick: move |_| number += 1,\n \"Increment\"\n }\n p {\n if let Some(halved) = halved_resource() {\n \"Halved: {halved}\"\n } else {\n \"Loading...\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n asynchronous::UseResourceDemo {}\n}\n````\n\n > \n > Note: The future you pass to `use_resource` must be cancel safe. Cancel safe futures are futures that can be stopped at any point without causing issues. For example, this task is not cancel safe:\n > \n > ````rust@asynchronous.rs\n > static RESOURCES_RUNNING: GlobalSignal> = Signal::global(|| HashSet::new());\n > let mut breed = use_signal(|| \"hound\".to_string());\n > let dogs = use_resource(move || async move {\n > // Modify some global state\n > RESOURCES_RUNNING.write().insert(breed());\n > \n > // Wait for a future to finish. The resource may cancel\n > // without warning if breed is changed while the future is running. If\n > // it does, then the breed pushed to RESOURCES_RUNNING will never be popped\n > let response = reqwest::Client::new()\n > .get(format!(\"https://dog.ceo/api/breed/{breed}/images\"))\n > .send()\n > .await?\n > .json::()\n > .await;\n > \n > // Restore some global state\n > RESOURCES_RUNNING.write().remove(&breed());\n > \n > response\n > });\n > ````\n > \n > ````inject-dioxus\n > DemoFrame {\n > asynchronous::NotCancelSafe {}\n > }\n > ````\n > \n > It can be fixed by making sure the global state is restored when the future is dropped:\n > \n > ````rust@asynchronous.rs\n > static RESOURCES_RUNNING: GlobalSignal> = Signal::global(|| HashSet::new());\n > let mut breed = use_signal(|| \"hound\".to_string());\n > let dogs = use_resource(move || async move {\n > // Modify some global state\n > RESOURCES_RUNNING.write().insert(breed());\n > \n > // Automatically restore the global state when the future is dropped, even if\n > // isn't finished\n > struct DropGuard(String);\n > impl Drop for DropGuard {\n > fn drop(&mut self) {\n > RESOURCES_RUNNING.write().remove(&self.0);\n > }\n > }\n > let _guard = DropGuard(breed());\n > \n > // Wait for a future to finish. The resource may cancel\n > // without warning if breed is changed while the future is running. If\n > // it does, then it will be dropped and the breed will be popped\n > reqwest::Client::new()\n > .get(format!(\"https://dog.ceo/api/breed/{breed}/images\"))\n > .send()\n > .await?\n > .json::()\n > .await\n > });\n > ````\n > \n > ````inject-dioxus\n > DemoFrame {\n > asynchronous::CancelSafe {}\n > }\n > ````\n > \n > Async methods will often mention if they are cancel safe in their documentation.\n\n## Unified Loading Views with suspense\n\n`SuspenseBoundary` is a convenient way to bundle multiple async tasks into a single loading view. It accepts a loading closure and children. You can suspend tasks in children to pause rendering of that child until the future is finished. The suspense boundary will show the loading view instead of the children while any of its children are suspended. Once that suspense is resolved, it will show the children again.\n\nWe can use a suspense boundary to show a grid of different breeds of dogs without handling each loading state individually:\n\n````rust@asynchronous.rs\nfn DogGrid() -> Element {\n rsx! {\n SuspenseBoundary {\n // When any child components (like BreedGallery) are suspended, this closure will\n // be called and the loading view will be rendered instead of the children\n fallback: |_| rsx! {\n div {\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n align_items: \"center\",\n justify_content: \"center\",\n \"Loading...\"\n }\n },\n div {\n display: \"flex\",\n flex_direction: \"column\",\n BreedGallery {\n breed: \"hound\"\n }\n BreedGallery {\n breed: \"poodle\"\n }\n BreedGallery {\n breed: \"beagle\"\n }\n }\n }\n }\n}\n\n#[component]\nfn BreedGallery(breed: ReadOnlySignal) -> Element {\n let response = use_resource(move || async move {\n // Artificially slow down the request to make the loading indicator easier to seer\n gloo_timers::future::TimeoutFuture::new(1000).await;\n reqwest::Client::new()\n .get(format!(\"https://dog.ceo/api/breed/{breed}/images\"))\n .send()\n .await?\n .json::()\n .await\n })\n // Calling .suspend()? will suspend the component and return early while the future is running\n .suspend()?;\n\n // Then you can just handle the happy path with the resolved future\n rsx! {\n div {\n display: \"flex\",\n flex_direction: \"row\",\n match &*response.read() {\n Ok(urls) => rsx! {\n for image in urls.iter().take(3) {\n img {\n src: \"{image}\",\n width: \"100px\",\n height: \"100px\",\n }\n }\n },\n Err(err) => rsx! { \"Failed to fetch response: {err}\" },\n }\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n asynchronous::DogGridView {}\n}\n````\n\nIf you need to change the loading view while a specific task is loading, you can provide a different loading view with the `with_loading_placeholder` method. The loading placeholder you return from the method will be passed to the suspense boundary and may choose to render it instead of the default loading view:\n\n````rust@asynchronous.rs\nfn DogGrid() -> Element {\n rsx! {\n SuspenseBoundary {\n // The fallback closure accepts a SuspenseContext which contains\n // information about the suspended component\n fallback: |suspense_context: SuspenseContext| if let Some(view) = suspense_context.suspense_placeholder() {\n view\n } else {\n rsx! {\n div {\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n align_items: \"center\",\n justify_content: \"center\",\n \"Loading...\"\n }\n }\n },\n div {\n display: \"flex\",\n flex_direction: \"column\",\n BreedGallery {\n breed: \"hound\"\n }\n BreedGallery {\n breed: \"poodle\"\n }\n BreedGallery {\n breed: \"beagle\"\n }\n }\n }\n }\n}\n\n#[component]\nfn BreedGallery(breed: ReadOnlySignal) -> Element {\n let response = use_resource(move || async move {\n gloo_timers::future::TimeoutFuture::new(breed().len() as u32 * 100).await;\n reqwest::Client::new()\n .get(format!(\"https://dog.ceo/api/breed/{breed}/images\"))\n .send()\n .await?\n .json::()\n .await\n })\n .suspend()\n // You can pass up a loading placeholder to the nearest SuspenseBoundary\n // with the with_loading_placeholder method\n .with_loading_placeholder(move || {\n rsx! {\n div {\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n align_items: \"center\",\n justify_content: \"center\",\n \"Loading {breed}...\"\n }\n }\n })?;\n\n // Then you can just handle the happy path with the resolved future\n rsx! {\n div {\n display: \"flex\",\n flex_direction: \"row\",\n match &*response.read() {\n Ok(urls) => rsx! {\n for image in urls.iter().take(3) {\n img {\n src: \"{image}\",\n width: \"100px\",\n height: \"100px\",\n }\n }\n },\n Err(err) => rsx! { \"Failed to fetch response: {err}\" },\n }\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n asynchronous::DogGridViewWithLoadingPlaceholder {}\n}\n````\n\n## Suspense with Fullstack\n\nTo use suspense in your fullstack application, you need to use the `use_server_future` hook instead of `use_resource`. `use_server_future` handles serialization of the result of the future for hydration. It will also suspend automatically, so you don't need to call `.suspend()` on the future.\n\n````rust@asynchronous.rs\n#[component]\nfn BreedGallery(breed: ReadOnlySignal) -> Element {\n // use_server_future is very similar to use_resource, but the value returned from the future\n // must implement Serialize and Deserialize and it is automatically suspended\n let response = use_server_future(move || async move {\n // The future will run on the server during SSR and then get sent to the client\n reqwest::Client::new()\n .get(format!(\"https://dog.ceo/api/breed/{breed}/images\"))\n .send()\n .await\n // reqwest::Result does not implement Serialize, so we need to map it to a string which\n // can be serialized\n .map_err(|err| err.to_string())?\n .json::()\n .await\n .map_err(|err| err.to_string())\n // use_server_future calls `suspend` internally, so you don't need to call it manually, but you\n // do need to bubble up the suspense variant with `?`\n })?;\n\n // If the future was still pending, it would have returned suspended with the `?` above\n // we can unwrap the None case here to get the inner result\n let response_read = response.read();\n let response = response_read.as_ref().unwrap();\n\n // Then you can just handle the happy path with the resolved future\n rsx! {\n div {\n display: \"flex\",\n flex_direction: \"row\",\n match response {\n Ok(urls) => rsx! {\n for image in urls.iter().take(3) {\n img {\n src: \"{image}\",\n width: \"100px\",\n height: \"100px\",\n }\n }\n },\n Err(err) => rsx! { \"Failed to fetch response: {err}\" },\n }\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n asynchronous::DogGridFullstack {}\n}\n````\n\nUnlike `use_resource`, `use_server_future` is only reactive in the closure, not the future itself. If you need to subscribe to another reactive value, you need to read it in the closure before passing it to the future:\n\n````rust@asynchronous.rs\nlet id = use_signal(|| 0);\n// ❌ The future inside of use_server_future is not reactive\nuse_server_future(move || {\n async move {\n // But the future is not reactive which means that the future will not subscribe to any reads here\n println!(\"{id}\");\n }\n});\n// ✅ The closure that creates the future for use_server_future is reactive\nuse_server_future(move || {\n // The closure itself is reactive which means the future will subscribe to any signals you read here\n let cloned_id = id();\n async move {\n // But the future is not reactive which means that the future will not subscribe to any reads here\n println!(\"{cloned_id}\");\n }\n});\n````\n\nWhen you use suspense with fullstack without streaming enabled, dioxus will wait until all suspended futures are resolved before sending the resolved html to the client. If you [enable](https://docs.rs/dioxus/0.6.2/dioxus/prelude/struct.ServeConfigBuilder.html#method.enable_out_of_order_streaming) out of order streaming, dioxus will send the finished HTML chunks to the client one at a time as they are resolved:\n\n````rust@asynchronous.rs\nfn main() {\n dioxus::LaunchBuilder::new()\n .with_context(server_only! {\n // Enable out of order streaming during SSR\n dioxus::fullstack::ServeConfig::builder().enable_out_of_order_streaming()\n })\n .launch(DogGrid);\n}\n````\n\n![Out of order streaming](/assets/06_docs/streaming_dogs.mp4)\n\n## Conclusion\n\nThis guide has covered the basics of asynchronous tasks in Dioxus. More detailed documentation about specific hooks are available in docs.rs:\n\n* [use_resource](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_resource.html)\n* [use_server_future](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_server_future.html)\n* [SuspenseBoundary](https://docs.rs/dioxus/latest/dioxus/prelude/fn.SuspenseBoundary.html)\n* [spawn](https://docs.rs/dioxus/latest/dioxus/prelude/fn.spawn.html)\n* [spawn_forever](https://docs.rs/dioxus/latest/dioxus/prelude/fn.spawn_forever.html)\n\nMore examples of futures and asynchronous tasks are available in the [example folder](https://github.com/DioxusLabs/dioxus/tree/v0.6/examples) in the dioxus repo." + } + 53usize => { + "# Authentication\n\nYou can use [extractors](./extractors) to integrate auth with your Fullstack application.\n\nYou can create a custom extractors that extracts the auth session from the request. From that auth session, you can check if the user has the required privileges before returning the private data.\n\nA [full auth example](https://github.com/DioxusLabs/dioxus/blob/v0.6/examples/fullstack-auth) with the complete implementation is available in the fullstack examples." + } + 67usize => { + "# Tailwind\n\nYou can style your Dioxus application with whatever CSS framework you choose, or just write vanilla CSS.\n\nOne popular option for styling your Dioxus application is [Tailwind](https://tailwindcss.com/). Tailwind allows you to style your elements with CSS utility classes. This guide will show you how to setup Tailwind CSS with your Dioxus application.\n\n## Setup\n\n1. Install the Dioxus CLI:\n\n````bash\ncargo install dioxus-cli\n````\n\n2. Install NPM: [https://docs.npmjs.com/downloading-and-installing-node-js-and-npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)\n\n2. Install the Tailwind CSS CLI: [https://tailwindcss.com/docs/installation/tailwind-cli](https://tailwindcss.com/docs/installation/tailwind-cli)\n\n2. Create a `input.css` file in the root of your project with the following content:\n\n````css\n@import \"tailwindcss\";\n@source \"./src/**/*.{rs,html,css}\";\n````\n\n5. Create a link to the `tailwind.css` file using manganis somewhere in your rust code:\n\n````rust@tailwind.rs\nuse dioxus::prelude::*;\n\n#[component]\nfn app() -> Element {\n rsx! {\n // The Stylesheet component inserts a style link into the head of the document\n document::Stylesheet {\n // Urls are relative to your Cargo.toml file\n href: asset!(\"/assets/tailwind.css\")\n }\n }\n}\n\n````\n\n### Bonus Steps\n\n1. Install the Tailwind CSS VSCode extension\n1. Go to the settings for the extension and find the experimental regex support section. Edit the setting.json file to look like this:\n\n````json\n\"tailwindCSS.experimental.classRegex\": [\"class: \\\"(.*)\\\"\"],\n\"tailwindCSS.includeLanguages\": {\n \"rust\": \"html\"\n},\n````\n\n## Development\n\n* Run the following command in the root of the project to start the Tailwind CSS compiler:\n\n````bash\nnpx @tailwindcss/cli -i ./input.css -o ./assets/tailwind.css --watch\n````\n\n### Web\n\n* Run the following command in the root of the project to start the Dioxus dev server:\n\n````bash\ndx serve\n````\n\n* Open the browser to [http://localhost:8080](http://localhost:8080).\n\n### Desktop\n\n* Launch the Dioxus desktop app:\n\n````bash\ndx serve --platform desktop\n````" + } + 33usize => { + "# Defining Routes\n\nWhen creating a \\[`Routable`\\] enum, we can define routes for our application using the `route(\"path\")` attribute.\n\n## Route Segments\n\nEach route is made up of segments. Most segments are separated by `/` characters in the path.\n\nThere are four fundamental types of segments:\n\n1. [Static segments](#static-segments) are fixed strings that must be present in the path.\n1. [Dynamic segments](#dynamic-segments) are types that can be parsed from a segment.\n1. [Catch-all segments](#catch-all-segments) are types that can be parsed from multiple segments.\n1. [Query segments](#query-segments) are types that can be parsed from the query string.\n\nRoutes are matched:\n\n* First, from most specific to least specific (Static then Dynamic then Catch All) (Query is always matched)\n* Then, if multiple routes match the same path, the order in which they are defined in the enum is followed.\n\n## Static segments\n\nFixed routes match a specific path. For example, the route `#[route(\"/about\")]` will match the path `/about`.\n\n````rust@static_segments.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // Routes always start with a slash\n #[route(\"/\")]\n Home {},\n // You can have multiple segments in a route\n #[route(\"/hello/world\")]\n HelloWorld {},\n}\n\n#[component]\nfn Home() -> Element {\n todo!()\n}\n\n#[component]\nfn HelloWorld() -> Element {\n todo!()\n}\n````\n\n## Dynamic Segments\n\nDynamic segments are in the form of `:name` where `name` is\nthe name of the field in the route variant. If the segment is parsed\nsuccessfully then the route matches, otherwise the matching continues.\n\nThe segment can be of any type that implements `FromStr`.\n\n````rust@dynamic_segments.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // segments that start with : are dynamic segments\n #[route(\"/post/:name\")]\n BlogPost {\n // You must include dynamic segments in child variants\n name: String,\n },\n #[route(\"/document/:id\")]\n Document {\n // You can use any type that implements FromStr\n // If the segment can't be parsed, the route will not match\n id: usize,\n },\n}\n\n// Components must contain the same dynamic segments as their corresponding variant\n#[component]\nfn BlogPost(name: String) -> Element {\n todo!()\n}\n\n#[component]\nfn Document(id: usize) -> Element {\n todo!()\n}\n````\n\n## Catch All Segments\n\nCatch All segments are in the form of `:..name` where `name` is the name of the field in the route variant. If the segments are parsed successfully then the route matches, otherwise the matching continues.\n\nThe segment can be of any type that implements `FromSegments`. (Vec implements this by default)\n\nCatch All segments must be the *last route segment* in the path (query segments are not counted) and cannot be included in nests.\n\n````rust@catch_all_segments.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // segments that start with :.. are catch all segments\n #[route(\"/blog/:..segments\")]\n BlogPost {\n // You must include catch all segment in child variants\n segments: Vec,\n },\n}\n\n// Components must contain the same catch all segments as their corresponding variant\n#[component]\nfn BlogPost(segments: Vec) -> Element {\n todo!()\n}\n````\n\n## Query Segments\n\nQuery segments are in the form of `?:name&:othername` where `name` and `othername` are the names of fields in the route variant.\n\nUnlike [Dynamic Segments](#dynamic-segments) and [Catch All Segments](#catch-all-segments), parsing a Query segment must not fail.\n\nThe segment can be of any type that implements `FromQueryArgument`.\n\nQuery segments must be the *after all route segments* and cannot be included in nests.\n\n````rust@query_segments.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // segments that start with ?: are query segments\n #[route(\"/blog?:name&:surname\")]\n BlogPost {\n // You must include query segments in child variants\n name: String,\n surname: String,\n },\n}\n\n#[component]\nfn BlogPost(name: String, surname: String) -> Element {\n rsx! {\n div { \"This is your blogpost with a query segment:\" }\n div { \"Name: {name}\" }\n div { \"Surname: {surname}\" }\n }\n}\n\nfn App() -> Element {\n rsx! { Router:: {} }\n}\n\nfn main() {}\n````" + } + 44usize => { + "# Mobile App\n\nBuild a mobile app with Dioxus!\n\n## Support\n\nThe Rust ecosystem for mobile continues to mature, with Dioxus offering strong support for mobile applications. Mobile is a first-class target for Dioxus apps, with a robust WebView implementation that supports CSS animations and transparency effects.\n\nMobile apps are rendered with either the platform's WebView or experimentally with WGPU. While native Android animations and widgets aren't currently supported, CSS-based animations and styling provide a powerful alternative.\n\nMobile support is well-suited for most application types, from business tools to consumer apps, making it an excellent choice for teams looking to build cross-platform applications with a single codebase.\n\n## Getting Set up\n\n## Android\n\nAndroid devices run a different executable architecture than desktop and web. We need to install these toolchains to build Dioxus apps for Android.\n\nFirst, install the Rust Android targets:\n\n````sh\nrustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android\n````\n\nTo develop on Android, you will need to [install Android Studio](https://developer.android.com/studio).\n\nOnce you have installed Android Studio, you will need to install the Android SDK and NDK:\n\n1. Create a blank Android project\n1. Select `Tools > SDK manager`\n1. Navigate to the `SDK tools` window:\n\n![NDK install window](/assets/static/android_ndk_install.png)\n\nThen select:\n\n* The SDK\n* The SDK Command line tools\n* The NDK (side by side)\n* CMAKE\n\n4. Select `apply` and follow the prompts\n\n > \n > More details that could be useful for debugging any errors you encounter are available [in the official android docs](https://developer.android.com/studio/intro/update#sdk-manager)\n\nNext set the Java, Android, NDK, and PATH variables:\n\nMac:\n\n````sh\nexport JAVA_HOME=\"/Applications/Android Studio.app/Contents/jbr/Contents/Home\"\nexport ANDROID_HOME=\"$HOME/Library/Android/sdk\"\nexport NDK_HOME=\"$ANDROID_HOME/ndk/25.2.9519653\"\nexport PATH=\"$PATH:$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools\"\n````\n\nWindows:\n\n````powershell\n[System.Environment]::SetEnvironmentVariable(\"JAVA_HOME\", \"C:\\Program Files\\Android\\Android Studio\\jbr\", \"User\")\n[System.Environment]::SetEnvironmentVariable(\"ANDROID_HOME\", \"$env:LocalAppData\\Android\\Sdk\", \"User\")\n[System.Environment]::SetEnvironmentVariable(\"NDK_HOME\", \"$env:LocalAppData\\Android\\Sdk\\ndk\\25.2.9519653\", \"User\")\n````\n\n > \n > The NDK version in the paths should match the version you installed in the last step\n\nWe manually set the PATH variable to include the Android emulator since some distributions of Android Studio include the emulator in the wrong location.\n\n## IOS\n\nTo develop on IOS, you will need to [install XCode](https://apps.apple.com/us/app/xcode/id497799835). Also make sure to install the iOS targets\n\n````sh\nrustup target add aarch64-apple-ios aarch64-apple-ios-sim\n````\n\n > \n > If you are using M1, you will have to run `cargo build --target x86_64-apple-ios` instead of `cargo apple build` if you want to run in simulator.\n\nYou will also need to install the iOS SDK and the Xcode command line tools.\n\n## Running your app\n\nStarting with Dioxus 0.6, `dx` ships with built-in support for mobile.\n\nSimply create a new Dioxus project:\n\n````sh\ndx new my-app\n````\n\nMake sure to launch the relevant mobile simulator. For Android, you can use the Android Studio emulator, or the Android Emulator in the terminal. Make sure to adjust the device name depending on which emulator you installed.\n\n````shell\nemulator -avd Pixel_6_API_34 -netdelay none -netspeed full\n````\n\nFor iOS, you can use the iOS simulator. You can launch it with:\n\n````sh\nopen /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app\nxcrun simctl boot \"iPhone 15 Pro Max\"\n````\n\nAnd then run the app with:\n\n````sh\ncd my-app\ndx serve\n````\n\nThis will start the app in development mode." + } + 74usize => { + "# Component Props\n\nJust like you can pass arguments to a function or attributes to an element, 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.\n\n## derive(Props)\n\nComponent props are a single struct annotated with `#[derive(PartialEq, Clone, Props)]`. For a component to accept props, the type of its argument must be `YourPropsStruct`.\n\nExample:\n\n````rust, no_run@component_owned_props.rs\n#[derive(PartialEq, Props, Clone)]\nstruct LikesProps {\n score: i32,\n}\n\nfn Likes(props: LikesProps) -> Element {\n rsx! {\n div {\n \"This post has \"\n b { \"{props.score}\" }\n \" likes\"\n }\n }\n}\n````\n\nYou can then pass prop values to the component the same way you would pass attributes to an element:\n\n````rust, no_run@component_owned_props.rs\npub fn App() -> Element {\n rsx! { Likes { score: 42 } }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n component_owned_props::App {}\n}\n````\n\n## Prop Options\n\nThe `#[derive(Props)]` macro has some features that let you customize the behavior of props.\n\n### Optional Props\n\nYou can create optional fields by using the `Option<…>` type for a field:\n\n````rust, no_run@component_props_options.rs\n#[derive(PartialEq, Clone, Props)]\nstruct OptionalProps {\n title: String,\n subtitle: Option,\n}\n\nfn Title(props: OptionalProps) -> Element {\n rsx! {\n h1 { \"{props.title}: \", {props.subtitle.unwrap_or_else(|| \"No subtitle provided\".to_string())} }\n }\n}\n````\n\nThen, you can choose to either provide them or not:\n\n````rust, no_run@component_props_options.rs\nTitle { title: \"Some Title\" }\nTitle { title: \"Some Title\", subtitle: \"Some Subtitle\" }\n// Providing an Option explicitly won't compile though:\n// Title {\n// title: \"Some Title\",\n// subtitle: None,\n// },\n````\n\n### Explicitly Required Option\n\nIf you want to explicitly require an `Option`, and not an optional prop, you can annotate it with `#[props(!optional)]`:\n\n````rust, no_run@component_props_options.rs\n#[derive(PartialEq, Clone, Props)]\nstruct ExplicitOptionProps {\n title: String,\n #[props(!optional)]\n subtitle: Option,\n}\n\nfn ExplicitOption(props: ExplicitOptionProps) -> Element {\n rsx! {\n h1 { \"{props.title}: \", {props.subtitle.unwrap_or_else(|| \"No subtitle provided\".to_string())} }\n }\n}\n````\n\nThen, you have to explicitly pass either `Some(\"str\")` or `None`:\n\n````rust, no_run@component_props_options.rs\nExplicitOption { title: \"Some Title\", subtitle: None }\nExplicitOption { title: \"Some Title\", subtitle: Some(\"Some Title\".to_string()) }\n// This won't compile:\n// ExplicitOption {\n// title: \"Some Title\",\n// },\n````\n\n### Default Props\n\nYou can use `#[props(default = 42)]` to make a field optional and specify its default value:\n\n````rust, no_run@component_props_options.rs\n#[derive(PartialEq, Props, Clone)]\nstruct DefaultProps {\n // default to 42 when not provided\n #[props(default = 42)]\n number: i64,\n}\n\nfn DefaultComponent(props: DefaultProps) -> Element {\n rsx! { h1 { \"{props.number}\" } }\n}\n````\n\nThen, similarly to optional props, you don't have to provide it:\n\n````rust, no_run@component_props_options.rs\nDefaultComponent { number: 5 }\nDefaultComponent {}\n````\n\n### Automatic Conversion with into\n\nIt 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`:\n\n````rust, no_run@component_props_options.rs\n#[derive(PartialEq, Props, Clone)]\nstruct IntoProps {\n #[props(into)]\n string: String,\n}\n\nfn IntoComponent(props: IntoProps) -> Element {\n rsx! { h1 { \"{props.string}\" } }\n}\n````\n\nThen, you can use it so:\n\n````rust, no_run@component_props_options.rs\nIntoComponent { string: \"some &str\" }\n````\n\n## The component macro\n\nSo 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 `props.whatever`, we could just use `whatever` directly!\n\n`component` allows you to do just that. Instead of typing the \"full\" version:\n\n````rust, no_run\n#[derive(Props, Clone, PartialEq)]\nstruct TitleCardProps {\n title: String,\n}\n\nfn TitleCard(props: TitleCardProps) -> Element {\n rsx!{\n h1 { \"{props.title}\" }\n }\n}\n````\n\n...you can define a function that accepts props as arguments. Then, just annotate it with `#[component]`, and the macro will turn it into a regular Component for you:\n\n````rust, no_run\n#[component]\nfn TitleCard(title: String) -> Element {\n rsx!{\n h1 { \"{title}\" }\n }\n}\n````\n\n > \n > 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.\n\n## Component Children\n\nIn 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`:\n\n````rust, no_run@component_element_props.rs\n#[derive(PartialEq, Clone, Props)]\nstruct ClickableProps {\n href: String,\n body: Element,\n}\n\nfn Clickable(props: ClickableProps) -> Element {\n rsx! {\n a { href: \"{props.href}\", class: \"fancy-button\", {props.body} }\n }\n}\n````\n\nThen, when rendering the component, you can pass in the output of `rsx!{...}`:\n\n````rust, no_run@component_element_props.rs\nrsx! {\n Clickable {\n href: \"https://www.youtube.com/watch?v=C-M2hs3sXGo\",\n body: rsx! {\n \"How to \" i { \"not\" } \" be seen\"\n }\n }\n}\n````\n\n > \n > Warning: While it may compile, do not include the same `Element` more than once in the RSX. The resulting behavior is unspecified.\n\n### The children field\n\nRather 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:\n\n````rust, no_run@component_children.rs\n#[derive(PartialEq, Clone, Props)]\nstruct ClickableProps {\n href: String,\n children: Element,\n}\n\nfn Clickable(props: ClickableProps) -> Element {\n rsx! {\n a { href: \"{props.href}\", class: \"fancy-button\", {props.children} }\n }\n}\n````\n\nThis 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!\n\n````rust, no_run@component_children.rs\nrsx! {\n Clickable { href: \"https://www.youtube.com/watch?v=C-M2hs3sXGo\",\n \"How to \"\n i { \"not\" }\n \" be seen\"\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n component_children::App {}\n}\n````" + } + 42usize => { + "# Web\n\nTo run on the Web, your app must be compiled to WebAssembly and depend on the `dioxus` and `dioxus-web` crates.\n\nA build of Dioxus for the web will be roughly equivalent to the size of a React build (70kb vs 65kb) but it will load significantly faster because [WebAssembly can be compiled as it is streamed](https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/).\n\nExamples:\n\n* [TodoMVC](https://github.com/DioxusLabs/dioxus/blob/main/examples/todomvc.rs)\n* [Tailwind App](https://github.com/DioxusLabs/dioxus/tree/main/examples/tailwind)\n\n[![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/dioxus/blob/main/examples/todomvc.rs)\n\n > \n > Note: Because of the limitations of Wasm, [not every crate will work](https://rustwasm.github.io/docs/book/reference/which-crates-work-with-wasm.html) with your web apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc).\n\n## Support\n\nThe Web is the best-supported target platform for Dioxus.\n\n* Because your app will be compiled to WASM you have access to browser APIs through [wasm-bindgen](https://rustwasm.github.io/docs/wasm-bindgen/introduction.html).\n* Dioxus provides hydration to resume apps that are rendered on the server. See the [fullstack](../fullstack/index.md) reference for more information.\n\n## Running Javascript\n\nDioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose.\n\nFor these cases, Dioxus web exposes the use_eval hook that allows you to run raw Javascript in the webview:\n\n````rust@eval.rs\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app);\n}\n\nfn app() -> Element {\n let future = use_resource(move || async move {\n // You can create as many eval instances as you want\n let mut eval = document::eval(\n r#\"\n // You can send messages from JavaScript to Rust with the dioxus.send function\n dioxus.send(\"Hi from JS!\");\n // You can receive messages from Rust to JavaScript with the dioxus.recv function\n let msg = await dioxus.recv();\n console.log(msg);\n \"#,\n );\n\n // You can send messages to JavaScript with the send method\n eval.send(\"Hi from Rust!\").unwrap();\n\n // You can receive any message from JavaScript with the recv method\n eval.recv::().await.unwrap()\n });\n\n match future.read_unchecked().as_ref() {\n Some(v) => rsx! {\n p { \"{v}\" }\n },\n _ => rsx! {\n p { \"hello\" }\n },\n }\n}\n\n````\n\nIf you are targeting web, but don't plan on targeting any other Dioxus renderer you can also use the generated wrappers in the [web-sys](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html) and [gloo](https://gloo-rs.web.app/) crates.\n\n## Customizing Index Template\n\nDioxus supports providing custom index.html templates. The index.html must include a `div` with the id `main` to be used. Hot Reload is still supported. An example\nis provided in the [PWA-Example](https://github.com/DioxusLabs/dioxus/blob/main/examples/pwa/index.html)." + } + 2usize => { + "# Dioxus Tutorial\n\nIn this tutorial, we'll be building a small app called: *HotDog* - basically Tinder, but for dogs! This app will serve as a great way to learn about building UIs, adding state, and deploying.\n\nBy the end of this tutorial, you will launch your very own web, desktop, and mobile apps and a backend deployed to [Fly.io](http://fly.io).\n\n![Photo of HotDog](/assets/06_docs/dog_app_styled.png)\n\nWe will primarily focus on the higher-level concepts of Dioxus without diving deep into the details of specific APIs. We recommend experimenting with the APIs yourself or reading the [Core Concepts](../essentials/index.md) and specific [Guides](../guides/index.md) for more information.\n\n## What will we be learning?\n\nThis guide will cover the \"core\" Dioxus features including:\n\n* [Tooling Setup](tooling.md)\n* [Creating a new app](new_app.md)\n* [How Components Work](component.md)\n* [Creating UI with RSX](rsx.md)\n* [Styling and Assets](assets.md)\n* [Adding State](state.md)\n* [Fetching Data](data_fetching.md)\n* [Adding a Backend](backend.md)\n* [Integrating a Database](databases.md)\n* [App Routing](routing.md)\n* [Bundling](bundle.md)\n* [Deployment](deploy.md)\n* [Next Steps](next_steps.md)\n\nDioxus is a very full-featured framework, so we encourage you to follow up this tutorial by building your own larger apps.\n\n## What are we building?\n\nThe features of *HotDog* are fairly simple:\n\n* Engage with a stream of cute dog photos\n* Swipe right if we want to save the dog photo to our collection\n* Swipe left if we don't want to save the dog photo\n* View the dog photos we saved later\n\nAt the end of the tutorial, you'll have your very own *HotDog* app to remix and download to your device." + } + 65usize => { + "### Bundling config\n\nThe `[bundle]` section of our Dioxus.toml can take a variety of options.\n\nHere are the options, in the form of Rust structs.\n\n````rust\n#[derive(Debug, Clone, Serialize, Deserialize, Default)]\npub(crate) struct BundleConfig {\n /// eg. com.dioxuslabs\n pub(crate) identifier: Option,\n /// eg. DioxusLabs\n pub(crate) publisher: Option,\n /// eg. assets/icon.png\n pub(crate) icon: Option>,\n /// eg. Extra assets like \"img.png\"\n pub(crate) resources: Option>,\n /// eg. DioxusLabs\n pub(crate) copyright: Option,\n /// eg. \"Social Media\"\n pub(crate) category: Option,\n /// eg. \"A great social media app\"\n pub(crate) short_description: Option,\n /// eg. \"A social media app that makes people love app development\"\n pub(crate) long_description: Option,\n /// eg. extra binaries (like tools) to include in the final app\n pub(crate) external_bin: Option>,\n /// Additional debian-only settings (see below)\n pub(crate) deb: Option,\n /// Additional macos settings (see below)\n pub(crate) macos: Option,\n /// Additional windows settings (see below)\n pub(crate) windows: Option,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default)]\npub(crate) struct DebianSettings {\n // OS-specific settings:\n /// the list of debian dependencies.\n pub depends: Option>,\n /// the list of dependencies the package provides.\n pub provides: Option>,\n /// the list of package conflicts.\n pub conflicts: Option>,\n /// the list of package replaces.\n pub replaces: Option>,\n /// List of custom files to add to the deb package.\n /// Maps the path on the debian package to the path of the file to include (relative to the current working directory).\n pub files: HashMap,\n /// Path to a custom desktop file Handlebars template.\n ///\n /// Available variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\n pub desktop_template: Option,\n /// Define the section in Debian Control file. See : \n pub section: Option,\n /// Change the priority of the Debian Package. By default, it is set to `optional`.\n /// Recognized Priorities as of now are : `required`, `important`, `standard`, `optional`, `extra`\n pub priority: Option,\n /// Path of the uncompressed Changelog file, to be stored at /usr/share/doc/package-name/changelog.gz. See\n /// \n pub changelog: Option,\n /// Path to script that will be executed before the package is unpacked. See\n /// \n pub pre_install_script: Option,\n /// Path to script that will be executed after the package is unpacked. See\n /// \n pub post_install_script: Option,\n /// Path to script that will be executed before the package is removed. See\n /// \n pub pre_remove_script: Option,\n /// Path to script that will be executed after the package is removed. See\n /// \n pub post_remove_script: Option,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default)]\npub(crate) struct WixSettings {\n pub(crate) language: Vec<(String, Option)>,\n pub(crate) template: Option,\n pub(crate) fragment_paths: Vec,\n pub(crate) component_group_refs: Vec,\n pub(crate) component_refs: Vec,\n pub(crate) feature_group_refs: Vec,\n pub(crate) feature_refs: Vec,\n pub(crate) merge_refs: Vec,\n pub(crate) skip_webview_install: bool,\n pub(crate) license: Option,\n pub(crate) enable_elevated_update_task: bool,\n pub(crate) banner_path: Option,\n pub(crate) dialog_image_path: Option,\n pub(crate) fips_compliant: bool,\n /// MSI installer version in the format `major.minor.patch.build` (build is optional).\n ///\n /// Because a valid version is required for MSI installer, it will be derived from [`PackageSettings::version`] if this field is not set.\n ///\n /// The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255.\n /// The third and fourth fields have a maximum value of 65,535.\n ///\n /// See for more info.\n pub version: Option,\n /// A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**,\n /// otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.\n ///\n /// By default, tauri generates this code by generating a Uuid v5 using the string `.exe.app.x64` in the DNS namespace.\n /// You can use Tauri's CLI to generate and print this code for you by running `tauri inspect wix-upgrade-code`.\n ///\n /// It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code\n /// whenever you want to change your product name.\n pub upgrade_code: Option,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, Default)]\npub(crate) struct MacOsSettings {\n pub(crate) frameworks: Option>,\n pub(crate) minimum_system_version: Option,\n pub(crate) license: Option,\n pub(crate) exception_domain: Option,\n pub(crate) signing_identity: Option,\n pub(crate) provider_short_name: Option,\n pub(crate) entitlements: Option,\n pub(crate) info_plist_path: Option,\n /// List of custom files to add to the application bundle.\n /// Maps the path in the Contents directory in the app to the path of the file to include (relative to the current working directory).\n pub files: HashMap,\n /// Preserve the hardened runtime version flag, see \n ///\n /// Settings this to `false` is useful when using an ad-hoc signature, making it less strict.\n pub hardened_runtime: bool,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub(crate) struct WindowsSettings {\n pub(crate) digest_algorithm: Option,\n pub(crate) certificate_thumbprint: Option,\n pub(crate) timestamp_url: Option,\n pub(crate) tsp: bool,\n pub(crate) wix: Option,\n pub(crate) icon_path: Option,\n pub(crate) webview_install_mode: WebviewInstallMode,\n pub(crate) webview_fixed_runtime_path: Option,\n pub(crate) allow_downgrades: bool,\n pub(crate) nsis: Option,\n /// Specify a custom command to sign the binaries.\n /// This command needs to have a `%1` in it which is just a placeholder for the binary path,\n /// which we will detect and replace before calling the command.\n ///\n /// Example:\n /// ```text\n /// sign-cli --arg1 --arg2 %1\n /// ```\n ///\n /// By Default we use `signtool.exe` which can be found only on Windows so\n /// if you are on another platform and want to cross-compile and sign you will\n /// need to use another tool like `osslsigncode`.\n pub sign_command: Option,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub(crate) struct NsisSettings {\n pub(crate) template: Option,\n pub(crate) license: Option,\n pub(crate) header_image: Option,\n pub(crate) sidebar_image: Option,\n pub(crate) installer_icon: Option,\n pub(crate) install_mode: NSISInstallerMode,\n pub(crate) languages: Option>,\n pub(crate) custom_language_files: Option>,\n pub(crate) display_language_selector: bool,\n pub(crate) start_menu_folder: Option,\n pub(crate) installer_hooks: Option,\n /// Try to ensure that the WebView2 version is equal to or newer than this version,\n /// if the user's WebView2 is older than this version,\n /// the installer will try to trigger a WebView2 update.\n pub minimum_webview2_version: Option,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub(crate) enum NSISInstallerMode {\n CurrentUser,\n PerMachine,\n Both,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub(crate) enum WebviewInstallMode {\n Skip,\n DownloadBootstrapper { silent: bool },\n EmbedBootstrapper { silent: bool },\n OfflineInstaller { silent: bool },\n FixedRuntime { path: PathBuf },\n}\n\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct CustomSignCommandSettings {\n /// The command to run to sign the binary.\n pub cmd: String,\n /// The arguments to pass to the command.\n ///\n /// \"%1\" will be replaced with the path to the binary to be signed.\n pub args: Vec,\n}\n\n#[derive(Clone, Copy, Debug)]\npub(crate) enum PackageType {\n /// \"macos\"\n MacOsBundle,\n /// \"ios\"\n IosBundle,\n /// \"msi\"\n WindowsMsi,\n /// \"nsis\"\n Nsis,\n /// \"deb\"\n Deb,\n /// \"rpm\"\n Rpm,\n /// \"appimage\"\n AppImage,\n /// \"dmg\"\n Dmg,\n /// \"updater\"\n Updater,\n}\n````" + } + 21usize => { + "# Breaking Out of Dioxus\n\nDioxus makes it easy to build reactive user interfaces. However, there are some cases where you may need to break out of the reactive paradigm to interact with the DOM directly.\n\n## Interacting with JavaScript with `eval` and `web-sys`\n\nDioxus exposes a limited number of [web apis](https://developer.mozilla.org/en-US/docs/Web/API) with a nicer interface. If you need access to more APIs, you can use the `eval` function to run JavaScript in the browser.\n\nFor example, you can use the eval function to read the domain of the current page:\n\n````rust, no_run@breaking_out.rs\npub fn Eval() -> Element {\n let mut domain = use_signal(String::new);\n rsx! {\n button {\n // When you click the button, some javascript will run in the browser\n // to read the domain and set the signal\n onclick: move |_| async move {\n domain.set(document::eval(\"return document.domain\").await.unwrap().to_string());\n },\n \"Read Domain\"\n }\n \"Current domain: {domain}\"\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n breaking_out::Eval {}\n}\n````\n\nIf you are only targeting web, you can also use the [`web-sys`](https://crates.io/crates/web-sys) crate for typed access to the web APIs. Here is what reading the domain looks like with web-sys:\n\n````rust, no_run@breaking_out.rs\nuse ::web_sys::window;\nuse wasm_bindgen::JsCast;\npub fn WebSys() -> Element {\n let mut domain = use_signal(String::new);\n rsx! {\n button {\n // When you click the button, we use web-sys to read the domain and a signal\n onclick: move |_| {\n domain\n .set(\n window()\n .unwrap()\n .document()\n .unwrap()\n .dyn_into::<::web_sys::HtmlDocument>()\n .unwrap()\n .domain(),\n );\n },\n \"Read Domain\"\n }\n \"Current domain: {domain}\"\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n breaking_out::WebSys {}\n}\n````\n\n## Synchronizing DOM updates with `use_effect`\n\nIf you do need to interact with the DOM directly, you should do so in a `use_effect` hook. This hook will run after the component is rendered and all of the Dioxus UI has been rendered. You can read or modify the DOM in this hook.\n\nFor example, you can use the `use_effect` hook to write to a canvas element after it is created:\n\n````rust, no_run@breaking_out.rs\npub fn Canvas() -> Element {\n let mut count = use_signal(|| 0);\n\n use_effect(move || {\n // Effects are reactive like memos, and resources. If you read a value inside the effect, the effect will rerun when that value changes\n let count = count.read();\n\n // You can use the count value to update the DOM manually\n document::eval(&format!(\n r#\"var c = document.getElementById(\"dioxus-canvas\");\nvar ctx = c.getContext(\"2d\");\nctx.clearRect(0, 0, c.width, c.height);\nctx.font = \"30px Arial\";\nctx.fillText(\"{count}\", 10, 50);\"#\n ));\n });\n\n rsx! {\n button {\n // When you click the button, count will be incremented and the effect will rerun\n onclick: move |_| count += 1,\n \"Increment\"\n }\n canvas { id: \"dioxus-canvas\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n breaking_out::Canvas {}\n}\n````\n\n## Getting access to elements with `onmounted`\n\nIf you need a handle to an element that is rendered by dioxus, you can use the `onmounted` event. This event will fire after the element is first mounted to the DOM. It returns a live reference to the element with some methods to interact with it.\n\nYou can use the onmounted event to do things like focus or scroll to an element after it is rendered:\n\n````rust, no_run@breaking_out.rs\npub fn OnMounted() -> Element {\n let mut input_element = use_signal(|| None);\n\n rsx! {\n div { height: \"100px\",\n button {\n class: \"focus:outline-2 focus:outline-blue-600 focus:outline-dashed\",\n // The onmounted event will run the first time the button element is mounted\n onmounted: move |element| input_element.set(Some(element.data())),\n \"First button\"\n }\n\n button {\n // When you click the button, if the button element has been mounted, we focus to that element\n onclick: move |_| async move {\n if let Some(header) = input_element() {\n let _ = header.set_focus(true).await;\n }\n },\n \"Focus first button\"\n }\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n breaking_out::OnMounted {}\n}\n````\n\n## Down casting web sys events\n\nDioxus provides platform agnostic wrappers over each event type. These wrappers are often nicer to interact with than the raw event types, but they can be more limited. If you are targeting web, you can downcast the event with the `as_web_event` method to get the underlying web-sys event:\n\n````rust, no_run@breaking_out.rs\npub fn Downcast() -> Element {\n let mut event_text = use_signal(|| 0);\n\n rsx! {\n div {\n onmousemove: move |event| {\n #[cfg(feature = \"web\")]\n {\n use dioxus::web::WebEventExt;\n event_text.set(event.as_web_event().movement_x());\n }\n },\n \"movement_x was {event_text}\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n breaking_out::Downcast {}\n}\n````" + } + 55usize => { + "# Static Site Generation\n\nStatic site generation (SSG) lets you pre-generate all static pages of your application at build time. Once you have the static html pages, you can deploy them to any static hosting provider like GitHub Pages.\n\n## Setting up the ServeConfig\n\nSSG builds on top of the incremental rendering feature of Dioxus fullstack. We need to set up the `ServeConfig` to enable incremental rendering. The incremental config needs to render to the `public` directory where Dioxus places all other public files like the wasm binary and static assets. The `public` directory in the web folder will always be placed alongside the server binary.\n\n````rust@static_site_generation.rs\nfn main() {\n dioxus::LaunchBuilder::new()\n // Set the server config only if we are building the server target\n .with_cfg(server_only! {\n ServeConfig::builder()\n // Enable incremental rendering\n .incremental(\n IncrementalRendererConfig::new()\n // Store static files in the public directory where other static assets like wasm are stored\n .static_dir(\n std::env::current_exe()\n .unwrap()\n .parent()\n .unwrap()\n .join(\"public\")\n )\n // Don't clear the public folder on every build. The public folder has other files including the wasm\n // binary and static assets required for the app to run\n .clear_cache(false)\n )\n .enable_out_of_order_streaming()\n })\n .launch(app);\n}\n````\n\n## Configuring static routes\n\nOnce you have incremental rendering enabled, you need to tell the CLI about the static routes in your app. The CLI looks for a server function at the endpoint `\"static_routes\"` that returns a list of all static urls. It will call this server function at build time and pre-render all of the routes in the list.\n\n````rust@static_site_generation.rs\n#[derive(Routable, Clone, PartialEq)]\npub enum Route {\n // Any routes with no dynamic segments in your router will be included in the static routes list\n #[route(\"/\")]\n Index {},\n #[route(\"/other\")]\n Other {},\n}\n\n// The server function at the endpoint \"static_routes\" will be called by the CLI to generate the list of static\n// routes. You must explicitly set the endpoint to `\"static_routes\"` in the server function attribute instead of\n// the default randomly generated endpoint.\n#[server(endpoint = \"static_routes\", output = server_fn::codec::Json)]\nasync fn static_routes() -> Result, ServerFnError> {\n // The `Routable` trait has a `static_routes` method that returns all static routes in the enum\n Ok(Route::static_routes().iter().map(ToString::to_string).collect())\n}\n````\n\n## Publishing static sites\n\nFinally, you can bundle your site with `dx bundle --platform web --ssg`. Once the CLI finishes bundling, you should see a `public` folder in the dx folder of your project:\n\n![Dioxus SSG](/assets/06_docs/ssg_folder.png)\n\nThe folder contains all of the static assets that you need to serve your site. You can copy the public folder into any static hosting provider like GitHub Pages." + } + 38usize => { + "# History Providers\n\n\\[`HistoryProvider`\\]s are used by the router to keep track of the navigation history\nand update any external state (e.g. the browser's URL).\n\nThe router provides two \\[`HistoryProvider`\\]s, but you can also create your own.\nThe two default implementations are:\n\n* The \\[`MemoryHistory`\\] is a custom implementation that works in memory.\n* The \\[`LiveviewHistory`\\] is a custom implementation that works with the liveview renderer.\n* The \\[`WebHistory`\\] integrates with the browser's URL.\n\nBy default, the router uses the \\[`MemoryHistory`\\]. It might be changed to use\n\\[`WebHistory`\\] when the `web` feature is active, but that is not guaranteed.\n\nYou can override the default history:\n\n````rust@history_provider.rs\n#[component]\nfn App() -> Element {\n rsx! {\n Router:: { config: || RouterConfig::default() }\n }\n}\n````" + } + 30usize => { + "# Redirection Perfection\n\nYou're well on your way to becoming a routing master!\n\nIn this chapter, we will cover creating redirects\n\n## Creating Redirects\n\nA redirect is very simple. When dioxus encounters a redirect while finding out\nwhat components to render, it will redirect the user to the target of the\nredirect.\n\nAs a simple example, let's say you want user to still land on your blog, even\nif they used the path `/myblog` or `/myblog/:name`.\n\nRedirects are special attributes in the router enum that accept a route and a closure\nwith the route parameters. The closure should return a route to redirect to.\n\nLet's add a redirect to our router enum:\n\n````rust@full_example.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(NavBar)]\n #[route(\"/\")]\n Home {},\n #[nest(\"/blog\")]\n #[layout(Blog)]\n #[route(\"/\")]\n BlogList {},\n #[route(\"/post/:name\")]\n BlogPost { name: String },\n #[end_layout]\n #[end_nest]\n #[end_layout]\n #[nest(\"/myblog\")]\n #[redirect(\"/\", || Route::BlogList {})]\n #[redirect(\"/:name\", |name: String| Route::BlogPost { name })]\n #[end_nest]\n #[route(\"/:..route\")]\n PageNotFound {\n route: Vec,\n },\n}\n````\n\nThat's it! Now your users will be redirected to the blog.\n\n### Conclusion\n\nWell done! You've completed the Dioxus Router guide. You've built a small\napplication and learned about the many things you can do with Dioxus Router.\nTo continue your journey, you attempt a challenge listed below, look at the [router examples](https://github.com/DioxusLabs/dioxus/tree/main/packages/router/examples), or\nthe [API reference](https://docs.rs/dioxus-router/).\n\n### Challenges\n\n* Organize your components into separate files for better maintainability.\n* Give your app some style if you haven't already.\n* Build an about page so your visitors know who you are.\n* Add a user system that uses URL parameters.\n* Create a simple admin system to create, delete, and edit blogs.\n* If you want to go to the max, hook up your application to a rest API and database." + } + 39usize => { + "# History Buttons\n\nSome platforms, like web browsers, provide users with an easy way to navigate\nthrough an app's history. They have UI elements or integrate with the OS.\n\nHowever, native platforms usually don't provide such amenities, which means that\napps wanting users to have access to them, need to implement them. For this\nreason, the router comes with two components, which emulate a browser's back and\nforward buttons:\n\n* [`GoBackButton`]\n* [`GoForwardButton`]\n\n > \n > If you want to navigate through the history programmatically, take a look at\n > [`programmatic navigation`](./navigation/programmatic.md).\n\n````rust@history_buttons.rs\nfn HistoryNavigation() -> Element {\n rsx! {\n GoBackButton { \"Back to the Past\" }\n GoForwardButton { \"Back to the Future\" }\n }\n}\n````\n\nAs you might know, browsers usually disable the back and forward buttons if\nthere is no history to navigate to. The router's history buttons try to do that\ntoo, but depending on the \\[history provider\\] that might not be possible.\n\nImportantly, neither `WebHistory` supports that feature.\nThis is due to limitations of the browser History API.\n\nHowever, in both cases, the router will just ignore button presses, if there is\nno history to navigate to.\n\nAlso, when using `WebHistory`, the history buttons might\nnavigate a user to a history entry outside your app." + } + 13usize => { + "# Bundling\n\nCongratulations! You built your first fully-functional Dioxus app, completely loaded with Routing, asynchronous data-fetching, Server Functions, and a database! That's incredible for just a few minutes of work.\n\nLet's get your app bundled for multiple platforms and then ready to deploy.\n\n## Testing on Desktop and Mobile\n\nSo far, we've been testing our app in a simple web browser. Let's actually build and test our app for mobile platforms.\n\nIn Dioxus 0.6, `dx` finally supports `dx serve` for Android and iOS!\n\n### Testing on iOS\n\nTo test iOS, your development environment needs to be setup to build iOS apps. This involves a few steps:\n\n* Make sure you are developing on a device running macOS\n* Install XCode\n* [Download a recent iOS SDK and Emulator pack](https://developer.apple.com/ios/)\n* Install the iOS Rust toolchains (`aarch64-apple-ios aarch64-apple-ios-sim`)\n\nThis is a multi-step process and requires creating an Apple Developer account. You shouldn't need to pay any fees until you want to sign your app. Signing your app is required for deploying to the Apple App Store and testing on your iOS device.\n\nIf everything is installed properly, you should be able to open the Simulator app:\n\n````sh\nopen /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app\n````\n\nIf the Simulator app opens but no device pops up, you might need to open a specific device. Use `xcrun` to discover which devices you have installed.\n\n````sh\nxcrun simctl list\n````\n\nIdentify an available device. We're going to simulate an iPhone 15 Pro Max:\n\n````sh\nxcrun simctl boot \"iPhone 15 Pro Max\"\n````\n\nOnce the simulator is booted, we can run `dx serve --platform ios`.\n\n![DogApp](/assets/06_docs/dog-app-ios.mp4)\n\nFantastic - our app works seamlessly with no changes.\n\n### Testing on Android\n\nSetting up your environment for Android development takes time, so make sure to read the [mobile tooling guide](../guides/mobile/index.md).\n\n* Install the Android NDK and SDK\n* Set JAVA_HOME, ANDROID_HOME, NDK_HOME, and fix PATH issues to use the `emulator` tool\n* Install and set up an Android emulator\n* Install the Android rustup targets (`aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android`)\n\nLet's start an emulator. We can use the `emulator` command which should be in your PATH if setup properly. We're going to use our `Pixel_6_API_34` emulator, but you can use any device you've configured.\n\n````sh\nemulator -avd Pixel_6_API_34 -netdelay none -netspeed full\n````\n\nIf we try to `dx serve --platform android`, we'll find that our app fails to build for Android. This is not good!\n\n````text\n12:45:39 [cargo] Could not find directory of OpenSSL installation, and this `-sys` crate cannot\n12:45:39 [cargo] proceed without this knowledge. If OpenSSL is installed and this crate had\n12:45:39 [cargo] trouble finding it, you can set the `OPENSSL_DIR` environment variable for the\n12:45:39 [cargo] compilation process.\n12:45:39 [cargo] Make sure you also have the development packages of openssl installed.\n12:45:39 [cargo] For example, `libssl-dev` on Ubuntu or `openssl-devel` on Fedora.\n12:45:39 [cargo] If you're in a situation where you think the directory *should* be found\n12:45:39 [cargo] automatically, please open a bug at https://github.com/sfackler/rust-openssl\n12:45:39 [cargo] and include information about your system as well as this message.\n12:45:39 [cargo] $HOST = aarch64-apple-darwin\n12:45:39 [cargo] $TARGET = aarch64-linux-android\n12:45:39 [cargo] openssl-sys = 0.9.104\n````\n\nCurrently, `rust-openssl` does not cross-compile properly for Android targets. To fix this, we need to add the `openssl` crate to our Cargo.toml and then enable its \"vendored\" feature. This will build OpenSSL from source instead of trying and failing to read it from the Android NDK.\n\nWe're only going to enable the vendored feature when targeting Android.\n\n````toml\n[target.'cfg(target_os = \"android\")'.dependencies]\nopenssl = { version = \"0.10\", features = [\"vendored\"] }\n````\n\nIn the future, Dioxus might add OpenSSL's vendored feature implicitly to make this error go away. We're covering it here since it's important to understand that not every Rust dependency works out-of-the-box for iOS and Android. Unfortunately, the Rust ecosystem for mobile is still quite young and you'll need to know how to solve problems like these.\n\nLet's try again!\n\n````\ndx serve --platform android\n````\n\n![Android DogApp](/assets/06_docs/android-dogapp.mp4)\n\n## Testing on Desktop\n\nHotDog also works on macOS, Windows, and Linux! We can use `dx serve --platform desktop` to serve our app as a desktop app.\n\n![HotDogDesktop](/assets/06_docs/hotdog-desktop.png)\n\n## Bundling for the web\n\nAfter we're done making changes to our server and client apps, we can build bundles that are ready to distribute.\n\nWe're going to follow the same pattern as `dx serve` but with `dx bundle`. To start, let's build the web version of our app.\n\n````sh\ndx bundle --platform web\n````\n\nWe should receive a series of INFO traces from the CLI as it builds, and then finally a path to the `public` folder it generates. Let's `cd` into its public directory and then check out its parent directory (cd ..) (the \"web\" folder).\n\n````sh\n❯ tree -L 3 --gitignore\n.\n├── public\n│ ├── assets\n│ │ ├── favicon.ico\n│ │ ├── header.svg\n│ │ ├── main-14aa55e73f669f3e.css\n│ │ ├── main.css\n│ │ └── screenshot.png\n│ ├── index.html\n│ └── wasm\n│ ├── hot_dog.js\n│ ├── hot_dog.js.br\n│ ├── hot_dog_bg.wasm\n│ ├── hot_dog_bg.wasm.br\n│ └── snippets\n└── server\n````\n\n`dx` built a `public` folder containing our assets, index.html, and various JavaScript snippets. Alongside our public folder is a `server` binary. When we deploy our web assets, we'll also want to deploy the server since it provides our server functions.\n\nWe can manually run the server simply by executing it. If you're using a default `dioxus::launch` setup, then the server will read the `IP` and `PORT` environment variables to serve.\n\n > \n > 📣 If you intend to serve from within a container (e.g., Docker), then you need to override the default `127.0.0.1` address with `IP=0.0.0.0` to listen for external connections.\n\n![Serving the server](/assets/06_docs/serving_server.png)\n\n## Bundling for Desktop and Mobile\n\nTo bundle desktop and mobile apps for deployment, we'll again use `dx bundle`. As of today, `dx bundle` only builds desktop apps for the native platform and architecture. Unfortunately, you can't build macOS apps from Windows, Linux apps from Mac, etc. We recommend using a Continuous Integration Matrix (like [Github Actions](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow)) to perform a \"cross-build\" of your app in multiple different containers.\n\nWhen bundling installable apps, there are many distribution formats to choose from. We can specify these formats using the `--package-types` flag on `dx bundle`. Dioxus supports packaging a broad number of package types:\n\n* macOS: `.app`, `.dmg`\n* Linux: `.appimage`, `.rpm`, `.deb`\n* Windows: `.msi`, `.exe`\n* iOS: `.app`\n* Android: `.apk`\n\nYou can specify package types like so:\n\n````sh\ndx bundle --platform desktop \\\n --package-types \"macos\" \\\n --package-types \"dmg\"\n````\n\nNote that not all package-types are compatible with each platform - eg. only `.exe` can be built when specifying `--platform desktop`.\n\nWe should see the outputs in our terminal:\n\n````sh\n18.252s INFO Bundled app successfully!\n18.252s INFO App produced 2 outputs:\n18.252s INFO app - [/Users/jonkelley/Development/Tinkering/06-demos/hot_dog/target/dx/hot_dog/bundle/macos/bundle/macos/HotDog.app]\n18.252s INFO dmg - [/Users/jonkelley/Development/Tinkering/06-demos/hot_dog/target/dx/hot_dog/bundle/macos/bundle/dmg/HotDog_0.1.0_aarch64.dmg]\n````\n\nGenerally, you can distribute desktop apps without needing an app store. However, some platforms like macOS might require you to sign and notarize your application to be considered \"safe\" for your users to open.\n\nWhen distributing mobile apps, you *are required* to sign and notarize your apps. Currently, Dioxus doesn't provide built-in utilities for this, so you'll need to figure out signing by reading 3rd-party documentation.\n\nTauri provides documentation on the signing process:\n\n* [macOS](https://tauri.app/distribute/sign/macos/)\n* [iOS](https://tauri.app/distribute/sign/iOS/)\n* [Android](https://tauri.app/distribute/sign/android/)\n* [Windows](https://tauri.app/distribute/sign/Windows/)\n* [Linux](https://tauri.app/distribute/sign/Linux/)\n\n## Customizing your Bundle\n\nBefore you ship your app, you might want to configure how your app icon looks, what entitlements it has, and other details. Our `dx bundle` tool can help you configure your bundles in a variety of ways.\n\nTo configure our bundle, we'll use our `Dioxus.toml` and modify the bundle section.\n\n````toml\n[application]\nname = \"docsite\"\n\n[bundle]\nidentifier = \"com.dioxuslabs\"\npublisher = \"DioxusLabs\"\nicon = [\"assets/icon.png\"]\n````\n\nFor a full list of options, see the [reference page on the `bundle` section](../cookbook/bundling.md).\n\n## Automating dx bundle with JSON mode\n\nAlso added in Dioxus 0.6 is a JSON output mode for `dx`. This makes it possible to parse the output of the CLI using tools like [jq](https://jqlang.github.io/jq/) which provide stdin/stdout support for JSON parsing.\n\nThis mode is not particular friendly to humans, but does contain more information than the standard trace output.\n\n````sh\n{\"timestamp\":\" 9.927s\",\"level\":\"INFO\",\"message\":\"Bundled app successfully!\",\"target\":\"dx::cli::bundle\"}\n{\"timestamp\":\" 9.927s\",\"level\":\"INFO\",\"message\":\"App produced 2 outputs:\",\"target\":\"dx::cli::bundle\"}\n{\"timestamp\":\" 9.927s\",\"level\":\"DEBUG\",\"message\":\"Bundling produced bundles: [\\n Bundle {\\n package_type: MacOsBundle,\\n bundle_paths: [\\n \\\"/Users/jonkelley/Development/Tinkering/06-demos/hot_dog/target/dx/hot_dog/bundle/macos/bundle/macos/HotDog.app\\\",\\n ],\\n },\\n Bundle {\\n package_type: Dmg,\\n bundle_paths: [\\n \\\"/Users/jonkelley/Development/Tinkering/06-demos/hot_dog/target/dx/hot_dog/bundle/macos/bundle/dmg/HotDog_0.1.0_aarch64.dmg\\\",\\n ],\\n },\\n]\",\"target\":\"dx::cli::bundle\"}\n{\"timestamp\":\" 9.927s\",\"level\":\"INFO\",\"message\":\"app - [/Users/jonkelley/Development/Tinkering/06-demos/hot_dog/target/dx/hot_dog/bundle/macos/bundle/macos/HotDog.app]\",\"target\":\"dx::cli::bundle\"}\n{\"timestamp\":\" 9.927s\",\"level\":\"INFO\",\"message\":\"dmg - [/Users/jonkelley/Development/Tinkering/06-demos/hot_dog/target/dx/hot_dog/bundle/macos/bundle/dmg/HotDog_0.1.0_aarch64.dmg]\",\"target\":\"dx::cli::bundle\"}\n{\"timestamp\":\" 9.927s\",\"level\":\"DEBUG\",\"json\":\"{\\\"BundleOutput\\\":{\\\"bundles\\\":[\\\"/Users/jonkelley/Development/Tinkering/06-demos/hot_dog/target/dx/hot_dog/bundle/macos/bundle/macos/HotDog.app\\\",\\\"/Users/jonkelley/Development/Tinkering/06-demos/hot_dog/target/dx/hot_dog/bundle/macos/bundle/dmg/HotDog_0.1.0_aarch64.dmg\\\"]}}\",\"target\":\"dx\"}\n````\n\nJSON mode works with all `dx` commands. However, it is most useful with `dx build` and `dx bundle`. The CLI always guarantees that the last emitted line is the result of the command. To collect the list of bundles from the `dx bundle` command, we can use `tail -1` and simple jq.\n\n````sh\ndx bundle --platform desktop \\\n --json-output \\\n --verbose \\\n | tail -1 \\\n | jq -r '.json | fromjson | .BundleOutput.bundles []'\n````\n\nThis returns the list of bundles:\n\n````\n/Users/jonkelley/Development/Tinkering/06-demos/hot_dog/target/dx/hot_dog/bundle/macos/bundle/macos/HotDog.app\n/Users/jonkelley/Development/Tinkering/06-demos/hot_dog/target/dx/hot_dog/bundle/macos/bundle/dmg/HotDog_0.1.0_aarch64.dmg\n````" + } + 3usize => { + "# Setting up Tooling\n\nBefore we get started, make sure you've followed the [Getting Started](../getting_started/index.md) page on installing the required dependencies.\n\nWe will be primarily developing *HotDog* as web application, but we still recommend setting up the relevant tooling for desktop and mobile development as well.\n\n## Checklist\n\nWe covered the setup instructions in [Getting Started](../getting_started/index.md), but first you should verify everything is set up properly:\n\n* Rust is installed\n* You have a code editor installed\n* The wasm32-unknown-unknown Rust target is installed\n* The `dioxus-cli` is installed and up-to-date\n* System-specific dependencies are installed\n\n## Verify your setup\n\nBefore proceeding, make sure you have the `dioxus-cli` installed and up-to-date.\n\nVerify the returned version matches this guide (eg 0.6) by running:\n\n````sh\ndx --version\n````\n\n## All the Commands\n\nYou can also run `dx help` which will give you a list of useful commands and some information on how to use `dx`.\n\n````sh\nBuild, Bundle & Ship Dioxus Apps\n\nUsage: dx [OPTIONS] \n\nCommands:\n build Build the Dioxus project and all of its assets\n translate Translate a source file into Dioxus code\n serve Build, watch & serve the Dioxus project and all of its assets\n new Create a new project for Dioxus\n init Init a new project for Dioxus in the current directory (by default). Will attempt to keep your project in a good state\n clean Clean output artifacts\n bundle Bundle the Dioxus app into a shippable object\n fmt Automatically format RSX\n check Check the project for any issues\n run Run the project without any hotreloading\n config Dioxus config file controls\n help Print this message or the help of the given subcommand(s)\n\nOptions:\n --verbose Use verbose output [default: false]\n --trace Use trace output [default: false]\n --json-output Output logs in JSON format\n -h, --help Print help\n -V, --version Print version\n````\n\nIf `dx` is installed properly, then you're ready to proceed!" + } + 46usize => { + "# Server-Side Rendering\n\nFor lower-level control over the rendering process, you can use the `dioxus-ssr` crate directly. This can be useful when integrating with a web framework that `dioxus-fullstack` does not support, or pre-rendering pages.\n\n## Setup\n\nFor this guide, we're going to show how to use Dioxus SSR with [Axum](https://docs.rs/axum/latest/axum/).\n\nMake sure you have Rust and Cargo installed, and then create a new project:\n\n````shell\ncargo new --bin demo\ncd demo\n````\n\nAdd Dioxus and the ssr renderer as dependencies:\n\n````shell\ncargo add dioxus\ncargo add dioxus-ssr\n````\n\nNext, add all the Axum dependencies. This will be different if you're using a different Web Framework\n\n````\ncargo add tokio --features full\ncargo add axum\n````\n\nYour dependencies should look roughly like this:\n\n````toml\n[dependencies]\naxum = \"0.7\"\ndioxus = { version = \"*\" }\ndioxus-ssr = { version = \"*\" }\ntokio = { version = \"1.15.0\", features = [\"full\"] }\n````\n\nNow, set up your Axum app to respond on an endpoint.\n\n````rust@ssr.rs\nuse axum::{response::Html, routing::get, Router};\nuse dioxus::prelude::*;\n\n#[tokio::main]\nasync fn main() {\n let listener = tokio::net::TcpListener::bind(\"127.0.0.1:3000\")\n .await\n .unwrap();\n\n println!(\"listening on http://127.0.0.1:3000\");\n\n axum::serve(\n listener,\n Router::new()\n .route(\"/\", get(app_endpoint))\n .into_make_service(),\n )\n .await\n .unwrap();\n}\n````\n\nAnd then add our endpoint. We can either render `rsx!` directly:\n\n````rust@ssr.rs\nasync fn app_endpoint() -> Html {\n // render the rsx! macro to HTML\n Html(dioxus_ssr::render_element(rsx! { div { \"hello world!\" } }))\n}\n````\n\nOr we can render VirtualDoms.\n\n````rust@ssr.rs\nasync fn app_endpoint() -> Html {\n // create a component that renders a div with the text \"hello world\"\n fn app() -> Element {\n rsx! { div { \"hello world\" } }\n }\n // create a VirtualDom with the app component\n let mut app = VirtualDom::new(app);\n // rebuild the VirtualDom before rendering\n app.rebuild_in_place();\n\n // render the VirtualDom to HTML\n Html(dioxus_ssr::render(&app))\n}\n````\n\nFinally, you can run it using `cargo run` rather than `dx serve`.\n\n## Multithreaded Support\n\nThe Dioxus VirtualDom, sadly, is not currently `Send`. Internally, we use quite a bit of interior mutability which is not thread-safe.\nWhen 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 spawn a VirtualDom on its own thread and communicate with it via channels or create a pool of VirtualDoms.\nYou might notice that you cannot hold the VirtualDom across an await point. Because Dioxus is currently not ThreadSafe, it *must* remain on the thread it started. We are working on loosening this requirement." + } + 47usize => { + "# Fullstack development\n\nDioxus Fullstack contains helpers for:\n\n* Incremental, static, and server side rendering\n* Hydrating your application on the Client\n* Communicating between a server and a client\n\nThis guide will teach you everything you need to know about how to use the utilities in Dioxus fullstack to create amazing fullstack applications.\n\n > \n > In addition to this guide, you can find more examples of full-stack apps and information about how to integrate with other frameworks and desktop renderers in the [examples directory](https://github.com/DioxusLabs/dioxus/tree/main/examples)." + } + 60usize => { + "# Logging\n\nDioxus has a wide range of supported platforms, each with their own logging requirements. We'll discuss the different options available for your projects.\n\n## Dioxus Logger\n\nDioxus provides a first-party logger as part of `launch`. This sets up a tracing subscriber that cleanly integrates with the Dioxus CLI and platforms like Web and Mobile. In development mode, the `Debug` tracing level is set, and in release only the `Info` level is set.\n\n````rust\nuse dioxus::prelude::*;\n\nfn main() {\n dioxus::launch(|| {\n // Will only log in \"dev\" mode\n tracing::debug!(\"Rendering app!\");\n\n // Will log in dev and release\n tracing::info!(\"Rendering app!\");\n\n rsx! {}\n })\n}\n````\n\nTo override the default or initialize the logger before `launch`, you can use the `init` function yourself:\n\nTo use Dioxus Logger, call the `init()` function:\n\n````rs\nuse tracing::Level;\n\nfn main() {\n // Init logger\n dioxus_logger::init(Level::INFO).expect(\"failed to init logger\");\n\n // Dioxus launch code\n dioxus::launch(|| rsx! {})\n}\n````\n\n## The Tracing Crate\n\nThe [Tracing](https://crates.io/crates/tracing) crate is the logging interface that the dioxus-logger uses. It is not required to use the Tracing crate, but you will not receive logs from the Dioxus library.\n\nThe Tracing crate provides a variety of simple `println`-like macros with varying levels of severity.\nThe available macros are as follows with the highest severity on the bottom:\n\n````rs\nfn main() {\n tracing::trace!(\"trace\");\n tracing::debug!(\"debug\");\n tracing::info!(\"info\");\n tracing::warn!(\"warn\");\n tracing::error!(\"error\");\n}\n````\n\nAll the loggers provided on this page are, besides configuration and initialization, interfaced using these macros. Often you will also utilize the Tracing crate's `Level` enum. This enum usually represents the maximum log severity you want your application to emit and can be loaded from a variety of sources such as configuration file, environment variable, and more.\n\nFor more information, visit the Tracing crate's [docs](https://docs.rs/tracing/latest/tracing/).\n\n## Platform Intricacies\n\nOn web, Dioxus Logger will use [tracing-wasm](https://crates.io/crates/tracing-wasm). On Desktop and server-based targets, Dioxus Logger will use [tracing-subscriber](https://crates.io/crates/tracing-subscriber)'s `FmtSubscriber`.\n\n## Viewing Logs\n\nAndroid logs are sent to logcat. To use logcat through the Android debugger, run:\n\n````cmd\nadb -d logcat\n````\n\nYour Android device will need developer options/usb debugging enabled.\n\nFor more information, visit android_logger's [docs](https://docs.rs/android_logger/latest/android_logger/).\n\niOS logs are sent to oslog.\n\nFor more information, visit [oslog](https://crates.io/crates/oslog).\n\n#### Final Notes\n\nDioxus Logger is the preferred logger to use with Dioxus if it suites your needs. There are more features to come. If there are any feature suggestions or issues with Dioxus Logger, feel free to reach out on the [Dioxus Discord Server](https://discord.gg/XgGxMSkvUM)!\n\nFor more information, visit Dioxus Logger's [docs](https://docs.rs/dioxus-logger/latest/dioxus_logger/)." + } + 86usize => { + "# Overall Goals\n\nThis document outlines some of the overall goals for Dioxus. These goals are not set in stone, but they represent general guidelines for the project.\n\nThe goal of Dioxus is to make it easy to build **cross-platform applications that scale**.\n\n## Cross-Platform\n\nDioxus is designed to be cross-platform by default. This means that it should be easy to build applications that run on the web, desktop, and mobile. However, Dioxus should also be flexible enough to allow users to opt into platform-specific features when needed. The `use_eval` is one example of this. By default, Dioxus does not assume that the platform supports JavaScript, but it does provide a hook that allows users to opt into JavaScript when needed.\n\n## Performance\n\nAs Dioxus applications grow, they should remain relatively performant without the need for manual optimizations. There will be cases where manual optimizations are needed, but Dioxus should try to make these cases as rare as possible.\n\nOne of the benefits of the core architecture of Dioxus is that it delivers reasonable performance even when components are rerendered often. It is based on a Virtual Dom which performs diffing which should prevent unnecessary re-renders even when large parts of the component tree are rerun. On top of this, Dioxus groups static parts of the RSX tree together to skip diffing them entirely.\n\n## Type Safety\n\nAs teams grow, the Type safety of Rust is a huge advantage. Dioxus should leverage this advantage to make it easy to build applications with large teams.\n\nTo take full advantage of Rust's type system, Dioxus should try to avoid exposing public `Any` types and string-ly typed APIs where possible.\n\n## Developer Experience\n\nDioxus should be easy to learn and ergonomic to use.\n\n* The API of Dioxus attempts to remain close to React's API where possible. This makes it easier for people to learn Dioxus if they already know React\n\n* We can avoid the tradeoff between simplicity and flexibility by providing multiple layers of API: One for the very common use case, one for low-level control\n \n * Hooks: the hooks crate has the most common use cases, but `use_hook` provides a way to access the underlying persistent value if needed.\n * The builder pattern in platform Configs: The builder pattern is used to default to the most common use case, but users can change the defaults if needed.\n* Documentation:\n \n * All public APIs should have rust documentation\n * Examples should be provided for all public features. These examples both serve as documentation and testing. They are checked by CI to ensure that they continue to compile\n * The most common workflows should be documented in the guide" + } + 41usize => { + "# Assets\n\nAssets are files that are included in the final build of the application. They can be images, fonts, stylesheets, or any other file that is not a source file. Dioxus includes first class support for assets, and provides a simple way to include them in your application and automatically optimize them for production.\n\nAssets in dioxus are also compatible with libraries! If you are building a library, you can include assets in your library and they will be automatically included in the final build of any application that uses your library.\n\n## Including images\n\nTo include an asset in your application, you can simply wrap the path to the asset in the `asset!` macro. For example, to include an image in your application, you can use the following code:\n\n````rust@assets.rs\nuse dioxus::prelude::*;\n\nfn App() -> Element {\n // You can link to assets that are relative to the package root or even link to an asset from a url\n // These assets will automatically be picked up by the dioxus cli, optimized, and bundled with your final applications\n const ASSET: Asset = asset!(\"/assets/static/ferrous_wave.png\");\n\n rsx! {\n img { src: \"{ASSET}\" }\n }\n}\n````\n\nYou can also optimize, resize, and preload images using the `asset!` macro. Choosing an optimized file type (like Avif) and a reasonable quality setting can significantly reduce the size of your images which helps your application load faster. For example, you can use the following code to include an optimized image in your application:\n\n````rust@assets.rs\npub const ENUM_ROUTER_IMG: Asset = asset!(\n \"/assets/static/enum_router.png\",\n // You can pass a second argument to the asset macro to set up options for the asset\n ImageAssetOptions::new()\n // You can set the image size in pixels at compile time to send the smallest possible image to the client\n .with_size(ImageSize::Manual {\n width: 52,\n height: 52\n })\n // You can also convert the image to a web friendly format at compile time. This can make your images significantly smaller\n .with_format(ImageFormat::Avif)\n);\n\nfn EnumRouter() -> Element {\n rsx! {\n img { src: \"{ENUM_ROUTER_IMG}\" }\n }\n}\n````\n\n## Including arbitrary files\n\nIn dioxus desktop, you may want to include a file with data for your application. If you don't set any options for your asset and the file extension is not recognized, the asset will be copied without any changes. For example, you can use the following code to include a binary file in your application:\n\n````rust@assets.rs\n// You can also collect arbitrary files. Relative paths are resolved relative to the package root\nconst PATH_TO_BUNDLED_CARGO_TOML: Asset = asset!(\"/Cargo.toml\");\n````\n\nThese files will be automatically included in the final build of your application, and you can use them in your application as you would any other file.\n\n## Including stylesheets\n\nYou can include stylesheets in your application using the `asset!` macro. Stylesheets will automatically be minified as they are bundled to speed up load times. For example, you can use the following code to include a stylesheet in your application:\n\n````rust@assets.rs\n// You can also bundle stylesheets with your application\n// Any files that end with .css will be minified and bundled with your application even if you don't explicitly include them in your \nconst _: Asset = asset!(\"/assets/tailwind.css\");\n````\n\n > \n > The [tailwind guide](../cookbook/tailwind.md) has more information on how to use tailwind with dioxus.\n\n#### SCSS Support\n\nSCSS is also supported through the `asset!` macro. Include it the same way as a regular CSS file.\n\n## Conclusion\n\nDioxus provides first class support for assets, and makes it easy to include them in your application. You can include images, arbitrary files, and stylesheets in your application, and dioxus will automatically optimize them for production. This makes it easy to include assets in your application and ensure that they are optimized for production.\n\nYou can read more about assets and all the options available to optimize your assets in the [manganis documentation](https://docs.rs/manganis/0.6.0/manganis)." + } + 63usize => { + "# Working with External State\n\nThis guide will help you integrate your Dioxus application with some external state like a different thread or a websocket connection.\n\n## Working with non-reactive State\n\n[Coroutines](../../../reference/use_coroutine.md) are great tool for dealing with non-reactive (state you don't render directly) state within your application.\n\nYou can store your state inside the coroutine async block and communicate with the coroutine with messages from any child components.\n\n````rust@use_coroutine.rs\n// import futures::StreamExt to use the next() method\nuse futures::StreamExt;\nlet mut response_state = use_signal(|| None);\nlet tx = use_coroutine(move |mut rx| async move {\n // Define your state before the loop\n let mut state = reqwest::Client::new();\n let mut cache: HashMap = HashMap::new();\n loop {\n // Loop and wait for the next message\n if let Some(request) = rx.next().await {\n // Resolve the message\n let response = if let Some(response) = cache.get(&request) {\n response.clone()\n } else {\n let response = state\n .get(&request)\n .send()\n .await\n .unwrap()\n .text()\n .await\n .unwrap();\n cache.insert(request, response.clone());\n response\n };\n response_state.set(Some(response));\n } else {\n break;\n }\n }\n});\n// Send a message to the coroutine\ntx.send(\"https://example.com\".to_string());\n// Get the current state of the coroutine\nlet response = response_state.read();\n````\n\n## Making Reactive State External\n\nIf you have some reactive state (state that is rendered), that you want to modify from another thread, you can use a signal that is sync. Signals take an optional second generic value with information about syncness. Sync signals have a slightly higher overhead than thread local signals, but they can be used in a multithreaded environment.\n\n````rust@sync_signal.rs\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app);\n}\n\nfn app() -> Element {\n let mut signal = use_signal_sync(|| 0);\n\n use_hook(|| {\n std::thread::spawn(move || loop {\n std::thread::sleep(std::time::Duration::from_secs(1));\n // You can easily update the signal from a different thread\n signal += 1;\n });\n });\n\n rsx! {\n button { onclick: move |_| signal += 1, \"Increase\" }\n \"{signal}\"\n }\n}\n\n````" + } + 64usize => { + "# Custom Hooks\n\nHooks are a great way to encapsulate business logic. If none of the existing hooks work for your problem, you can write your own.\n\nWhen writing your hook, you can make a function that starts with `use_` and takes any arguments you need. You can then use the `use_hook` method to create a hook that will be called the first time the component is rendered.\n\n## Composing Hooks\n\nTo avoid repetition, you can encapsulate business logic based on existing hooks to create a new hook.\n\nFor example, if many components need to access an `AppSettings` struct, you can create a \"shortcut\" hook:\n\n````rust@hooks_composed.rs\nfn use_settings() -> Signal {\n consume_context()\n}\n````\n\nOr if you want to wrap a hook that persists reloads with the storage API, you can build on top of the use_signal hook to work with mutable state:\n\n````rust@hooks_composed.rs\nuse gloo_storage::{LocalStorage, Storage};\nuse serde::{de::DeserializeOwned, Serialize};\n\n/// A persistent storage hook that can be used to store data across application reloads.\n#[allow(clippy::needless_return)]\npub fn use_persistent(\n // A unique key for the storage entry\n key: impl ToString,\n // A function that returns the initial value if the storage entry is empty\n init: impl FnOnce() -> T,\n) -> UsePersistent {\n // Use the use_signal hook to create a mutable state for the storage entry\n let state = use_signal(move || {\n // This closure will run when the hook is created\n let key = key.to_string();\n let value = LocalStorage::get(key.as_str()).ok().unwrap_or_else(init);\n StorageEntry { key, value }\n });\n\n // Wrap the state in a new struct with a custom API\n UsePersistent { inner: state }\n}\n\nstruct StorageEntry {\n key: String,\n value: T,\n}\n\n/// Storage that persists across application reloads\npub struct UsePersistent {\n inner: Signal>,\n}\n\nimpl Clone for UsePersistent {\n fn clone(&self) -> Self {\n *self\n }\n}\n\nimpl Copy for UsePersistent {}\n\nimpl UsePersistent {\n /// Returns a reference to the value\n pub fn get(&self) -> T {\n self.inner.read().value.clone()\n }\n\n /// Sets the value\n pub fn set(&mut self, value: T) {\n let mut inner = self.inner.write();\n // Write the new value to local storage\n LocalStorage::set(inner.key.as_str(), &value);\n inner.value = value;\n }\n}\n````\n\n## Custom Hook Logic\n\nYou can use [`use_hook`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_hook.html) to build your own hooks. In fact, this is what all the standard hooks are built on!\n\n`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.\n\n > \n > Note: You can use the `use_on_destroy` hook to clean up any resources the hook uses when the component is destroyed.\n\nInside the initialization closure, you will typically make calls to other `cx` methods. For example:\n\n* The `use_signal` hook tracks state in the hook value, and uses [`ReactiveContext`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.ReactiveContext.html) to make Dioxus re-render any component that has observed it whenever the signal's value changes.\n\nHere is a simplified implementation of the `use_signal` hook:\n\n````rust@hooks_custom_logic.rs\nuse std::cell::RefCell;\nuse std::collections::HashSet;\nuse std::rc::Rc;\nuse std::sync::{Arc, Mutex};\n\nstruct Signal {\n value: Rc>,\n subscribers: Arc>>,\n}\n\nimpl Clone for Signal {\n fn clone(&self) -> Self {\n Self {\n value: self.value.clone(),\n subscribers: self.subscribers.clone(),\n }\n }\n}\n\nfn my_use_signal(init: impl FnOnce() -> T) -> Signal {\n use_hook(|| {\n // A set of subscribers to notify about changes to this signals value\n let subscribers = Default::default();\n // Create the initial state\n let value = Rc::new(RefCell::new(init()));\n\n Signal { value, subscribers }\n })\n}\n\nimpl Signal {\n fn get(&self) -> T {\n // Subscribe the context observing the signal (if any) to updates of its value.\n if let Some(reactive_context) = ReactiveContext::current() {\n reactive_context.subscribe(self.subscribers.clone());\n }\n\n self.value.borrow().clone()\n }\n\n fn set(&self, value: T) {\n // Update the state\n *self.value.borrow_mut() = value;\n // Trigger a re-render of the components that observed the signal's previous value\n let mut subscribers = std::mem::take(&mut *self.subscribers.lock().unwrap());\n subscribers.retain(|reactive_context| reactive_context.mark_dirty());\n // Extend the subscribers list instead of overwriting it in case a subscriber is added while reactive contexts are marked dirty\n self.subscribers.lock().unwrap().extend(subscribers);\n }\n}\n````\n\n* The `use_context` hook calls [`consume_context`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.consume_context.html) (which would be expensive to call on every render) to get some context from the component\n\nHere is an implementation of the `use_context` and `use_context_provider` hooks:\n\n````rust@hooks_custom_logic.rs\npub fn use_context() -> T {\n use_hook(|| consume_context())\n}\n\npub fn use_context_provider(f: impl FnOnce() -> T) -> T {\n use_hook(|| {\n let val = f();\n // Provide the context state to the component\n provide_context(val.clone());\n val\n })\n}\n\n````" + } + 34usize => { + "# Nested Routes\n\nWhen developing bigger applications we often want to nest routes within each\nother. As an example, we might want to organize a settings menu using this\npattern:\n\n````plain\n└ Settings\n ├ General Settings (displayed when opening the settings)\n ├ Change Password\n └ Privacy Settings\n````\n\nWe might want to map this structure to these paths and components:\n\n````plain\n/settings\t\t -> Settings { GeneralSettings }\n/settings/password -> Settings { PWSettings }\n/settings/privacy -> Settings { PrivacySettings }\n````\n\nNested routes allow us to do this without repeating /settings in every route.\n\n## Nesting\n\nTo nest routes, we use the `#[nest(\"path\")]` and `#[end_nest]` attributes.\n\nThe path in nest must not:\n\n1. Contain a [Catch All Segment](index.md#catch-all-segments)\n1. Contain a [Query Segment](index.md#query-segments)\n\nIf you define a dynamic segment in a nest, it will be available to all child routes and layouts.\n\nTo finish a nest, we use the `#[end_nest]` attribute or the end of the enum.\n\n````rust@nest.rs\n#[derive(Routable, Clone)]\n// Skipping formatting allows you to indent nests\n#[rustfmt::skip]\nenum Route {\n // Start the /blog nest\n #[nest(\"/blog\")]\n // You can nest as many times as you want\n #[nest(\"/:id\")]\n #[route(\"/post\")]\n PostId {\n // You must include parent dynamic segments in child variants\n id: usize,\n },\n // End nests manually with #[end_nest]\n #[end_nest]\n #[route(\"/:id\")]\n // The absolute route of BlogPost is /blog/:name\n BlogPost {\n id: usize,\n },\n // Or nests are ended automatically at the end of the enum\n}\n\n#[component]\nfn BlogPost(id: usize) -> Element {\n todo!()\n}\n\n#[component]\nfn PostId(id: usize) -> Element {\n todo!()\n}\n````" + } + 48usize => { + "# Hydration\n\nIn dioxus fullstack, the server renders the initial HTML for improved loading times. This initial version of the page is what most web crawlers and search engines see. After the initial HTML is rendered, the client makes the page interactive and takes over rendering in a process called **hydration**. Most of the time, you shouldn't need to think about hydration, but there are a few things you need to keep in mind to avoid [hydration errors](#hydration-errors). To better understand hydration, let's walk through a simple example:\n\n````rust@hydration.rs\nfn Weather() -> Element {\n let mut weather = use_server_future(fetch_weather)?;\n\n rsx! {\n div {\n \"{weather:?}\"\n }\n button {\n onclick: move |_| weather.restart(),\n \"Refetch\"\n }\n }\n}\n````\n\n## Rendering the initial HTML\n\nWhen the server receives a request to render the `Weather` component, it renders the page to HTML and serializes some additional data the client needs to hydrate the page. It will follow these steps to render our component:\n\n1. Run the component\n1. Wait until all server futures are resolved\n1. Serialize any non-deterministic data (like the `weather` future) for the client\n1. Render the HTML\n\n[![](https://mermaid.ink/img/pako:eNpdkDFTwzAMhf-KT3M70HbKwELhGMqSdAIziFhNfI2lnGzDQa__HZfk4Iq1-D1_ejr5BK04ggoOg3y0PWoy-61lE_Nbpzj2Jt68WGhI30lN4x2ZmtiReu4svBZwPs4rtckLm13958ZVaa4zmzsJozBxumqK6ynb4-C_yMxTHnLKSvGa3FyCfiabx_3Tbn4sxsTElVkub0vgLNeT3FieChYQSAN6V1Y9XSALqadAFqpydahHC5bPhcOcpPnkFqqkmRagkrseqgMOsag8Oky09Vh-J_y6I_KzSPhH3TufRGfz_A3Ce3PT?type=png)](https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNpdkDFTwzAMhf-KT3M70HbKwELhGMqSdAIziFhNfI2lnGzDQa__HZfk4Iq1-D1_ejr5BK04ggoOg3y0PWoy-61lE_Nbpzj2Jt68WGhI30lN4x2ZmtiReu4svBZwPs4rtckLm13958ZVaa4zmzsJozBxumqK6ynb4-C_yMxTHnLKSvGa3FyCfiabx_3Tbn4sxsTElVkub0vgLNeT3FieChYQSAN6V1Y9XSALqadAFqpydahHC5bPhcOcpPnkFqqkmRagkrseqgMOsag8Oky09Vh-J_y6I_KzSPhH3TufRGfz_A3Ce3PT)\n\nOnce the server finishes rendering, it will send this structure to the client as HTML:\n\n[![](https://mermaid.ink/img/pako:eNqFUcFKAzEQ_ZUwh57agy22sAUFqaCgF1sQNCLTZLYbupss2VmLlv67s92luoo4uSRv3nvzhuzBBEuQQJqHnckwslottFdVvd5ELDNVjZ81LCk6zN0HWbVARg0vQpGyLpJhF7xaXbVIU_5MJCmxyV53hJxR7ATkbc96Irxb71i81c3q_u4_3ybKIOe5dW-DDc9P9GPzXJr7bl5yeeg3p51yXTOLa_Amd2b722QmvAdKI1XZH5mb3R57W7VVjb_dJ1_KY241Gl1IQjWQJB02bbGZ9u2BIRQUC3RWPmPfkDTIkII0JHK1GLcatD8ID2sOy3dvIOFY0xBiqDcZJCnmlbzq0iLTwqEELk5oif4phOIH69o6DrEDD5_uGqQ1?type=png)](https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNqFUcFKAzEQ_ZUwh57agy22sAUFqaCgF1sQNCLTZLYbupss2VmLlv67s92luoo4uSRv3nvzhuzBBEuQQJqHnckwslottFdVvd5ELDNVjZ81LCk6zN0HWbVARg0vQpGyLpJhF7xaXbVIU_5MJCmxyV53hJxR7ATkbc96Irxb71i81c3q_u4_3ybKIOe5dW-DDc9P9GPzXJr7bl5yeeg3p51yXTOLa_Amd2b722QmvAdKI1XZH5mb3R57W7VVjb_dJ1_KY241Gl1IQjWQJB02bbGZ9u2BIRQUC3RWPmPfkDTIkII0JHK1GLcatD8ID2sOy3dvIOFY0xBiqDcZJCnmlbzq0iLTwqEELk5oif4phOIH69o6DrEDD5_uGqQ1)\n\n## Hydrating on the client\n\nWhen the client receives the initial HTML, it hydrates the HTML by rerunning each component and linking each node that component renders to the node the server rendered. Rerunning each component lets the client re-construct some non-serializable state like event handlers and kick off any client side logic like `use_effect` and `use_future`.\n\nIt will follow these steps:\n\n1. Deserialize any non-deterministic data from the server (like the `weather` future)\n1. Run the component with the deserialized data. All server futures are immediately resolved with the deserialized data from the server.\n1. Hydrate the HTML sent from the server. This adds all event handlers and links the html nodes to the component so they can be moved or modified later\n\n[![](https://mermaid.ink/img/pako:eNpdkLFuAjEMhl_F8gxDgemGLlyrDnThmNp0SC-Gi7g4JydpRRHvXsOdkFpnif__s534jG10hBXu-_jddlYy7GrDkMrnQezQQXp4N7juPXGGxjuCl5MT63Nkgx8Kajgv1GYfGTbbUblGWmphTYnE297_EDQkXyTwXHIRSvfqG7tQdlsY1jEMkXXWX3ul9m1u1vm7183kErsRSkuYzx-1zZQuxnRleDw4w0ASrHf60_MVMpg7CmSw0quzcjRo-KKcLTk2J26xylJohhLLocNqb_ukWRmcvqH2VpcT7upg-S3G8I96crolmcTLL4RBdIg?type=png)](https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNpdkLFuAjEMhl_F8gxDgemGLlyrDnThmNp0SC-Gi7g4JydpRRHvXsOdkFpnif__s534jG10hBXu-_jddlYy7GrDkMrnQezQQXp4N7juPXGGxjuCl5MT63Nkgx8Kajgv1GYfGTbbUblGWmphTYnE297_EDQkXyTwXHIRSvfqG7tQdlsY1jEMkXXWX3ul9m1u1vm7183kErsRSkuYzx-1zZQuxnRleDw4w0ASrHf60_MVMpg7CmSw0quzcjRo-KKcLTk2J26xylJohhLLocNqb_ukWRmcvqH2VpcT7upg-S3G8I96crolmcTLL4RBdIg)\n\n## Hydration errors\n\nFor hydration to work, **the component must render exactly the same thing on the client and the server**. If it doesn't, you might see an error like this:\n\n````\nUncaught TypeError: Cannot set properties of undefined (setting 'textContent')\nat RawInterpreter.run (yourwasm-hash.js:1:12246)\n````\n\nOr this:\n\n````\nError deserializing data:\nSemantic(None, \"invalid type: floating point `1.2`, expected integer\")\nThis type was serialized on the server at src/main.rs:11:5 with the type name f64. The client failed to deserialize the type i32 at /path/to/server_future.rs\n````\n\n### Non-deterministic data with server cached\n\nYou must put any non-deterministic data in `use_server_future`, `use_server_cached` or `use_effect` to avoid hydration errors. For example, if you need to render a random number on your page, you can use `use_server_cached` to cache the random number on the server and then use it on the client:\n\n````rust@hydration.rs\n// ❌ The random number will be different on the client and the server\nlet random: u8 = use_hook(|| rand::random());\n// ✅ The same random number will be serialized on the server and deserialized on the client\nlet random: u8 = use_server_cached(|| rand::random());\n````\n\n### Async loading with server futures\n\nIf you need render some data from a server future, you need to use `use_server_future` to serialize the data instead of waiting for the (non-deterministic) amount of time `use_resource(...).suspend()?` takes:\n\n````rust@hydration.rs\n// ❌ The server function result may be finished on the server, but pending on the client\nlet random: u8 = use_resource(|| random_server_function()).suspend()?().unwrap_or_default();\n// ✅ Once the server function is resolved on the server, it will be sent to the client\nlet random: u8 = use_server_future(|| random_server_function())?()\n .unwrap()\n .unwrap_or_default();\n````\n\n### Client only data with effects\n\nIf you need to grab some data that is only available on the client, make sure you get it inside of a `use_effect` hook which runs after the component has been hydrated:\n\n````rust@hydration.rs\n// ❌ Using a different value client side before hydration will cause hydration issues\n// because the server rendered the html with another value\nlet mut storage = use_signal(|| {\n #[cfg(feature = \"server\")]\n return None;\n let window = web_sys::window().unwrap();\n let local_storage = window.local_storage().unwrap().unwrap();\n local_storage.set_item(\"count\", \"1\").unwrap();\n local_storage.get_item(\"count\").unwrap()\n});\n// ✅ Changing the value inside of an effect is fine because effects run after hydration\nlet mut storage = use_signal(|| None);\nuse_effect(move || {\n let window = web_sys::window().unwrap();\n let local_storage = window.local_storage().unwrap().unwrap();\n local_storage.set_item(\"count\", \"1\").unwrap();\n storage.set(local_storage.get_item(\"count\").unwrap());\n});\n````\n\n### Avoid side effects in server cached hooks\n\nThe dioxus fullstack specific hooks `use_server_cached` and `use_server_future` don't run the same on the server and the client. The server will always run the closure, but the client may not run the closure if the server serialized the result. Because of this, the code you run inside these hooks **cannot have side effects**. If it does, the side effects will not be serialized and it can cause a hydration mismatch error:\n\n````rust@hydration.rs\n// ❌ The state of the signal cannot be serialized on the server\nlet mut storage = use_signal(|| None);\nuse_server_future(move || async move {\n storage.set(Some(server_future().await));\n})?;\n// ✅ The value returned from use_server_future will be serialized on the server and hydrated on the client\nlet storage = use_server_future(|| async move { server_future().await })?;\n````" + } + 66usize => { + "# Testing\n\nWhen building application or libraries with Dioxus, you may want to include some tests to check the behavior of parts of your application. This guide will teach you how to test different parts of your Dioxus application.\n\n## Component Testing\n\nYou can use a combination of [pretty-assertions](https://docs.rs/pretty_assertions/latest/pretty_assertions/) and [dioxus-ssr](http://crates.io/crates/dioxus-ssr) to check that two snippets of rsx are equal:\n\n````rust@component_test.rs\nuse futures::FutureExt;\nuse std::{cell::RefCell, sync::Arc};\n\nuse dioxus::prelude::*;\n\n#[test]\nfn test() {\n assert_rsx_eq(\n rsx! {\n div { \"Hello world\" }\n div { \"Hello world\" }\n },\n rsx! {\n for _ in 0..2 {\n div { \"Hello world\" }\n }\n },\n )\n}\n\nfn assert_rsx_eq(first: Element, second: Element) {\n let first = dioxus_ssr::render_element(first);\n let second = dioxus_ssr::render_element(second);\n pretty_assertions::assert_str_eq!(first, second);\n}\n\n````\n\n## Hook Testing\n\nWhen creating libraries around Dioxus, it can be helpful to make tests for your [custom hooks](./state/custom_hooks/index.md).\n\nDioxus does not currently have a full hook testing library, but you can build a bespoke testing framework by manually driving the virtual dom.\n\n````rust@hook_test.rs\nuse futures::FutureExt;\nuse std::{cell::RefCell, rc::Rc, sync::Arc, thread::Scope};\n\nuse dioxus::{dioxus_core::NoOpMutations, prelude::*};\n\n#[test]\nfn test() {\n test_hook(\n || use_signal(|| 0),\n |mut value, mut proxy| match proxy.generation {\n 0 => {\n value.set(1);\n }\n 1 => {\n assert_eq!(*value.read(), 1);\n value.set(2);\n }\n 2 => {\n proxy.rerun();\n }\n 3 => {}\n _ => todo!(),\n },\n |proxy| assert_eq!(proxy.generation, 4),\n );\n}\n\nfn test_hook(\n initialize: impl FnMut() -> V + 'static,\n check: impl FnMut(V, MockProxy) + 'static,\n mut final_check: impl FnMut(MockProxy) + 'static,\n) {\n #[derive(Props)]\n struct MockAppComponent {\n hook: Rc>,\n check: Rc>,\n }\n\n impl PartialEq for MockAppComponent {\n fn eq(&self, _: &Self) -> bool {\n true\n }\n }\n\n impl Clone for MockAppComponent {\n fn clone(&self) -> Self {\n Self {\n hook: self.hook.clone(),\n check: self.check.clone(),\n }\n }\n }\n\n fn mock_app V, C: FnMut(V, MockProxy), V>(\n props: MockAppComponent,\n ) -> Element {\n let value = props.hook.borrow_mut()();\n\n props.check.borrow_mut()(value, MockProxy::new());\n\n rsx! { div {} }\n }\n\n let mut vdom = VirtualDom::new_with_props(\n mock_app,\n MockAppComponent {\n hook: Rc::new(RefCell::new(initialize)),\n check: Rc::new(RefCell::new(check)),\n },\n );\n\n vdom.rebuild_in_place();\n\n while vdom.wait_for_work().now_or_never().is_some() {\n vdom.render_immediate(&mut NoOpMutations);\n }\n\n vdom.in_runtime(|| {\n ScopeId::ROOT.in_runtime(|| {\n final_check(MockProxy::new());\n })\n })\n}\n\nstruct MockProxy {\n rerender: Arc,\n pub generation: usize,\n}\n\nimpl MockProxy {\n fn new() -> Self {\n let generation = generation();\n let rerender = schedule_update();\n\n Self {\n rerender,\n generation,\n }\n }\n\n pub fn rerun(&mut self) {\n (self.rerender)();\n }\n}\n\n````\n\n## End to End Testing\n\nYou can use [Playwright](https://playwright.dev/) to create end to end tests for your dioxus application.\n\nIn your `playwright.config.js`, you will need to run cargo run or dx serve instead of the default build command. Here is a snippet from the end to end web example:\n\n````js\n//...\nwebServer: [\n {\n cwd: path.join(process.cwd(), 'playwright-tests', 'web'),\n command: 'dx serve',\n port: 8080,\n timeout: 10 * 60 * 1000,\n reuseExistingServer: !process.env.CI,\n stdout: \"pipe\",\n },\n],\n````\n\n* [Web example](https://github.com/DioxusLabs/dioxus/tree/main/playwright-tests/web)\n* [Liveview example](https://github.com/DioxusLabs/dioxus/tree/main/playwright-tests/liveview)\n* [Fullstack example](https://github.com/DioxusLabs/dioxus/tree/main/playwright-tests/fullstack)" + } + 70usize => { + "# Reference\n\nThis portion of the Dioxus Docs contains specific details about features that provide more depth than what is necessary for most reading.\n\n* [RSX](./rsx.md)\n* [Components](./components.md)\n* [Props](./component_props.md)\n* [Event Handlers](./event_handlers.md)\n* [Hooks](./hooks.md)\n* [User Input](./user_input.md)\n* [Context](./context.md)\n* [Dynamic Rendering](./dynamic_rendering.md)\n* [Routing](./router.md)\n* [Resource](./use_resource.md)\n* [UseCoroutine](./use_coroutine.md)\n* [Spawn](./spawn.md)" + } + 81usize => { + "# Resource\n\n[`use_resource`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_resource.html) lets you run an async closure, and provides you with its result.\n\nFor example, we can make an API request (using [reqwest](https://docs.rs/reqwest/latest/reqwest/index.html)) inside `use_resource`:\n\n````rust@use_resource.rs\nlet mut future = use_resource(|| async move {\n reqwest::get(\"https://dog.ceo/api/breeds/image/random\")\n .await\n .unwrap()\n .json::()\n .await\n});\n````\n\nThe code inside `use_resource` will be submitted to the Dioxus scheduler once the component has rendered.\n\nWe can use `&*future.read_unchecked()` 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.\n\nWe can then render that result:\n\n````rust@use_resource.rs\nmatch &*future.read_unchecked() {\n Some(Ok(response)) => rsx! {\n button { onclick: move |_| future.restart(), \"Click to fetch another doggo\" }\n div {\n img {\n max_width: \"500px\",\n max_height: \"500px\",\n src: \"{response.image_url}\",\n }\n }\n },\n Some(Err(_)) => rsx! {\n div { \"Loading dogs failed\" }\n },\n None => rsx! {\n div { \"Loading dogs...\" }\n },\n}\n````\n\n````inject-dioxus\nDemoFrame {\n use_resource::App {}\n}\n````\n\n## Restarting the Future\n\nThe `Resource` handle provides a `restart` method. It can be used to execute the future again, producing a new value.\n\n## Dependencies\n\nOften, you will need to run the future again every time some value (e.g. a state) changes. Rather than calling `restart` manually, you can read a signal inside of the future. It will automatically re-run the future when any of the states you read inside the future change. Example:\n\n````rust, no_run@use_resource.rs\nlet future = use_resource(move || async move {\n reqwest::get(format!(\"https://dog.ceo/api/breed/{breed}/images/random\"))\n .await\n .unwrap()\n .json::()\n .await\n});\n\n// You can also add non-reactive state to the resource hook with the use_reactive method\nlet non_reactive_state = \"poodle\";\nuse_resource(use_reactive!(|(non_reactive_state,)| async move {\n reqwest::get(format!(\n \"https://dog.ceo/api/breed/{non_reactive_state}/images/random\"\n ))\n .await\n .unwrap()\n .json::()\n .await\n}));\n````" + } + 31usize => { + "# Full Code\n\n````rust@full_example.rs\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n// ANCHOR: router\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(NavBar)]\n #[route(\"/\")]\n Home {},\n #[nest(\"/blog\")]\n #[layout(Blog)]\n #[route(\"/\")]\n BlogList {},\n #[route(\"/post/:name\")]\n BlogPost { name: String },\n #[end_layout]\n #[end_nest]\n #[end_layout]\n #[nest(\"/myblog\")]\n #[redirect(\"/\", || Route::BlogList {})]\n #[redirect(\"/:name\", |name: String| Route::BlogPost { name })]\n #[end_nest]\n #[route(\"/:..route\")]\n PageNotFound {\n route: Vec,\n },\n}\n// ANCHOR_END: router\n\npub fn App() -> Element {\n rsx! { Router:: {} }\n}\n\n#[component]\nfn NavBar() -> Element {\n rsx! {\n nav {\n ul {\n li {\n Link { to: Route::Home {}, \"Home\" }\n }\n li {\n Link { to: Route::BlogList {}, \"Blog\" }\n }\n }\n }\n Outlet:: {}\n }\n}\n\n#[component]\nfn Home() -> Element {\n rsx! { h1 { \"Welcome to the Dioxus Blog!\" } }\n}\n\n#[component]\nfn Blog() -> Element {\n rsx! {\n h1 { \"Blog\" }\n Outlet:: {}\n }\n}\n\n#[component]\nfn BlogList() -> Element {\n rsx! {\n h2 { \"Choose a post\" }\n ul {\n li {\n Link {\n to: Route::BlogPost {\n name: \"Blog post 1\".into(),\n },\n \"Read the first blog post\"\n }\n }\n li {\n Link {\n to: Route::BlogPost {\n name: \"Blog post 2\".into(),\n },\n \"Read the second blog post\"\n }\n }\n }\n }\n}\n\n#[component]\nfn BlogPost(name: String) -> Element {\n rsx! { h2 { \"Blog Post: {name}\" } }\n}\n\n#[component]\nfn PageNotFound(route: Vec) -> Element {\n rsx! {\n h1 { \"Page not found\" }\n p { \"We are terribly sorry, but the page you requested doesn't exist.\" }\n pre { color: \"red\", \"log:\\nattemped to navigate to: {route:?}\" }\n }\n}\n\n````" + } + 82usize => { + "# Coroutines\n\nAnother tool in your async toolbox are coroutines. Coroutines are futures that can have values sent to them.\n\nLike regular futures, code in a coroutine will run until the next `await` point before yielding. This low-level control over asynchronous tasks is quite powerful, allowing for infinitely looping tasks like WebSocket polling, background timers, and other periodic actions.\n\n## use_coroutine\n\nThe `use_coroutine` hook allows you to create a coroutine. Most coroutines we write will be polling loops using await.\n\n````rust, no_run@use_coroutine_reference.rs\nuse futures_util::StreamExt;\n\nfn app() {\n let ws: Coroutine<()> = use_coroutine(|rx| async move {\n // Connect to some sort of service\n let mut conn = connect_to_ws_server().await;\n\n // Wait for data on the service\n while let Some(msg) = conn.next().await {\n // handle messages\n }\n });\n}\n````\n\nFor many services, a simple async loop will handle the majority of use cases.\n\n## Yielding Values\n\nTo yield values from a coroutine, simply bring in a `Signal` handle and set the value whenever your coroutine completes its work.\n\nThe future must be `'static` – so any values captured by the task cannot carry any references to `cx`, such as a `Signal`.\n\nYou can use [to_owned](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned) to create a clone of the hook handle which can be moved into the async closure.\n\n````rust, no_run@use_coroutine_reference.rs\nlet mut sync_status = use_signal(|| Status::Launching);\nlet sync_task = use_coroutine(move |rx: UnboundedReceiver| async move {\n loop {\n tokio::time::sleep(Duration::from_secs(1)).await;\n sync_status.set(Status::Working);\n }\n});\n````\n\nTo 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.\n\n````rust, no_run@use_coroutine_reference.rs\nlet sync_status = use_signal(|| Status::Launching);\nlet load_status = use_signal(|| Status::Launching);\nlet sync_task = use_coroutine(|rx: UnboundedReceiver| {\n async move {\n // ...\n }\n});\n````\n\n## Sending Values\n\nYou might've noticed the `use_coroutine` closure takes an argument called `rx`. What is that? Well, a common pattern in complex apps is to handle a bunch of async code at once. With libraries like Redux Toolkit, managing multiple promises at once can be challenging and a common source of bugs.\n\nWith Coroutines, we can centralize our async logic. The `rx` parameter is an Channel that allows code external to the coroutine to send data *into* the coroutine. Instead of looping on an external service, we can loop on the channel itself, processing messages from within our app without needing to spawn a new future. To send data into the coroutine, we would call \"send\" on the handle.\n\n````rust, no_run@use_coroutine_reference.rs\nuse futures_util::StreamExt;\n\nenum ProfileUpdate {\n SetUsername(String),\n SetAge(i32),\n}\n\nlet profile = use_coroutine(|mut rx: UnboundedReceiver| async move {\n let mut server = connect_to_server().await;\n\n while let Some(msg) = rx.next().await {\n match msg {\n ProfileUpdate::SetUsername(name) => server.update_username(name).await,\n ProfileUpdate::SetAge(age) => server.update_age(age).await,\n }\n }\n});\n\nrsx! {\n button { onclick: move |_| profile.send(ProfileUpdate::SetUsername(\"Bob\".to_string())),\n \"Update username\"\n }\n}\n````\n\n > \n > Note: In order to use/run the `rx.next().await` statement you will need to extend the \\[`Stream`\\] trait (used by \\[`UnboundedReceiver`\\]) by adding 'futures_util' as a dependency to your project and adding the `use futures_util::stream::StreamExt;`.\n\nFor sufficiently complex apps, we could build a bunch of different useful \"services\" that loop on channels to update the app.\n\n````rust, no_run@use_coroutine_reference.rs\nlet profile = use_coroutine(profile_service);\nlet editor = use_coroutine(editor_service);\nlet sync = use_coroutine(sync_service);\n\nasync fn profile_service(rx: UnboundedReceiver) {\n // do stuff\n}\n\nasync fn sync_service(rx: UnboundedReceiver) {\n // do stuff\n}\n\nasync fn editor_service(rx: UnboundedReceiver) {\n // do stuff\n}\n````\n\nWe can combine coroutines with Global State 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 `Signal::global` or Redux – the only Atoms that need to exist are those that are used to drive the display/UI.\n\n````rust, no_run@use_coroutine_reference.rs\nstatic USERNAME: GlobalSignal = Signal::global(|| \"default\".to_string());\n\nfn app() -> Element {\n use_coroutine(sync_service);\n\n rsx! {\n Banner {}\n }\n}\n\nfn Banner() -> Element {\n rsx! {\n h1 { \"Welcome back, {USERNAME}\" }\n }\n}\n````\n\nNow, in our sync service, we can structure our state however we want. We only need to update the view values when ready.\n\n````rust, no_run@use_coroutine_reference.rs\nuse futures_util::StreamExt;\n\nstatic USERNAME: GlobalSignal = Signal::global(|| \"default\".to_string());\nstatic ERRORS: GlobalSignal> = Signal::global(|| Vec::new());\n\nenum SyncAction {\n SetUsername(String),\n}\n\nasync fn sync_service(mut rx: UnboundedReceiver) {\n while let Some(msg) = rx.next().await {\n match msg {\n SyncAction::SetUsername(name) => {\n if set_name_on_server(&name).await.is_ok() {\n *USERNAME.write() = name;\n } else {\n *ERRORS.write() = vec![\"Failed to set username\".to_string()];\n }\n }\n }\n }\n}\n````\n\n## Automatic injection into the Context API\n\nCoroutine handles are automatically injected through the context API. You can use the `use_coroutine_handle` hook with the message type as a generic to fetch a handle.\n\n````rust, no_run@use_coroutine_reference.rs\nfn Child() -> Element {\n let sync_task = use_coroutine_handle::();\n\n sync_task.send(SyncAction::SetUsername);\n\n todo!()\n}\n````" + } + 88usize => { + "# Create a Project\n\nOnce you have the Dioxus CLI installed, you can use it to create your own project!\n\n## Initializing a project\n\nFirst, run the `dx new` command to create a new project.\n\n > \n > It clones this [template](https://github.com/DioxusLabs/dioxus-template), which is used to create dioxus apps.\n > \n > You can create your project from a different template by passing the `template` argument:\n > \n > ````\n > dx new --template gh:dioxuslabs/dioxus-template\n > ````\n\nNext, navigate into your new project using `cd project-name`, or simply opening it in an IDE.\n\n > \n > Make sure the WASM target is installed before running the projects.\n > You can install the WASM target for rust using rustup:\n > \n > ````\n > rustup target add wasm32-unknown-unknown\n > ````\n\nFinally, serve your project with `dx serve`! The CLI will tell you the address it is serving on, along with additional\ninfo such as code warnings." + } + 27usize => { + "# Creating Our First Route\n\nIn this chapter, we will start utilizing Dioxus Router and add a homepage and a\n404 page to our project.\n\n## Fundamentals\n\nThe core of the Dioxus Router is the [`Routable`] macro and the [`Router`] component.\n\nRoutable is a trait for anything that can:\n\n* Be parsed from a URL\n* Be turned into a URL\n* Be rendered as to a Element\n\nLet's create a new router. First, we need an actual page to route to! Let's add a homepage component:\n\n````rust@first_route.rs\n#[component]\nfn Home() -> Element {\n rsx! { h1 { \"Welcome to the Dioxus Blog!\" } }\n}\n````\n\n## Creating Routes\n\nWe want to use Dioxus Router to separate our application into different \"pages\".\nDioxus Router will then determine which page to render based on the URL path.\n\nTo start using Dioxus Router, we need to use the [`Routable`] macro.\n\nThe [`Routable`] macro takes an enum with all of the possible routes in our application. Each variant of the enum represents a route and must be annotated with the `#[route(path)]` attribute.\n\n````rust@first_route.rs\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n/// An enum of all of the possible routes in the app.\n#[derive(Routable, Clone)]\nenum Route {\n // The home page is at the / route\n #[route(\"/\")]\n Home {},\n}\n````\n\nThe [`Router`] component will provide a router context for all the inner components and hooks to use. You usually will want to place this at the top of your components tree.\n\n````rust@first_route.rs\nfn App() -> Element {\n rsx! { Router:: {} }\n}\n````\n\nIf you head to your application's browser tab, you should now see the text\n`Welcome to Dioxus Blog!` when on the root URL (`http://localhost:8080/`). If\nyou enter a different path for the URL, nothing should be displayed.\n\nThis is because we told Dioxus Router to render the `Home` component only when\nthe URL path is `/`.\n\n## Fallback Route\n\nIn our example, when a route doesn't exist Dioxus Router doesn't render anything. Many sites also have a \"404\" page when a path does not exist. Let's add one to our site.\n\nFirst, we create a new `PageNotFound` component.\n\n````rust@catch_all.rs\n#[component]\nfn PageNotFound(route: Vec) -> Element {\n rsx! {\n h1 { \"Page not found\" }\n p { \"We are terribly sorry, but the page you requested doesn't exist.\" }\n pre { color: \"red\", \"log:\\nattemped to navigate to: {route:?}\" }\n }\n}\n````\n\nNext, register the route in the Route enum to match if all other routes fail.\n\n````rust@catch_all.rs\n#[derive(Routable, Clone)]\nenum Route {\n #[route(\"/\")]\n Home {},\n // PageNotFound is a catch all route that will match any route and placing the matched segments in the route field\n #[route(\"/:..route\")]\n PageNotFound { route: Vec },\n}\n````\n\nNow when you go to a route that doesn't exist, you should see the page not found\ntext.\n\n## Conclusion\n\nIn this chapter, we learned how to create a route and tell Dioxus Router what\ncomponent to render when the URL path is `/`. We also created a 404 page to\nhandle when a route doesn't exist. Next, we'll create the blog portion of our\nsite. We will utilize nested routes and URL parameters." + } + 43usize => { + "# Desktop\n\nThis guide will cover concepts specific to the Dioxus desktop renderer.\n\nApps built with Dioxus desktop use the system WebView to render the page. This makes the final size of application much smaller than other WebView renderers (typically under 5MB).\n\nAlthough desktop apps are rendered in a WebView, your Rust code runs natively. 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 easily accessible though system APIs.\n\nDioxus desktop is built on top of [wry](https://github.com/tauri-apps/wry), a Rust library for creating desktop applications with a WebView.\n\n > \n > In the future, we plan to move to a custom web renderer-based DOM renderer with WGPU integrations ([Blitz](https://github.com/DioxusLabs/blitz)).\n\n## Examples\n\n* [File Explorer](https://github.com/DioxusLabs/dioxus/tree/main/example-projects/file-explorer)\n* [Tailwind App](https://github.com/DioxusLabs/dioxus/tree/main/examples/tailwind)\n\n[![Tailwind App screenshot](/assets/static/tailwind_desktop_app.png)](https://github.com/DioxusLabs/dioxus/tree/main/examples/tailwind)\n\n## Running Javascript\n\nDioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose.\n\nFor these cases, Dioxus desktop exposes the use_eval hook that allows you to run raw Javascript in the webview:\n\n````rust@eval.rs\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app);\n}\n\nfn app() -> Element {\n let future = use_resource(move || async move {\n // You can create as many eval instances as you want\n let mut eval = document::eval(\n r#\"\n // You can send messages from JavaScript to Rust with the dioxus.send function\n dioxus.send(\"Hi from JS!\");\n // You can receive messages from Rust to JavaScript with the dioxus.recv function\n let msg = await dioxus.recv();\n console.log(msg);\n \"#,\n );\n\n // You can send messages to JavaScript with the send method\n eval.send(\"Hi from Rust!\").unwrap();\n\n // You can receive any message from JavaScript with the recv method\n eval.recv::().await.unwrap()\n });\n\n match future.read_unchecked().as_ref() {\n Some(v) => rsx! {\n p { \"{v}\" }\n },\n _ => rsx! {\n p { \"hello\" }\n },\n }\n}\n\n````\n\n## Custom Assets\n\nYou can link to local assets in dioxus desktop instead of using a url:\n\n````rust@custom_assets.rs\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app);\n}\n\nfn app() -> Element {\n rsx! {\n div {\n img { src: asset!(\"/assets/static/scanner.png\") }\n }\n }\n}\n\n````\n\nYou can read more about assets in the [assets](../assets.md) reference.\n\n## Integrating with Wry\n\nIn cases where you need more low level control over your window, you can use wry APIs exposed through the [Desktop Config](https://docs.rs/dioxus-desktop/0.6.0/dioxus_desktop/struct.Config.html) and the [use_window hook](https://docs.rs/dioxus-desktop/0.6.0/dioxus_desktop/fn.use_window.html)" + } + 17usize => { + "# Building UIs with RSX\n\nDioxus renders to HTML, if you are not familiar with HTML, this guide will help you get started with the basics. For more detail, the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTML) are a great resource.\n\n## Text Nodes\n\nAny content surrounded by quotes is rendered as a text node in rsx:\n\n````rust, no_run@building_uis_with_rsx.rs\nrsx! {\n \"Hello world\"\n}\n````\n\n````inject-dioxus\nDemoFrame {\n building_uis_with_rsx::Text {}\n}\n````\n\nYou can include formatted segments inside of the text just like the `format!` macro:\n\n````rust, no_run@building_uis_with_rsx.rs\nlet user = use_signal(|| User {\n name: \"Dioxus\".to_string(),\n});\nrsx! {\n // Unlike the format macro, you can include many expressions inline in the formatted text\n \"Hello {user.read().name}\"\n}\n````\n\n````inject-dioxus\nDemoFrame {\n building_uis_with_rsx::FormattedText {}\n}\n````\n\n## Elements\n\nThe most basic building block of HTML is an element. In rsx, you can create elements with the name and then curly braces. One of the most common elements is the `input` element. The input element creates an interactive input box:\n\n````rust, no_run@building_uis_with_rsx.rs\nrsx! {\n input {}\n}\n````\n\n````inject-dioxus\nDemoFrame {\n building_uis_with_rsx::Input {}\n}\n````\n\n > \n > Bonus: web components\n > Any element with a dash in the name is a web component. Web components are rendered directly in dioxus without type checking. We recommend wrapping web components in a type safe component to make them easier to use.\n > \n > ````rust, no_run@building_uis_with_rsx.rs\n > rsx! {\n > my-web-component {}\n > }\n > ````\n\n## Attributes\n\nAttributes provide extra information about an element. You can specify attributes in dioxus inside an element's braces by typing the name of the attribute, a colon, and then the value (typically a formatted string). We can use an attribute to set the `type` of an input element. The default type is `text` which shows a text input box, but we can set it to `number` to only accept numbers:\n\n````rust, no_run@building_uis_with_rsx.rs\nrsx! {\n input { type: \"number\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n building_uis_with_rsx::InputType {}\n}\n````\n\nJust like text nodes, attributes can include formatted segments. We can set the value of the input element to a signal to control it:\n\n````rust, no_run@building_uis_with_rsx.rs\nlet mut value = use_signal(|| \"Hello world\".to_string());\nrsx! {\n input { value: \"{value}\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n building_uis_with_rsx::InputValue {}\n}\n````\n\n### Conditional Attributes\n\nYou can conditionally set an attribute by setting the attribute value to an unterminated if statement. If the if statement evaluates to true, the attribute will be set:\n\n````rust, no_run@building_uis_with_rsx.rs\nlet number_type = use_signal(|| false);\nrsx! {\n input { type: if number_type() { \"number\" } }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n building_uis_with_rsx::InputDisabled {}\n}\n````\n\n## Event Listeners\n\nEvent listeners allow you to respond to user input. In rsx, event handlers always start with `on`. The syntax is the same as normal attributes, but event handlers only accept a closure that responds to the event. We can attach an event listener to the `oninput` event of the input element to listen for changes to the input:\n\n````rust, no_run@building_uis_with_rsx.rs\nlet mut value = use_signal(|| \"Hello world\".to_string());\nrsx! {\n input {\n oninput: move |event| value.set(event.value()),\n value: \"{value}\"\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n building_uis_with_rsx::OnInput {}\n}\n````\n\n## Children\n\nYou can add children to an element after all attributes and event listeners. Elements can accept text, components or other elements as children. We can add a `div` element around our input to center it:\n\n````rust, no_run@building_uis_with_rsx.rs\nrsx! {\n div {\n // display sets the layout mode of the element\n display: \"flex\",\n // justify-content centers the element horizontally\n justify_content: \"center\",\n input {\n type: \"number\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n building_uis_with_rsx::InputChildren {}\n}\n````\n\n## Loops\n\nYou can insert for loops directly in rsx. The body of the loop accepts any number of children that will be rendered with each iteration of the loop. The `ul` element in html renders an unordered list with any number of `li` (list item) elements. We can use those two elements to render a list of items in a loop:\n\n````rust, no_run@building_uis_with_rsx.rs\nlet mut items = use_signal(|| vec![\"Hello\", \"Dioxus\"]);\n\nrsx! {\n ul {\n for item in items.iter() {\n li { \"{item}\" }\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n building_uis_with_rsx::ForLoop {}\n}\n````\n\nEach item in your list should have unique value that is stable across rerenders called a key. Keys are used to identify how items move while diffing. Without keys, it is easy to accidentally lose or move state when you reorder items in a list. We can add keys to our list items by using the `key` attribute:\n\n````rust, no_run@building_uis_with_rsx.rs\nlet mut items = use_signal(|| vec![\"Hello\", \"Dioxus\"]);\n\nrsx! {\n ul {\n for item in items.iter() {\n li { key: \"{item}\", \"{item}\" }\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n building_uis_with_rsx::KeyedForLoop {}\n}\n````\n\n## If Statements\n\nYou can also use if/else statements in rsx. Each branch of the if statement accepts child nodes that will be rendered if the condition is true. We can use the `if` statement to conditionally render a login screen:\n\n````rust, no_run@building_uis_with_rsx.rs\nlet logged_in = use_signal(|| false);\n\nrsx! {\n div {\n if logged_in() {\n \"You are logged in\"\n } else {\n \"You are not logged in\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n building_uis_with_rsx::IfStatement {}\n}\n````\n\n## Why RSX and not HTML ?\n\nIf you've seen React's JSX or the `html!{}` Rust macro, you might be curious as to why Dioxus chose to use its own syntax instead of a syntax that looks more similar to HTML.\n\nA few reasons:\n\n* RSX gets token coloring and code-folding without additional tooling\n* RSX is faster to type since curly braces are auto-closed\n* Not all RSX is HTML - Dioxus can be used in non-HTML contexts\n* HTML is not valid Rust - not all HTML can be used in html!{}" + } + 35usize => { + "# Layouts\n\nLayouts allow you to wrap all child routes in a component. This can be useful when creating something like a header that will be used in many different routes.\n\n[`Outlet`] tells the router where to render content in layouts. In the following example,\nthe Index will be rendered within the [`Outlet`].\n\nThis page is built with the Dioxus. It uses Layouts in several different places. Here is an outline of how layouts are used on the current page. Hover over different layouts to see what elements they are on the page.\n\n````inject-dioxus\nLayoutsExplanation {}\n````\n\nHere is a more complete example of a layout wrapping the body of a page.\n\n````rust@outlet.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(Wrapper)]\n #[route(\"/\")]\n Index {},\n}\n\n#[component]\nfn Wrapper() -> Element {\n rsx! {\n header { \"header\" }\n // The index route will be rendered here\n Outlet:: {}\n footer { \"footer\" }\n }\n}\n\n#[component]\nfn Index() -> Element {\n rsx! { h1 { \"Index\" } }\n}\n````\n\nThe example above will output the following HTML (line breaks added for\nreadability):\n\n````html\n
header
\n

Index

\n
footer
\n````\n\n## Layouts with dynamic segments\n\nYou can combine layouts with [nested routes](./routes/nested.md) to create dynamic layouts with content that changes based on the current route.\n\nJust like routes, layouts components must accept a prop for each dynamic segment in the route. For example, if you have a route with a dynamic segment like `/:name`, your layout component must accept a `name` prop:\n\n````rust@outlet.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[nest(\"/:name\")]\n #[layout(Wrapper)]\n #[route(\"/\")]\n Index {\n name: String,\n },\n}\n\n#[component]\nfn Wrapper(name: String) -> Element {\n rsx! {\n header { \"Welcome {name}!\" }\n // The index route will be rendered here\n Outlet:: {}\n footer { \"footer\" }\n }\n}\n\n#[component]\nfn Index(name: String) -> Element {\n rsx! { h1 { \"This is a homepage for {name}\" } }\n}\n````\n\nOr to get the full route, you can use the `use_route` hook.\n\n````rust@outlet.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(Wrapper)]\n #[route(\"/:name\")]\n Index {\n name: String,\n },\n}\n\n#[component]\nfn Wrapper() -> Element {\n let full_route = use_route::();\n rsx! {\n header { \"Welcome to {full_route}!\" }\n // The index route will be rendered here\n Outlet:: {}\n footer { \"footer\" }\n }\n}\n\n#[component]\nfn Index(name: String) -> Element {\n rsx! { h1 { \"This is a homepage for {name}\" } }\n}\n````" + } + 19usize => { + "# Managing State\n\nIn Dioxus, your app is defined as a function of the current state. As the state changes, the parts of your app that depend on that state will automatically re-run. Reactivity automatically tracks state and updates derived state in your application.\n\n## Creating State\n\nYou can create mutable state in Dioxus with Signals. Signals are tracked values that automatically update your app when you change them. They form the skeleton of your app's state from which you can derive other state. Signals are often driven directly from user input through event handlers or async tasks.\n\nYou can create a signal with the `use_signal` hook:\n\n````rust@reactivity.rs\nlet mut signal = use_signal(|| 0);\n````\n\nOnce you have your signal, you can clone it by calling the signal like a function or get a reference to the inner value with the `.read()` method:\n\n````rust@reactivity.rs\n// Call the signal like a function to clone the current value\nlet value: i32 = signal();\n// get a reference to the inner value with the .read() method\nlet value: &i32 = &signal.read();\n// or use one of the traits implemented for Signal like Display\nlog!(\"{signal}\");\n````\n\nFinally, you can set the value of the signal with the `.set()` method or get a mutable reference to the inner value with the `.write()` method:\n\n````rust@reactivity.rs\n// Set the value from the signal\nsignal.set(1);\n// get a mutable reference to the inner value with the .write() method\nlet mut value: &mut i32 = &mut signal.write();\n*value += 1;\n````\n\n### Reactive Scopes\n\nThe simplest reactive primitive in Dioxus is the `use_effect` hook. It creates a closure that is run any time a tracked value that is run inside the closure changes.\n\nAny value you read inside the closure will become a dependency of the effect. If the value changes, the effect will rerun.\n\n````rust@reactivity.rs\nfn Effect() -> Element {\n // use_signal creates a tracked value called count\n let mut count = use_signal(|| 0);\n\n use_effect(move || {\n // When we read count, it becomes a dependency of the effect\n let current_count = count();\n // Whenever count changes, the effect will rerun\n log!(\"{current_count}\");\n });\n\n rsx! {\n button { onclick: move |_| count += 1, \"Increment\" }\n\n div { \"Count is {count}\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n reactivity::EffectDemo {}\n}\n````\n\n### Derived State\n\n`use_memo` is a reactive primitive that lets you derive state from any tracked value. It takes a closure that computes the new state and returns a tracked value with the current state of the memo. Any time a dependency of the memo changes, the memo will rerun.\n\nThe value you return from the closure will only change when the output of the closure changes (`PartialEq` between the old and new value returns false).\n\n````rust@reactivity.rs\nfn Memo() -> Element {\n let mut count = use_signal(|| 0);\n\n // use_memo creates a tracked value that is derived from count\n // Since we read count inside the closure, it becomes a dependency of the memo\n // Whenever count changes, the memo will rerun\n let half_count = use_memo(move || count() / 2);\n\n use_effect(move || {\n // half_count is itself a tracked value\n // When we read half_count, it becomes a dependency of the effect\n // and the effect will rerun when half_count changes\n log!(\"{half_count}\");\n });\n\n rsx! {\n button { onclick: move |_| count += 1, \"Increment\" }\n\n div { \"Count is {count}\" }\n div { \"Half count is {half_count}\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n reactivity::MemoDemo {}\n}\n````\n\n### Derived Async State\n\n`use_resource` is a reactive primitive that lets you derive state from any async closure. It takes an async closure that computes the new state and returns a tracked value with the current state of the resource. Any time a dependency of the resource changes, the resource will rerun.\n\nThe value you return from the closure will only change when the state of the future changes. Unlike `use_memo`, the resource's output is not memoized with `PartialEq`.\n\n````rust@reactivity.rs\nfn Resource() -> Element {\n let mut count = use_signal(|| 0);\n\n // use_resource creates a tracked value that is derived from count\n // Since we read count inside the closure, it becomes a dependency of the resource\n // Whenever count changes, the resource will rerun\n let half_count = use_resource(move || async move {\n // You can do async work inside resources\n gloo_timers::future::TimeoutFuture::new(100).await;\n count() / 2\n });\n\n use_effect(move || {\n // half_count is itself a tracked value\n // When we read half_count, it becomes a dependency of the effect\n // and the effect will rerun when half_count changes\n log!(\"{:?}\", half_count());\n });\n\n rsx! {\n button { onclick: move |_| count += 1, \"Change Signal\" }\n\n div { \"Count is {count}\" }\n div { \"Half count is {half_count():?}\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n reactivity::ResourceDemo {}\n}\n````\n\n### Derived UI\n\nComponents are functions that return some UI. They memorize the output of the function just like memos. Components keep track of any dependencies you read inside the component and rerun when those dependencies change.\n\n````rust@reactivity.rs\nfn Component() -> Element {\n let mut count = use_signal(|| 0);\n\n rsx! {\n button { onclick: move |_| count += 1, \"Change Signal\" }\n\n // Since we read count inside Component, it becomes a dependency of Component\n // Whenever count changes, Component will rerun\n Count { count: count() }\n }\n}\n\n// Components automatically memorize their props. If the props change, Count will rerun\n#[component]\nfn Count(count: i32) -> Element {\n rsx! {\n div { \"Count: {count}\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n reactivity::ComponentDemo {}\n}\n````\n\n### Working with Untracked State\n\nMost of the state in your app will be tracked values. All built in hooks return tracked values, and we encourage custom hooks to do the same. However, there are times when you need to work with untracked state. For example, you may receive a raw untracked value in props. When you read an untracked value inside a reactive context, it will not subscribe to the value:\n\n````rust@reactivity.rs\nfn Component() -> Element {\n let mut count = use_signal(|| 0);\n\n rsx! {\n button { onclick: move |_| count += 1, \"Change Signal\" }\n\n Count { count: count() }\n }\n}\n\n// The count reruns the component when it changes, but it is not a tracked value\n#[component]\nfn Count(count: i32) -> Element {\n // When you read count inside the memo, it does not subscribe to the count signal\n // because the value is not reactive\n let double_count = use_memo(move || count * 2);\n\n rsx! {\n div { \"Double count: {double_count}\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n reactivity::NonReactiveDemo {}\n}\n````\n\nYou can start tracking raw state with the `use_reactive` hook. This hook takes a tuple of dependencies and returns a reactive closure. When the closure is called in a reactive context, it will track subscribe to the dependencies and rerun the closure when the dependencies change.\n\n````rust@reactivity.rs\n#[component]\nfn Count(count: i32) -> Element {\n // You can manually track a non-reactive value with the use_reactive hook\n let double_count = use_memo(\n // Use reactive takes a tuple of dependencies and returns a reactive closure\n use_reactive!(|(count,)| count * 2),\n );\n\n rsx! {\n div { \"Double count: {double_count}\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n reactivity::UseReactiveDemo {}\n}\n````\n\n### Opting Out of Subscriptions\n\nIn some situations, you may need to read a reactive value without subscribing to it. You can use the `peek` method to get a reference to the inner value without registering the value as a dependency of the current reactive context:\n\n````rust@reactivity.rs\nfn Peek() -> Element {\n let mut count = use_signal(|| 0);\n\n // The toggle signal is a tracked value\n let mut toggle = use_signal(|| false);\n\n use_effect(move || {\n // When we read count, it becomes a dependency of the effect\n let current_count = count();\n log!(\"current_count is {current_count}\");\n\n if current_count % 4 == 0 {\n // We peek at the value of toggle instead of reading it,\n // so it does not become a dependency\n let current_toggle = *toggle.peek();\n // We didn't subscribe to toggle, so this will not cause\n // the effect to rerun forever\n toggle.set(!current_toggle);\n log!(\"flipped toggle to {current_toggle}\");\n }\n });\n\n rsx! {\n button { onclick: move |_| count += 1, \"Change Signal\" }\n\n div { \"Count is {count}\" }\n div { \"Toggle is {toggle}\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n reactivity::PeekDemo {}\n}\n````\n\n### Making Props Reactive\n\nTo avoid losing reactivity with props, we recommend you wrap any props you want to track in a `ReadOnlySignal`. Dioxus will automatically convert `T` into `ReadOnlySignal` when you pass props to the component. This will ensure your props are tracked and rerun any state you derive in the component:\n\n````rust@reactivity.rs\n// You can track props by wrapping the type in a ReadOnlySignal\n// Dioxus will automatically convert T into ReadOnlySignal when you pass\n// props to the component\n#[component]\nfn Count(count: ReadOnlySignal) -> Element {\n // Then when you read count inside the memo, it subscribes to the count signal\n let double_count = use_memo(move || count() * 2);\n\n rsx! {\n div { \"Double count: {double_count}\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n reactivity::MakingPropsReactiveDemo {}\n}\n````\n\n## Moving Around State\n\nAs you create signals and derived state in your app, you will need to move around that state between components. Dioxus provides three different ways to pass around state:\n\n### Passing props\n\nYou can pass your values through component [props](../../reference/component_props.md). This should be your default when passing around state. It is the most explicit and local to your component. Use this until it gets annoying to pass around the value:\n\n````rust@moving_state_around.rs\npub fn ParentComponent() -> Element {\n let count = use_signal(|| 0);\n\n rsx! {\n \"Count is {count}\"\n IncrementButton {\n count\n }\n }\n}\n\n#[component]\nfn IncrementButton(mut count: Signal) -> Element {\n rsx! {\n button {\n onclick: move |_| count += 1,\n \"Increment\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n moving_state_around::PassingProps {}\n}\n````\n\n### Passing context\n\nIf you need a slightly more powerful way to pass around state, you can use the context API.\n\nThe context API lets you pass state from a parent component to all children. This is useful if you want to share state between many components. You can insert a unique type into the context with the [`use_context_provider`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html) hook in the parent component. Then you can access the context in any child component with the [`use_context`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html) hook.\n\n````rust@moving_state_around.rs\n#[derive(Clone, Copy)]\nstruct MyState {\n count: Signal,\n}\n\npub fn ParentComponent() -> Element {\n // Use context provider provides an unique type to all children of this component\n let state = use_context_provider(|| MyState {\n count: Signal::new(0),\n });\n\n rsx! {\n \"Count is {state.count}\"\n // IncrementButton will have access to the count without explicitly passing it through props\n IncrementButton {}\n }\n}\n\n#[component]\nfn IncrementButton() -> Element {\n // Use context gets the value from a parent component\n let mut count = use_context::().count;\n\n rsx! {\n button {\n onclick: move |_| count += 1,\n \"Increment\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n moving_state_around::PassingContext {}\n}\n````\n\nThis is slightly less explicit than passing it as a prop, but it is still local to the component. This is really great if you want state that is global to part of your app. It lets you create multiple global-ish states while still making state different when you reuse components. If I create a new `ParentComponent`, it will have a new `MyState`.\n\n### Using globals\n\nFinally, if you have truly global state, you can put your state in a `Global` static. This is useful if you want to share state with your whole app:\n\n````rust@moving_state_around.rs\nuse dioxus::prelude::*;\n// Globals are created the first time you access them with the closure you pass to Global::new\nstatic COUNT: GlobalSignal = Global::new(|| 0);\n\npub fn ParentComponent() -> Element {\n rsx! {\n \"Count is {COUNT}\"\n IncrementButton {}\n }\n}\n\nfn IncrementButton() -> Element {\n rsx! {\n button {\n // You don't need to pass anything around or get anything out of the context because COUNT is global\n onclick: move |_| *COUNT.write() += 1,\n \"Increment\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n moving_state_around::UsingGlobals {}\n}\n````\n\nGlobal state can be very ergonomic if your state is truly global, but you shouldn't use it if you need state to be different for different instances of your component. If I create another `IncrementButton` it will use the same `COUNT`. Libraries should generally avoid this to make components more reusable.\n\n > \n > Note: Even though it is in a static, `COUNT` will be different for each app instance so you don't need to worry about state mangling when multiple instances of your app are running on the server" + } + 89usize => { + "# Configure Project\n\nThis chapter will teach you how to configure the CLI with the `Dioxus.toml` file. There's an [example](#config-example) which has comments to describe individual keys. You can copy that or view this documentation for a more complete learning experience.\n\n\"🔒\" indicates a mandatory item. Some headers are mandatory, but none of the keys inside them are. In that case, you only need to include the header, but no keys. It might look weird, but it's normal.\n\n## Structure\n\nEach header has its TOML form directly under it.\n\n### Application 🔒\n\n````toml\n[application]\n````\n\nApplication-wide configuration. Applies to both web and desktop.\n\n* **asset_dir** - The directory with your static assets. The CLI will automatically copy these assets into the **out_dir** after a build/serve.\n ````toml\n asset_dir = \"public\"\n ````\n\n* **sub_package** - The sub package in the workspace to build by default.\n ````toml\n sub_package = \"my-crate\"\n ````\n\n### Web.App 🔒\n\n````toml\n[web.app]\n````\n\nWeb-specific configuration.\n\n* **title** - The title of the web page.\n ````toml\n # HTML title tag content\n title = \"project_name\"\n ````\n\n* **base_path** - The base path to build the application for serving at. This can be useful when serving your application in a subdirectory under a domain. For example, when building a site to be served on GitHub Pages.\n ````toml\n # The application will be served at domain.com/my_application/, so we need to modify the base_path to the path where the application will be served\n base_path = \"my_application\"\n ````\n\n### Web.Watcher 🔒\n\n````toml\n[web.watcher]\n````\n\nDevelopment server configuration.\n\n* **reload_html** - If this is true, the cli will rebuild the index.html file every time the application is rebuilt\n \n ````toml\n reload_html = true\n ````\n\n* **watch_path** - The files & directories to monitor for changes\n \n ````toml\n watch_path = [\"src\", \"public\"]\n ````\n\n* **index_on_404** - If enabled, Dioxus will serve the root page when a route is not found.\n *This is needed when serving an application that uses the router*. However, when serving your app using something else than Dioxus (e.g. GitHub Pages), you will have to check how to configure it on that platform. In GitHub Pages, you can make a copy of `index.html` named `404.html` in the same directory.\n \n ````toml\n index_on_404 = true\n ````\n\n### Web.Resource 🔒\n\n````toml\n[web.resource]\n````\n\nStatic resource configuration.\n\n* **style** - CSS files to include in your application.\n \n ````toml\n style = [\n # Include from public_dir.\n \"./assets/style.css\",\n # Or some asset from online cdn.\n \"https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css\"\n ]\n ````\n\n* **script** - JavaScript files to include in your application.\n \n ````toml\n script = [\n # Include from asset_dir.\n \"./public/index.js\",\n # Or from an online CDN.\n \"https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js\"\n ]\n ````\n\n### Web.Resource.Dev 🔒\n\n````toml\n[web.resource.dev]\n````\n\nThis is the same as [`[web.resource]`](#webresource-), but it only works in development servers. For example, if you want to include a file in a `dx serve` server, but not a `dx serve --release` server, put it here.\n\n### Web.Proxy\n\n````toml\n[[web.proxy]]\n````\n\nConfiguration related to any proxies your application requires during development. Proxies will forward requests to a new service.\n\n* **backend** - The URL to the server to proxy. The CLI will forward any requests under the backend relative route to the backend instead of returning 404\n ````toml\n backend = \"http://localhost:8000/api/\"\n ````\n \n This will cause any requests made to the dev server with prefix /api/ to be redirected to the backend server at http://localhost:8000. The path and query parameters will be passed on as-is (path rewriting is currently not supported).\n\n### Web.https\n\n````toml\n[[web.https]]\n````\n\nControls the https config for the CLI.\n\n* **enabled** enables or disables https in the CLI\n ````toml\n enabled = true\n ````\n\n* **mkcert** enables or disables generating certs with the mkcert CLI\n ````toml\n mkcert = true\n ````\n\n* **key_path** sets the path to use for the https key\n ````toml\n key_path = \"/path/to/key\"\n ````\n\n* **cert_path** sets the path to use for the https cert\n ````toml\n cert_path = \"/path/to/cert\"\n ````\n\n### Web.pre_compress\n\nIf this setting is enabled, the CLI will pre-compress the built assets in release mode with brotli. This setting is enabled by default.\n\n````toml\n[web]\npre_compress = true\n````\n\n### Web.wasm_opt\n\nControls the wasm-opt config for the CLI.\n\n* **level** sets the level of optimization to use for wasm-opt in release builds.\n * z: optimize aggressively for size\n * s: optimize for size\n * 1: optimize for speed\n * 2: optimize for more for speed\n * 3: optimize for even more for speed\n * 4: optimize aggressively for speed (default)\n ````toml\n level = \"z\"\n ````\n\n* **debug** keep debug symbols in the wasm file even in release builds\n ````toml\n debug = true\n ````\n\n### Bundle\n\n````toml\n[bundle]\n````\n\nControls the bundling process for your application. Dioxus uses tauri-bundler under the hood. This section only includes a subset of the options available in tauri-bundler. More options can be found in the tauri-bundler [documentation](https://v1.tauri.app/v1/guides/building/#configuration-options).\n\n* **identifier** - A unique identifier for your application (e.g., `com.dioxuslabs`).\n ````toml\n identifier = \"com.dioxuslabs\"\n ````\n\n* **publisher** - The name of the entity publishing the application.\n ````toml\n publisher = \"DioxusLabs\"\n ````\n\n* **icon** - Paths to icon files to be used in the bundle. Icon files must be square and 16, 24, 32, 64, or 256 pixels in size. PNG icons must have a 32 bit depth in the RGBA format. If you use a `.icns` file is must fit [this](https://github.com/tauri-apps/tauri/blob/d8db5042a28635259f646c329c3ec5ccf23eac9e/tooling/cli/src/helpers/icns.json) format. The icons must include a `.icns` icon for macOS, `.ico` for Windows and `.png` for Linux.\n ````toml\n icon = [\n \"icons/32x32.png\",\n \"icons/128x128.png\",\n \"icons/128x128@2x.png\",\n \"icons/icon.icns\",\n \"icons/icon.ico\"\n ]\n ````\n\n* **resources** - Additional files to include in the bundle. Each asset is copied from the path and is accessible from the bundle at the same path. Any [assets](../guides/assets.md) are automatically bundled with the installer.\n ````toml\n resources = [\"path/to/resource\"]\n ````\n\n* **copyright** - Copyright information for the application.\n ````toml\n copyright = \"Copyright 2023 DioxusLabs\"\n ````\n\n* **category** - The category of the application. Must be one of `Business`, `DeveloperTool`, `Education`, `Entertainment`, `Finance`, `Game`, `ActionGame`, `AdventureGame`, `ArcadeGame`, `BoardGame`, `CardGame`, `CasinoGame`, `DiceGame`, `EducationalGame`, `FamilyGame`, `KidsGame`, `MusicGame`, `PuzzleGame`, `RacingGame`, `RolePlayingGame`, `SimulationGame`, `SportsGame`, `StrategyGame`, `TriviaGame`, `WordGame`, `GraphicsAndDesign`, `HealthcareAndFitness`, `Lifestyle`, `Medical`, `Music`, `News`, `Photography`, `Productivity`, `Reference`, `SocialNetworking`, `Sports`, `Travel`, `Utility`, `Video`, or `Weather`\n ````toml\n category = \"Utility\"\n ````\n\n* **short_description** - A brief description of the application.\n ````toml\n short_description = \"A utility application built with Dioxus\"\n ````\n\n* **long_description** - A detailed description of the application.\n ````toml\n long_description = \"This application provides various utility functions...\"\n ````\n\n* **external_bin** - Paths to external sidecar binaries to include in the bundle. These bundles may be accessed at runtime with the name of the binary (not the absolute path). **the target triple will be automatically added to the binary name before it is added to the bundle.**\n ````toml\n external_bin = [\"path/to/external_binary\"] # On macos, the binary at path/to/external_binary-aarch64-apple-darwin will be included in the bundle. It can be accessed at runtime with the name external_binary\n ````\n\n### Bundle.macos\n\n````toml\n[bundle.macos]\n````\n\nConfiguration options for macOS bundles.\n\n* **frameworks** - List of frameworks to include in the bundle.\n ````toml\n frameworks = [\"CoreML\"]\n ````\n\n* **minimum_system_version** - Minimum macOS version required. (default: `10.13`)\n ````toml\n minimum_system_version = \"10.13\"\n ````\n\n* **license** - Path to the license file.\n ````toml\n license = \"LICENSE.txt\"\n ````\n\n* **exception_domain** - Domain for exception handling. The domain must be lowercase without a port or protocol.\n ````toml\n exception_domain = \"mysite.com\"\n ````\n\n* **signing_identity** - macOS signing identity.\n ````toml\n signing_identity = \"SIGNING IDENTITY KEYCHAIN ENTRY NAME\"\n ````\n\n* **provider_short_name** - Provider short name for the bundle.\n ````toml\n provider_short_name = \"DioxusLabs\"\n ````\n\n* **entitlements** - Path to the entitlements file.\n ````toml\n entitlements = \"entitlements.plist\"\n ````\n\n* **hardened_runtime** - Whether to enable the [hardened runtime](https://developer.apple.com/documentation/security/hardened-runtime) in the bundle.\n ````toml\n hardened_runtime = true\n ````\n\n### Bundle.windows\n\n````toml\n[bundle.windows]\n````\n\nConfiguration options for Windows bundles.\n\n* **digest_algorithm** - Sets the file digest algorithm used for signing.\n ````toml\n digest_algorithm = \"sha-256\"\n ````\n\n* **certificate_thumbprint** - SHA1 hash of the signing certificate.\n ````toml\n certificate_thumbprint = \"A1B2C3D4E5F6...\"\n ````\n\n* **timestamp_url** - Sets the server to used for timestamping the signature.\n ````toml\n timestamp_url = \"http://timestamp.digicert.com\"\n ````\n\n* **tsp** - Whether to use the time stamping protocol.\n ````toml\n tsp = true\n ````\n\n* **icon_path** - Path to the icon for the system tray icon. (defaults to `./icons/icon.ico`)\n ````toml\n icon_path = \"assets/icon.ico\"\n ````\n\n* **webview_install_mode** - Installation mode for WebView2.\n EmbedBootstrapper: embed the WebView2 bootstrapper into the installer\n ````toml\n [webview_install_mode.EmbedBootstrapper]\n silent = true\n ````\n \n DownloadBootstrapper: download the WebView2 bootstrapper in the installer at runtime\n ````toml\n [webview_install_mode.DownloadBootstrapper]\n silent = true\n ````\n \n OfflineInstaller: Embed the WebView2 installer into the main installer\n ````toml\n [webview_install_mode.OfflineInstaller]\n silent = true\n ````\n \n FixedRuntime: Use a fixed path to the WebView2 runtime\n ````toml\n [webview_install_mode.FixedRuntime]\n path = \"path/to/runtime\"\n ````\n \n Skip: Does not install WebView2 as part of the installer. This will cause the application to fail if webview was not already installed\n ````toml\n webview_install_mode = \"Skip\"\n ````\n\n## Config example\n\nThis includes all fields, mandatory or not.\n\n````toml\n[application]\n\n# App name\nname = \"project_name\"\n\n# `build` & `serve` output path\nout_dir = \"dist\"\n\n# The static resource path\nasset_dir = \"public\"\n\n[web.app]\n\n# HTML title tag content\ntitle = \"project_name\"\n\n[web.watcher]\n\n# When watcher is triggered, regenerate the `index.html`\nreload_html = true\n\n# Which files or dirs will be monitored\nwatch_path = [\"src\", \"public\"]\n\n# Include style or script assets\n[web.resource]\n\n# CSS style file\nstyle = []\n\n# Javascript code file\nscript = []\n\n[web.resource.dev]\n\n# Same as [web.resource], but for development servers\n\n# CSS style file\nstyle = []\n\n# JavaScript files\nscript = []\n\n[[web.proxy]]\nbackend = \"http://localhost:8000/api/\"\n\n[bundle]\nidentifier = \"com.dioxuslabs\"\npublisher = \"DioxusLabs\"\nicon = \"assets/icon.png\"\n````" + } + 5usize => { + "# Your First Component\n\nNow that we've initialized our *HotDog* app, we can start building out its components.\n\n## What is a component?\n\nIn Dioxus, apps are comprised of individual functions called *Components* that take in some *Properties* and render an *Element*:\n\n````rust@guide_component.rs\nfn DogApp(props: DogAppProps) -> Element {\n todo!()\n}\n````\n\n## Component Properties\n\nAll components take an object that outlines which parameters the component can accept. All `Props` structs in Dioxus need to derive the `Properties` trait which requires both `Clone` and `PartialEq`:\n\n````rust@guide_component.rs\n#[derive(Props, PartialEq, Clone)]\nstruct DogAppProps {\n breed: String,\n}\n````\n\nDioxus provides the `#[component]` macro for simplifying how components are defined. This macro converts the parameters of the annotated function into a hidden accompanying struct.\n\n````rust@guide_component.rs\n#[component]\nfn DogApp(breed: String) -> Element {\n todo!()\n}\n````\n\nWhen building apps, you'll frequently use the `#[component]` macro. When building libraries, we generally suggest deriving Props instead.\n\n## Properties are Immutable\n\nIf you're familiar with JavaScript, then you might also be familiar with libraries like [React](http://react.dev). Dioxus is *very* similar to React: if you know React then you will feel comfortable with Dioxus.\n\nJust like React, Dioxus components are rendered by calling the function component. On every render, Dioxus makes a `.clone()` of the component's props. This ensures you can't accidentally modify your props which can lead to hard-to-track issues with state management.\n\n````rust@guide_component.rs\n#[component]\nfn DogApp(breed: String) -> Element {\n tracing::info!(\"Rendered with breed: {breed}\");\n\n todo!()\n}\n````\n\nDioxus provides types that make `.clone()` cheaper to call, so don't worry about performance gotchas here.\n\n## Component Functions are Called Multiple Times\n\nJust like React, Dioxus will call your component function multiple times throughout its lifecycle. This is called *re-rendering*. In Dioxus, re-renders are extremely cheap (much cheaper than React!). In most cases you shouldn't worry about re-rendering too frequently.\n\nWhen Dioxus re-renders your component, it compares the `Element` returned from the *last* render against the `Element` returned in the *current* render.\n\nFor example, when the `breed` property changes on the DogApp component, Dioxus will call the DogApp function a second time and compare the previous Element against the new Element.\n\n````rust@guide_component.rs\n#[component]\nfn DogApp(breed: String) -> Element {\n rsx! {\n \"Breed: {breed}\"\n }\n}\n````\n\n![Diffing](/assets/06_docs/diffing_diagram.png)\n\nDioxus will re-render your component in only two circumstances:\n\n* When the `Props` change as determined by `PartialEq`\n* When a function like `signal.set()` or `signal.write()` calls `Scope.needs_update()`\n\nUnlike React, all Dioxus components are *memoized by default* meaning Dioxus will always compare `Props` before deciding to re-render your component. As an additional optimization, Dioxus only compares dynamic parts of your RSX. Elements that don't contain dynamic data won't be checked for changes.\n\n## Composing Components\n\nIn Dioxus, *Components* are composed together to create *Apps*. Each component will hold onto its own state and handle its own updates. This makes it easy to abstract your app into different parts and even share pieces of your app as libraries for others to use.\n\nTo compose components together, we'll use the `rsx! {}` macro to define the structure of our app.\n\n````rust@guide_component.rs\n#[component]\nfn App() -> Element {\n rsx! {\n Header {}\n DogApp { breed: \"corgi\" }\n Footer {}\n }\n}\n````\n\nWe'll cover `rsx! {}` in more depth in the [next chapter](rsx.md)." + } + 4usize => { + "## Create a new project\n\nLet's get to work!\n\nYou can create a new Dioxus project by running the following command and following the prompts:\n\n````sh\ndx new hot_dog\n````\n\n![dxnew](/assets/06_docs/dx_new_06.mp4)\n\nYou'll need to select a template to use to get started.\n\n* Bare-bones: a very simple setup with just a `main.rs` an and `assets` folder.\n* Jumpstart: a scaffolded app with components, views, and suggested structure.\n* Workspace: a full cargo workspace setup with different crates per platform.\n\nWe're going to use the bare-bones template for *HotDog*. Our app won't be too complex and can fit in one file.\n\n* Select \"false\" when asked if you want to create a fullstack website.\n* Select \"false\" for the router, though we *will* eventually add the router to the app.\n* Select \"false\" for TailwindCSS. If you want to use Tailwind, make sure to read the [TailwindCSS guide](../cookbook/tailwind.md).\n* Select \"Web\" as the default platform.\n\n > \n > 📣 You don't need `dx new` to create new Dioxus apps! Dioxus apps are Rust projects and can also be built with tools like cargo.\n\n## Running the project\n\nOnce the project is generated, you can start it with the following command:\n\n````sh\ncd hot_dog\ndx serve\n````\n\n![Serve](/assets/06_docs/dx_serve_06.mp4)\n\nThis will start the cargo build and launch a web server to serve your app. If you visit the \"serve\" address (in this case, `http://127.0.0.1:8080`), then you'll receive a loading screen in your browser:\n\n![loading](/assets/06_docs/hotdog_loading.png)\n\nOnce the app is loaded, you should be greeted with the default Dioxus template app:\n\n![app](/assets/06_docs/default_dioxus_app.png)\n\nCongrats! You have your very first Dioxus app.\n\n## Structure of the app\n\nOpen the app in your editor and take a look at its structure:\n\n````sh\n├── Cargo.lock\n├── Cargo.toml\n├── Dioxus.toml\n├── README.md\n├── assets\n│\u{a0}\u{a0} ├── favicon.ico\n│\u{a0}\u{a0} ├── header.svg\n│\u{a0}\u{a0} └── main.css\n└── src\n └── main.rs\n````\n\nAll Rust apps are comprised of a root `Cargo.toml` with a `main.rs` file located in the `src` folder. Our CLI `dx` pre-filled these files with the `dioxus` dependency and some starter code for us to get building quickly.\n\nAssets in Dioxus can be placed anywhere in the project, but we suggest leaving them in the `assets` folder.\n\n## The Cargo.toml\n\nThe `Cargo.toml` outlines the dependencies to our app and specifies compiler settings. All Rust apps are *compiled*: we execute the Rust tool `cargo` which aggregates our `.rs` files together and generates a final binary executable (like a `.exe`) that runs our app.\n\nAll Dioxus apps will include `dioxus` as a dependency:\n\n````toml\n[dependencies]\ndioxus = { version = \"0.6.0\" }\n````\n\nThe prebuilt Dioxus templates initialize different cargo features for your app. `dx` will use these to decide which cargo features to enable when you specify the `--platform` feature. For example, if you use `dx serve --platform desktop` to build your app for desktop, `dx` will call `cargo build --no-default-features --features desktop`.\n\n````toml\n[features]\ndefault = [\"web\"]\nweb = [\"dioxus/web\"]\ndesktop = [\"dioxus/desktop\"]\nmobile = [\"dioxus/mobile\"]\n````\n\nStarting with Dioxus 0.6, `dx` will also initialize separate [Cargo profiles](https://doc.rust-lang.org/cargo/reference/profiles.html) for your app. These profiles let you customize the optimization level of each platform. `dx` also uses these platforms as a mechanism of isolating builds from each other.\n\n## Dioxus.toml\n\nThe `Dioxus.toml` file contains Dioxus-specific configuration for stages like bundling and deploying. Before Dioxus 0.5, we used the `Dioxus.toml` to specify asset inclusion and hot-reload watch paths, but as of Dioxus 0.6, these fields are deprecated and replaced by standards like `asset!()` and `.gitignore`.\n\nWe won't need to configure the `Dioxus.toml` for our app just yet.\n\n## Assets Folder\n\nTo include assets in your Dioxus app, you'll want to use the `asset!()` macro that we'll cover later in the [Styling and Assets](assets.md) chapter. You can include assets from anywhere within your app's file tree, but we recommend using the pregenerated `assets` folder.\n\n## main.rs\n\nFinally, the `main.rs`. The `main.rs` file is the entrypoint of our app, containing the `fn main` function. All Rust executables start their life at `main`.\n\nThe `main` of our HotDog app looks like this:\n\n````rust@guide_new_app.rs\nuse dioxus::prelude::*;\n\nfn main() {\n dioxus::launch(App);\n}\n````\n\nThe `launch` function calls the platform-specific `launch` function depending on which feature (web/desktop/mobile) is enabled on `dioxus`. `launch` accepts a root component, typically called `App`.\n\nWe'll cover components more in-depth in the [next chapter](component.md).\n\n## Resetting to Basics\n\nThe bare-bones template provides basic starter code for our app. However, we want to start *truly* from scratch, so we'll wipe away the `Hero` component and empty the `App` component to its basics:\n\n````rust@guide_new_app.rs\nuse dioxus::prelude::*;\n\nfn main() {\n dioxus::launch(App);\n}\n\n#[component]\nfn App() -> Element {\n rsx! { \"HotDog!\" }\n}\n````" + } + 51usize => { + "# Extractors\n\nServer functions are an ergonomic way to call a function on the server. Server function work by registering an endpoint on the server and using requests on the client. Most of the time, you shouldn't need to worry about how server functions operate, but there are some times when you need to get some value from the request other than the data passed in the server function.\n\nFor example, requests contain information about the user's browser (called the [user agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent)). We can use an extractor to retrieve that information.\n\nYou can use the `extract` method within a server function to extract something from the request. You can extract any type that implements `FromServerContext` (or when axum is enabled, you can use axum extractors directly):\n\n````rust@server_function_extract.rs\n#[server]\npub async fn log_headers() -> Result<(), ServerFnError> {\n let headers: http::HeaderMap = extract().await?;\n log::info!(\"{:?}\", headers[http::header::USER_AGENT]);\n Ok(())\n}\n````" + } + 84usize => { + "# Contributing\n\nDevelopment 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 (but first check if someone hasn't [done it already](https://github.com/DioxusLabs/dioxus/issues)).\n\n[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.\n\n## Improving Docs\n\nIf you'd like to improve the docs, PRs are welcome! The Rust docs ([source](https://github.com/DioxusLabs/dioxus/tree/main/packages)) and this guide ([source](https://github.com/DioxusLabs/docsite/tree/main/docs-src/0.6)) can be found in their respective GitHub repos.\n\n## Working on the Ecosystem\n\nPart 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 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. Once you are done, add your library to the [awesome dioxus](https://github.com/DioxusLabs/awesome-dioxus) list or share it in the `#I-made-a-thing` channel on [Discord](https://discord.gg/XgGxMSkvUM).\n\n## Bugs & Features\n\nIf you've fixed [an open issue](https://github.com/DioxusLabs/dioxus/issues), feel free to submit a PR! 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!\n\nAll pull requests (including those made by a team member) must be approved by at least one other team member.\nLarger, more nuanced decisions about design, architecture, breaking changes, trade-offs, etc. are made by team consensus.\n\n## Before you contribute\n\nYou might be surprised that a lot of checks fail when making your first PR.\nThat's why you should first run these commands before contributing, and it will save you *lots* of time, because the\nGitHub CI is much slower at executing all of these than your PC.\n\n* Format code with [rustfmt](https://github.com/rust-lang/rustfmt):\n\n````sh\ncargo fmt -- src/**/**.rs\n````\n\n* You might need to install some packages on Linux (Ubuntu/deb) before the following commands will complete successfully (there is also a Nix flake in the repo root):\n\n````sh\nsudo apt install libgdk3.0-cil libatk1.0-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev libsoup-3.0-dev libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev\n````\n\n* Check all code [cargo check](https://doc.rust-lang.org/cargo/commands/cargo-check.html):\n\n````sh\ncargo check --workspace --examples --tests\n````\n\n* Check if [Clippy](https://doc.rust-lang.org/clippy/) generates any warnings. Please fix these!\n\n````sh\ncargo clippy --workspace --examples --tests -- -D warnings\n````\n\n* Test all code with [cargo-test](https://doc.rust-lang.org/cargo/commands/cargo-test.html):\n\n````sh\ncargo test --all --tests\n````\n\n* More tests, this time with [cargo-make](https://sagiegurari.github.io/cargo-make/). Here are all steps, including installation:\n\n````sh\ncargo install --force cargo-make\ncargo make tests\n````\n\n* Test with Playwright. This tests the UI itself, right in a browser. Here are all steps, including installation:\n **Disclaimer: This might inexplicably fail on your machine without it being your fault.** Make that PR anyway!\n\n````sh\ncd playwright-tests\nnpm ci\nnpm install -D @playwright/test\nnpx playwright install --with-deps\nnpx playwright test\n````\n\n## How to test dioxus with local crate\n\nIf you are developing a feature, you should test it in your local setup before raising a PR. This process makes sure you are aware of your code functionality before being reviewed by peers.\n\n* Fork the following github repo (DioxusLabs/dioxus):\n\n`https://github.com/DioxusLabs/dioxus`\n\n* Create a new or use an existing rust crate (ignore this step if you will use an existing rust crate):\n This is where we will be testing the features of the forked\n\n````sh\ncargo new --bin demo\n````\n\n* Add the dioxus dependency to your rust crate (new/existing) in Cargo.toml:\n\n````toml\ndioxus = { path = \"/dioxus/packages/dioxus\", features = [\"web\", \"router\"] }\n````\n\nThis above example is for dioxus-web, with dioxus-router. To know about the dependencies for different renderer visit [here](../getting_started/index.md).\n\n* Run and test your feature\n\n````sh\ndx serve\n````\n\nIf this is your first time with dioxus, please read [the guide](../guide/index.md) to get familiar with dioxus." + } + 50usize => { + "# Communicating with the server\n\n`dioxus-fullstack` provides server functions that allow you to call an automatically generated API on the server from the client as if it were a local function.\n\nTo make a server function, simply add the `#[server(YourUniqueType)]` attribute to a function. The function must:\n\n* Be an async function\n* Have arguments and a return type that both implement serialize and deserialize (with [serde](https://serde.rs/)).\n* Return a `Result` with an error type of ServerFnError\n\n > \n > If you are targeting WASM on the server with WASI, you must call `register` on the type you passed into the server macro in your main function before starting your server to tell Dioxus about the server function. For all other targets, the server function will be registered automatically.\n\nLet's continue building on the app we made in the [getting started](../../getting_started/index.md) guide. We will add a server function to our app that allows us to double the count on the server.\n\nFirst, add serde as a dependency:\n\n````shell\ncargo add serde\n````\n\nNext, add the server function to your `main.rs`:\n\n````rust@server_function.rs\n#![allow(non_snake_case)]\n\nuse dioxus::prelude::*;\n\nfn main() {\n launch(App)\n}\n\nfn App() -> Element {\n let mut count = use_signal(|| 0);\n\n rsx! {\n h1 { \"High-Five counter: {count}\" }\n button { onclick: move |_| count += 1, \"Up high!\" }\n button { onclick: move |_| count -= 1, \"Down low!\" }\n button {\n onclick: move |_| {\n async move {\n if let Ok(new_count) = double_server(count()).await {\n count.set(new_count);\n }\n }\n },\n \"Double\"\n }\n }\n}\n\n#[server]\nasync fn double_server(number: i32) -> Result {\n // Perform some expensive computation or access a database on the server\n tokio::time::sleep(std::time::Duration::from_secs(1)).await;\n let result = number * 2;\n println!(\"server calculated {result}\");\n Ok(result)\n}\n\n````\n\nNow, build your client-side bundle with `dx build --features web` and run your server with `cargo run --features ssr`. You should see a new button that multiplies the count by 2.\n\n## Cached data fetching\n\nOne common use case for server functions is fetching data from the server:\n\n````rust@server_data_fetch.rs\n#![allow(non_snake_case, unused)]\n\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app)\n}\n\nfn app() -> Element {\n let mut count = use_resource(get_server_data);\n\n rsx! {\"server data is {count.value():?}\"}\n}\n\n#[server]\nasync fn get_server_data() -> Result {\n // Access a database\n tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n Ok(\"Hello from the server!\".to_string())\n}\n\n````\n\nIf you navigate to the site above, you will first see `server data is None`, then after the `WASM` has loaded and the request to the server has finished, you will see `server data is Some(Ok(\"Hello from the server!\"))`.\n\nThis approach works, but it can be slow. Instead of waiting for the client to load and send a request to the server, what if we could get all of the data we needed for the page on the server and send it down to the client with the initial HTML page?\n\nThis is exactly what the `use_server_future` hook allows us to do! `use_server_future` is similar to the `use_resource` hook, but it allows you to wait for a future on the server and send the result of the future down to the client.\n\nLet's change our data fetching to use `use_server_future`:\n\n````rust@server_data_prefetch.rs\n#![allow(non_snake_case, unused)]\n\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app);\n}\n\nfn app() -> Element {\n let mut count = use_server_future(get_server_data)?;\n\n rsx! {\"server data is {count.value():?}\"}\n}\n\n#[server]\nasync fn get_server_data() -> Result {\n // Access a database\n tokio::time::sleep(std::time::Duration::from_millis(100)).await;\n Ok(\"Hello from the server!\".to_string())\n}\n\n````\n\n > \n > Notice the `?` after `use_server_future`. This is what tells Dioxus fullstack to wait for the future to resolve before continuing rendering. If you want to not wait for a specific future, you can just remove the ? and deal with the `Option` manually.\n\nNow when you load the page, you should see `server data is Ok(\"Hello from the server!\")`. No need to wait for the `WASM` to load or wait for the request to finish!\n\n````inject-dioxus\nSandBoxFrame {\n\turl: \"https://codesandbox.io/p/sandbox/dioxus-fullstack-server-future-qwpp4p?file=/src/main.rs:3,24\"\n}\n````\n\n## Running the client with dioxus-desktop\n\nThe project presented so far makes a web browser interact with the server, but it is also possible to make a desktop program interact with the server in a similar fashion. (The full example code is available in the [Dioxus repo](https://github.com/DioxusLabs/dioxus/tree/main/packages/fullstack/examples/axum-desktop))\n\nFirst, we need to make two binary targets, one for the desktop program (the `client.rs` file), one for the server (the `server.rs` file). The client app and the server functions are written in a shared `lib.rs` file.\n\nThe desktop and server targets have slightly different build configuration to enable additional dependencies or features.\nThe Cargo.toml in the full example has more information, but the main points are:\n\n* the client.rs has to be run with the `desktop` feature, so that the optional `dioxus-desktop` dependency is included\n* the server.rs has to be run with the `ssr` features; this will generate the server part of the server functions and will run our backend server.\n\nOnce you create your project, you can run the server executable with:\n\n````bash\ncargo run --bin server --features ssr\n````\n\nand the client desktop executable with:\n\n````bash\ncargo run --bin client --features desktop\n````\n\n### Client code\n\nThe client file is pretty straightforward. You only need to set the server url in the client code, so it knows where to send the network requests. Then, dioxus_desktop launches the app.\n\nFor development, the example project runs the server on `localhost:8080`. **Before you release remember to update the url to your production url.**\n\n### Server code\n\nIn the server code, first you have to set the network address and port where the server will listen to.\n\n````rust@server_function_desktop_client.rs\nlet listener = tokio::net::TcpListener::bind(\"127.0.0.1:3000\")\n .await\n .unwrap();\nprintln!(\"listening on http://127.0.0.1:3000\");\n````\n\nThen, you have to register the types declared in the server function macros into the server.\nFor example, consider this server function:\n\n````rust@server_function_desktop_client.rs\n#[server(GetServerData)]\nasync fn get_server_data() -> Result {\n Ok(\"Hello from the server!\".to_string())\n}\n````\n\nFinally, the server is started and it begins responding to requests." + } + 0usize => { + "# Introduction\n\nWelcome to the Dioxus documentation! Dioxus is a framework for building cross-platform apps with the Rust programming language. With one codebase, you can build apps that run on web, desktop, and mobile.\n\nDioxus is designed to be familiar for developers who already know tools like React and Flutter.\n\n````rust@readme.rs\nuse dioxus::prelude::*;\n\npub fn App() -> Element {\n let mut count = use_signal(|| 0);\n\n rsx! {\n h1 { \"High-Five counter: {count}\" }\n button { onclick: move |_| count += 1, \"Up high!\" }\n button { onclick: move |_| count -= 1, \"Down low!\" }\n }\n}\n\n````\n\n````inject-dioxus\nDemoFrame {\n readme::App {}\n}\n````\n\nThis guide is split into different sections:\n\n* [Tutorial](guide/index.md) walks you through your first Dioxus app.\n* [Core Concepts](essentials/index.md) provides detail on topics like managing state.\n* [Guides](reference/index.md) provides references for things like assets, routing, testing, and more.\n\nFirst, try walking through the [Tutorial](guide/index.md) to get familiar with Dioxus. Before embarking on a larger project, we strongly recommend reading the entire [Essential Concepts](essentials/index.md) and glancing through the [Guides Overview](guides/index.md).\n\n > \n > 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.\n\n## What is Dioxus?\n\nDioxus is a developer-friendly framework that empowers developers to ship cross-platform apps with one codebase.\n\n![Multi-platform app architecture diagram](/assets/static/dioxus-architecture-diagram.png)\n\nIn many ways, Dioxus is similar to Flutter: we integrate our own build tools, foster an ecosystem, and provide a markup language for declaring UI. In key areas, Dioxus takes a different approach:\n\n* Apps are declared with HTML and CSS instead of custom styling solution\n* Reactivity is inspired by web frameworks like React and SolidJS\n* Dioxus code runs natively with no virtual machine and enables direct FFI with system APIs\n\nOur goal is to provide a \"better Flutter\": faster, slimmer, and web-native. You can think of Dioxus as a hybrid of [Flutter](http://flutter.dev) and [NextJS](http://nextjs.org): cross-platform apps with stellar fullstack support. Today, Dioxus apps can only be written in Rust, but we plan to support more languages in the future.\n\n## Why Dioxus?\n\nWe started Dioxus because we believe the current standard of building apps is too complex. Developers need to learn and install dozens of different tools just to get their app into the world.\n\n![App stack](/assets/static/dioxus-app-stack.png)\n\nOur vision for Dioxus is a framework that is fast, flexible, and has a minimal learning curve. We want developers to confidently ship their app from idea to production as fast as possible. We believe that fewer tools and a simpler architecture makes it easier to develop apps. Apps that are easier to build also ship faster and are more likely to succeed.\n\n## Syntax and Ecosystem\n\nThe Dioxus syntax is similar to React's JSX markup, borrowing React's component and hooks approach. All components are Rust functions that take `Properties`, define state with hooks, and return an `Element`. We only support markup within the `rsx! {}` macro; this ensures your app is automatically optimized and has stellar devtools support like advanced hot-reloading.\n\n````rust\n#[component]\nfn Component(name: String) -> Element {\n let mut count = use_signal(|| 0);\n\n rsx! {\n h1 { \"Hello, {name}\" }\n p { \"Count: {count}\" }\n }\n}\n````\n\nDioxus is designed to be easy to extend and fairly thin over system APIs. This means you can easily drop into system APIs when first-party APIs are lacking. When targeting the web, this might mean using [`web-sys`](http://crates.io/crates/web-sys/) and on Android using [`jni`](http://crates.io/crates/jni).\n\n````rust\nfn PromptModal() {\n #[cfg(web)]\n web_sys::call_web_function();\n\n #[cfg(android)]\n jni_sys::call_android_function();\n}\n````\n\nThe core Dioxus framework covers a number of utilities that are either challenging to design or integrate the `dx` tooling:\n\n* [App Routing](router/index.md)\n* [Backend integration via server functions](guides/fullstack/server_functions.md)\n* [Including and optimizing](guides/assets.md) assets\n* [State management](essentials/state/index.md) (signals-based reactivity)\n* [SDK](http://github.com/dioxusLabs/sdk): 1st-party System integrations\n\n## Stability\n\nDioxus has not reached a \"1.0\" release yet.\n\nWe are currently on version 0.6, which has stabilized a huge number of APIs and drastically improved the developer experience. In version 0.5 we overhauled the state management system and in 0.6 we overhauled tooling.\n\nIt's likely that the next few versions of Dioxus (0.7, 0.8) will bring breaking changes to your apps. Fortunately, these planned changes will only affect the syntax of specific APIs and not your apps at large. With every version update, we ship a rather comprehensive migration guide - eg [0.6](migration/index.md).\n\n## Examples, projects, tutorials, and more\n\nThe Dioxus ecosystem is growing and so are the number of examples, projects, tutorials, books, and other learning resources.\n\nWe highly recommend a few first-party sources:\n\n* The [official folder of small examples](https://github.com/DioxusLabs/dioxus/tree/main/examples)\n* The [official repository of example projects](https://github.com/DioxusLabs/dioxus/tree/main/example-projects)\n* The official [YouTube channel](https://www.youtube.com/@DioxusLabs)\n\n## Who's funding Dioxus?\n\nDioxus is funded by a mix of corporate sponsorships, enterprise support contracts, [crowd-sourced funding](https://github.com/sponsors/DioxusLabs#sponsors), and [venture capital](http://ycombinator.com/companies/dioxus-labs). We strive to maintain a healthy mix of funding to balance the various competing visions of the future. We want to provide a \"Flutter but better\" for everyone - not controlled by Apple, Meta, or Google - and we need to make sure Dioxus has a sustainable long-term financial future.\n\nUltimately, we'd like Dioxus to be self-sustaining. This means that you'll eventually have the option to deploy your production apps with [Dioxus Deploy](https://dioxuslabs.com/deploy). Revenue from *Dioxus Deploy* will in turn fund development on Dioxus itself.\n\nWe're committed to keeping Dioxus free and open source forever. You'll never need to pay us to build apps nor will we ever change the license of Dioxus." + } + 85usize => { + "# Project Structure\n\nThere are many packages in the Dioxus organization. This document will help you understand the purpose of each package and how they fit together:\n\n![Dioxus Dependency Graph](/assets/static/workspace-graph.png)\n\n## Entry Points\n\n* [dioxus](https://github.com/DioxusLabs/dioxus/tree/main/packages/dioxus): The main crate for Dioxus applications. The dioxus crate has different feature flags to enable a specific [renderer](#renderers) with the launch API and expose different features like the router and [fullstack](#fullstack). The [CLI](https://github.com/DioxusLabs/dioxus/tree/main/packages/cli) uses the renderer feature flag that is enabled to determine what rust target to compile for.\n* [dioxus-lib](https://github.com/DioxusLabs/dioxus/tree/main/packages/dioxus-lib): Dioxus lib is a re-export of the dioxus crate without any renderer features. This crate is recommended for libraries because it is impossible to pull in a renderer feature accidentally which would enable that renderer for any downstream crates.\n\n## Renderers\n\nRenderers are the entry point for Dioxus applications. They handle rendering the application, polling async tasks, and handling events. Each renderer depends on `dioxus-core` for the core virtual dom and implements both the history trait from `dioxus-history` and the event conversion trait from `dioxus-html`. Dioxus has four renderers in the main repository:\n\n* [desktop](https://github.com/DioxusLabs/dioxus/tree/main/packages/desktop): A Render that Runs Dioxus applications natively, but renders them with the system webview\n* [mobile](https://github.com/DioxusLabs/dioxus/tree/main/packages/mobile): A Render that Runs Dioxus applications natively, but renders them with the system webview. This is currently a think wrapper on top of the desktop renderer since both renderers use the webview\n* [web](https://github.com/DioxusLabs/dioxus/tree/main/packages/web): Renders Dioxus applications in the browser by compiling to WASM and manipulating the DOM. The web renderer has a hydration feature to take over rendering from the server if [fullstack](#fullstack) is enabled\n* [liveview](https://github.com/DioxusLabs/dioxus/tree/main/packages/liveview): A Render that Runs on the server, and renders using a websocket proxy in the browser. The liveview renderer is currently supported, but development has been deprioritized in favor of fullstack and it may be removed in the future\n\n > \n > The [TUI](https://github.com/DioxusLabs/blitz/tree/legacy/packages/dioxus-tui) renderer has been deprecated but may be revisited in the future once the new version of Blitz is more stable\n\n## Experimental Native Rendering\n\nIn addition to the renderers listed above, Dioxus also has an experimental native renderer called Blitz that uses WebGPU to render HTML+CSS for dioxus applications:\n\n* [taffy](https://github.com/DioxusLabs/taffy): Layout engine powering Blitz-Core, Plasmo, and Bevy UI\n* [blitz](https://github.com/DioxusLabs/blitz): An experimental native renderer for Dioxus applications using WGPU\n\n## Fullstack\n\nFullstack can be layered on top of any renderer to add support for server functions and server-side rendering.\n\n* [ssr](https://github.com/DioxusLabs/dioxus/tree/main/packages/ssr): dioxus-ssr handles rendering a dioxus virtual dom to a string for testing or on the server. SSR is used in the fullstack renderer to handle server side rendering and static generation.\n* [isrg](https://github.com/DioxusLabs/dioxus/tree/main/packages/isrg): dioxus-isrg handles incremental static site generation for dioxus fullstack applications. It helps fullstack cache server side rendered routes in memory and on the file system.\n* [fullstack](https://github.com/DioxusLabs/dioxus/tree/main/packages/fullstack): dioxus-fullstack package handles the integration between a [axum](https://github.com/tokio-rs/axum) server and a dioxus renderer. If the frontend renderer is targeting the web, the fullstack renderer will prepare html with embedded data so the client can take over rendering after the initial load (hydration)\n* [server-macro](https://github.com/DioxusLabs/dioxus/tree/main/packages/server-macro): The server-macro crate defines the `server` macro used to define server functions in Dioxus applications. It integrates with the [server_fn](https://crates.io/crates/server_fn) to automatically register the server functions on the server and call them on the client.\n\n## Core utilities\n\nThe core utilities contain the implementation of the virtual dom, and other macros used in all dioxus renderers. The core of dioxus doesn't assume it is running in a web context, so these utilities can be used by third party renderers like [Freya](https://github.com/marc2332/freya).\n\n* [core](https://github.com/DioxusLabs/dioxus/tree/main/packages/core): The core virtual dom implementation every Dioxus application uses. The main entry point for core is the `VirtualDom`. The virtual dom diffing methods accept a cross platform `WriteMutations` trait that is called any time the renderer need to change what is rendered. The vdom also has methods for running futures, and inserting events. You can read more about the architecture of the core [in this blog post](https://dioxuslabs.com/blog/templates-diffing/)\n* [core-types](https://github.com/DioxusLabs/dioxus/tree/main/packages/core-types): The core types crate contains some of the core functions used in both in dioxus core and the hot reloading engine.\n* [core-macro](https://github.com/DioxusLabs/dioxus/tree/main/packages/core-macro): The core macro crate implement the `derive(Props)` and `#[component]` macros to derive builds for components. It also re-exports the rsx macro\n* [rsx](https://github.com/DioxusLabs/dioxus/tree/main/packages/rsx): Implements parsing and expansion for the RSX macro. The parser is also used for hot reloading, and autoformatting in the CLI\n\n## Web utilities\n\nEvery first party dioxus renderer targets html and css. With the exception of the blitz, all renderers run inside the browser context. Dioxus has a few utilities in the workspace with shared traits and javascript bindings to help interact with the browser:\n\n* [interpreter](https://github.com/DioxusLabs/dioxus/tree/main/packages/interpreter): The interpreter implements the `WriteMutations` trait from dioxus core to modify the DOM with the diffs the virtual dom generates. The interpreter is used by the desktop, web and liveview renderers. It uses a combination of [`wasm-bindgen`](https://rustwasm.github.io/wasm-bindgen) and [`sledgehammer-bindgen`](https://github.com/ealmloff/sledgehammer_bindgen) to interact with the browser\n* [html](https://github.com/DioxusLabs/dioxus/tree/main/packages/html): defines html specific elements, events, and attributes. The elements and attributes are used in the rsx macro and hot reloading engine to map the rust identifiers to the html names. The events defined in the html crate are traits defined for each platform.\n* [html-internal-macro](https://github.com/DioxusLabs/dioxus/tree/main/packages/html-internal-macro): The html-internal-macro crate is used by the html crate to define the html elements and attributes.\n* [lazy-js-bundle](https://github.com/DioxusLabs/dioxus/tree/main/packages/lazy-js-bundle): A library to bundle typescript files at build time with bun only if the contents change. Only compiling the typescript when the files change and committing the build output lets us not require a ts compiler to be installed when dioxus is added as a library.\n* [history](https://github.com/DioxusLabs/dioxus/tree/main/packages/history): The dioxus-history crate defines the history trait backing each renderer must provide for use with the router. For web renderers, this should call the javascript history api. Native renderers maintain their own history stack in memory.\n* [document](https://github.com/DioxusLabs/dioxus/tree/main/packages/document): The dioxus-document crate defines the document trait backing each renderer must provide for use with `eval` and the `document::*` components. `eval` runs javascript code from rust, and the `document::*` components create html elements in the head.\n\n## State Management\n\n* [generational-box](https://github.com/DioxusLabs/dioxus/tree/main/packages/generational-box): Generational Box is the core of all `Copy` state management in Dioxus. It allocates an arena of dynamically borrow checked values used throughout the dioxus ecosystem. The `GenerationalBox` type backs `Signal`, `Memo`, and `Resource` in dioxus signals. It is also used in `dioxus-core` to make the `Closure` and `EventHandler` types `Copy`.\n* [signals](https://github.com/DioxusLabs/dioxus/tree/main/packages/signals): Signals are the main user facing state management crate for Dioxus. Signals track when they are read and written to and automatically re-run any `ReactiveContext`s that depends on the signal.\n* [hooks](https://github.com/DioxusLabs/dioxus/tree/main/packages/hooks): Hooks are a collection of common hooks for Dioxus applications. Most hooks are a thin wrapper over the new methods in the `signals` crate to only create the object once when the component is created.\n\n## Logging\n\n* [logger](https://github.com/DioxusLabs/dioxus/tree/main/packages/logger): The logger crate provides a simple logging interface for Dioxus applications that works across native and wasm targets. It is automatically called in the launch function if the logging feature is enabled.\n\n## Routing\n\n* [router](https://github.com/DioxusLabs/dioxus/tree/main/packages/router): The router crate handles routing in Dioxus applications. It uses the history provider the renderer provides to get and modify the url. The route parsing logic is derived with the `derive(Routable)` macro defined in the dioxus-router-macro crate.\n* [router-macro](https://github.com/DioxusLabs/dioxus/tree/main/packages/router-macro): The router-macro crate defines the `derive(Routable)` macro used to the route enum from a url and display it as a url.\n\n## Assets\n\n* [manganis](https://github.com/DioxusLabs/dioxus/tree/main/packages/manganis/manganis): Manganis is dioxus' asset system. It uses a macro to inject assets from rust code into the linker. Every asset gets a unique hash for cache busting. The CLI pulls the asset out of the linker and bundled them into the final application.\n* [manganis-macro](https://github.com/DioxusLabs/dioxus/tree/main/packages/manganis/manganis-macro): Manganis-macro defines the `asset!()` macro used to include assets in Dioxus applications.\n* [manganis-core](https://github.com/DioxusLabs/dioxus/tree/main/packages/manganis/manganis-core): Manganis-core contains the builders for all options passed into the `asset!()` macro and the link sections the asset macro and CLI use to bundle assets.\n* [const-serialize](https://github.com/DioxusLabs/dioxus/tree/main/packages/const-serialize): Const Serialize defines a trait to serialize rust types to a cross platform format at compile time. This is used to serialize the options for assets at compile time in manganis.\n* [const-serialize-macro](https://github.com/DioxusLabs/dioxus/tree/main/packages/const-serialize-macro): Const Serialize Macro defines a derive macro for types that can be serialized at compile time with the `const-serialize` crate.\n* [cli-opt](https://github.com/DioxusLabs/dioxus/tree/main/packages/cli-opt): The cli-opt optimizes the assets that manganis produces.\n\n## Formatting\n\n* [autofmt](https://github.com/DioxusLabs/dioxus/tree/main/packages/autofmt): The autofmt crate finds and formats all rsx macros in a rust project. It uses the `dioxus-rsx` crate to parse rsx.\n\n## Linting\n\n* [check](https://github.com/DioxusLabs/dioxus/tree/main/packages/check): The dioxus-check crate analyzes dioxus code to check for common errors like calling hooks in conditionals or loops.\n\n## Translation\n\n* [rsx-rosetta](https://github.com/DioxusLabs/dioxus/tree/main/packages/rsx-rosetta): The rsx-rosetta crate translates html to rsx. It uses the element definitions from `dioxus-html` to translate html elements and attributes to their rust names and the `rsx` crate to generate the rsx macro.\n\n## Hot Reloading\n\n* [rsx-hotreload](https://github.com/DioxusLabs/dioxus/tree/main/packages/rsx-hotreload): The rsx-hotreload crate handles diffing rsx macros between builds and creating the hot reload templates for the CLI.\n* [devtools](https://github.com/DioxusLabs/dioxus/tree/main/packages/devtools): The devtools crate contains the frontend for hot reloading each renderer needs to integrate with. It receives hot reload messages from a websocket connection with the CLI\n* [devtools-types](https://github.com/DioxusLabs/dioxus/tree/main/packages/devtools-types): The devtools-types crate contains the types used to communicate between the devtools frontend and the backend in the CLI.\n\n## CLI\n\n* [cli](https://github.com/DioxusLabs/dioxus/tree/main/packages/cli): The cli crate contains the dioxus CLI. It integrates check, autofmt, cli-opt, and rsx-hotreload to build and serve Dioxus applications.\n* [cli-config](https://github.com/DioxusLabs/dioxus/tree/main/packages/cli-config): The cli-config crate has shared types that are provided at runtime from the CLI to crates the CLI are built with. It is used by `dioxus-desktop` to set the title from the `Dioxus.toml` file and by `dioxus-fullstack` to set the port the CLI proxies the server from.\n* [dx-wire-format](https://github.com/DioxusLabs/dioxus/tree/main/packages/dx-wire-format): The dx-wire-format crate has the unstable types the CLI emits in json mode. This is used by the dioxus playground.\n\n## Extension\n\n* [extension](https://github.com/DioxusLabs/dioxus/tree/main/packages/extension): The extension folder contains the source code for the dioxus VSCode extension. It uses many of the same crates as the CLI, but packaged into a wasm+JS bundle for VSCode.\n\n## Testing\n\n* [playwright-tests](https://github.com/DioxusLabs/dioxus/tree/main/packages/playwright-tests): The playwright-tests folder contains end to end tests for dioxus-web, dioxus-liveview and fullstack. These crates are not published on crates.io" + } + 12usize => { + "# Adding More Routes\n\nSo far, our app has only had a single page. Let's change that!\n\nIn this chapter, we'll be adding a Navbar, a welcome screen, and a \"favorites\" page where we can revisit our favorite dogs.\n\n## Organizing our Project\n\nBefore we get too far with adding new pages to our app, let's organize our codebase a bit better. For larger projects you might want to break your app into different smaller crates. For HotDog, we'll keep it simple.\n\n > \n > The `dx new` Jumpstart and Workspace templates provide great scaffolding for new apps!\n\nWe generally recommend splitting your components, models, and backend functionality into different files. For HotDog, we're going to use a simple directory structure:\n\n````sh\n├── Cargo.toml\n├── assets\n│ └── main.css\n└── src\n ├── backend.rs\n ├── components\n │ ├── favorites.rs\n │ ├── mod.rs\n │ ├── nav.rs\n │ └── view.rs\n └── main.rs\n````\n\nWe'll have a `backend.rs` that contains our server functions and a `components` folder that contains our components. We don't have a `NavBar` or a `Favorites` component yet, but we'll still create the relevant files before adding them. By splitting out our server functions into a `backend.rs` file, we'll make it easier to extract our backend functionality as a shared library for different apps in the future.\n\nOur `components/mod.rs` file will simply import and re-export the components in `view.rs`, `nav.rs`, and `favorites.rs`:\n\n````rust\nmod favorites;\nmod nav;\nmod view;\n\npub use favorites::*;\npub use nav::*;\npub use view::*;\n````\n\nFinally, we need to bring `backend` and `components` into scope in our `main.rs` file:\n\n````rust\nmod components;\nmod backend;\n\nuse crate::components::*;\n````\n\nFor more information on organizing Rust projects with modules, see the [Modules section](https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html) of the Rust Book.\n\n## Creating a Route\n\nMost Dioxus apps you'll build will have different screens. This could include pages like *Login*, *Settings*, and *Profile*. Our HotDog app will have two screens: a *DogView* page and a *Favorites* page.\n\nDioxus provides a first-party router that natively integrates with web, desktop, and mobile. For example, on web, whenever you visit the `/favorites` url in your browser, the corresponding *Favorites* page will load. The Dioxus router is very powerful, and most importantly, type-safe. You can rest easy knowing that users will never be sent to an invalid route. To achieve this, we first need to add the \"Router\" feature to the Cargo.toml file:\n\n````toml\n[dependencies]\ndioxus = { version = \"0.6.0\", features = [\"fullstack\", \"router\"] } # <----- add \"router\"\n````\n\nNext, the Dioxus router is defined as an enum with the `Routable` derive attribute:\n\n````rust@guide_router.rs\n#[derive(Routable, Clone, PartialEq)]\nenum Route {\n #[route(\"/\")]\n DogView,\n}\n````\n\nWith the Dioxus router, every route is an enum variant with a `#[route]` attribute that specifics the route's URL. Whenever the router renders our route, the component of the same name will be rendered.\n\n````rust@guide_router.rs\nuse dioxus::prelude::*;\n\n#[derive(Routable, Clone, PartialEq)]\nenum Route {\n #[route(\"/\")]\n DogView, // <---- a DogView component must be in scope\n}\n\nfn DogView() -> Element {\n todo!()\n}\n````\n\n## Rendering the Route\n\nNow that we have our app's `Route` defined, we need to render it. Let's change our `app` component to render the `Route {}` component instead of the `DogView`.\n\n````rust@guide_router.rs\nfn app() -> Element {\n rsx! {\n document::Stylesheet { href: asset!(\"/assets/main.css\") }\n\n // 📣 delete Title and DogView and replace it with the Router component.\n Router:: {}\n }\n}\n````\n\nWhen the `Router {}` component renders, it will parse the document's current URL into a `Route` variant. If the url doesn't parse properly, the router will render nothing unless you add a \"catch-all\" route:\n\n````rust@guide_router.rs\n#[derive(Routable, Clone, PartialEq)]\nenum Route {\n // ...\n // We can collect the segments of the URL into a Vec\n #[route(\"/:..segments\")]\n PageNotFound { segments: Vec },\n}\n````\n\nNote here that the `PageNotFound` route takes the \"segments\" parameter. Dioxus routes are not only type-safe as variants, but also type-safe with URL parameters. For more information on how this works, [check the router guide](../router/index.md).\n\nAt this point, we should see our app, but this time without its Title.\n\n![No Navbar](/assets/06_docs/no_navbar.png)\n\n## Rendering the NavBar with a Layout\n\nWe're rendering our DogView component, but unfortunately we no longer see our Title. Let's add that back and turn it into a NavBar!\n\nIn our `src/components/nav.rs` file, we'll add back our Title code, but rename it to NavBar and modify it with two new items: the `Link {}` and `Outlet` components.\n\n````rust@guide_router.rs\nuse crate::Route;\nuse dioxus::prelude::*;\n\n#[component]\npub fn NavBar() -> Element {\n rsx! {\n div { id: \"title\",\n Link { to: Route::DogView,\n h1 { \"🌭 HotDog! \" }\n }\n }\n Outlet:: {}\n }\n}\n````\n\nThe `Link {}` component wraps the anchor `
` element with a type-safe interface. This means any struct that implements `Routable` - anything that can `.to_string()` - is a valid navigation target.\n\n````rust@guide_router.rs\n// Using the Link with Route\nLink { to: Route::DogView }\n\n// Or passing in a \"/\" route directly\nLink { to: \"/\" }\n````\n\nThe Link component takes many different arguments, making it possible to extend and customize for your use-case.\n\nIn `NavBar`, we also added an `Outlet:: {}` component. When the Router component renders, it first looks for any child `Outlet` components. If one is present, it renders the current route *under the outlet*. This lets us wrap the current page in extra elements - in this case, the NavBar. If no Outlet is present, then the current route is simply rendered where the `Router {}` is declared.\n\nTo actually add the NavBar component to our app, we need to update our `Route` enum with the `#[layout]` attribute. This forces the router to render the `NavBar` component *first* so it can expose its `Outlet {}`.\n\n````rust@guide_router.rs\n#[derive(Routable, PartialEq, Clone)]\nenum Route {\n #[layout(NavBar)] // <---- add the #[layout] attribute\n #[route(\"/\")]\n DogView,\n}\n````\n\nThe `layout` attribute instructs the Router to wrap the following enum variants in the given component.\n\n````rust, ignore\nRouter {\n NavBar {\n Outlet {\n if route == “/” {\n DogView {}\n }\n }\n }\n}\n````\n\nVisually, this should be straight-forward to understand. Note that the Router and Outlet share the same `Route` generic type.\n\n![RouterLayout](/assets/06_docs/routeroutlet.png)\n\n## Adding a Favorites Route\n\nNow that we understand the fundamentals of routing, let's finally add our *Favorites* page so we can view the dog photos we saved.\n\nWe'll start by creating an empty component `src/components/favorites.rs`:\n\n````rust@guide_router.rs\nuse dioxus::prelude::*;\n\n#[component]\npub fn Favorites() -> Element {\n rsx! { \"favorites!\" }\n}\n````\n\nAnd then let's make sure to add a new variant in our `Route` enum:\n\n````rust@guide_router.rs\n#[derive(Routable, PartialEq, Clone)]\nenum Route {\n #[layout(NavBar)]\n #[route(\"/\")]\n DogView,\n\n #[route(\"/favorites\")]\n Favorites, // <------ add this new variant\n}\n````\n\nTo make sure the user can reach this page, let's also add a button in the nav that points to it.\n\n````rust@guide_router.rs\nuse crate::Route;\nuse dioxus::prelude::*;\n\n#[component]\npub fn NavBar() -> Element {\n rsx! {\n div { id: \"title\",\n Link { to: Route::DogView,\n h1 { \"🌭 HotDog! \" }\n }\n Link { to: Route::Favorites, id: \"heart\", \"♥\u{fe0f}\" } // <------- add this Link\n }\n Outlet:: {}\n }\n}\n````\n\n## Our Favorites Page\n\nFinally, we can build our favorites page. Let's add a new `list_dogs` server function that fetches the 10 most recently saved dog photos:\n\n````rust@guide_router.rs\n// Query the database and return the last 10 dogs and their url\n#[server]\npub async fn list_dogs() -> Result, ServerFnError> {\n let dogs = DB.with(|f| {\n f.prepare(\"SELECT id, url FROM dogs ORDER BY id DESC LIMIT 10\")\n .unwrap()\n .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))\n .unwrap()\n .map(|r| r.unwrap())\n .collect()\n });\n\n Ok(dogs)\n}\n````\n\nNow, we can fill in our component. We're going to use the same `use_resource` hook from earlier. Resolving the request from the server might take some time, so we'll use the `.suspend()?` method on `Resource` to wait for the request to finish before mapping the contents to a list.\n\n````rust@guide_router.rs\nuse dioxus::prelude::*;\n\n#[component]\npub fn Favorites() -> Element {\n // Create a pending resource that resolves to the list of dogs from the backend\n // Wait for the favorites list to resolve with `.suspend()`\n let mut favorites = use_resource(super::backend::list_dogs).suspend()?;\n\n rsx! {\n div { id: \"favorites\",\n div { id: \"favorites-container\",\n for (id, url) in favorites().unwrap() {\n // Render a div for each photo using the dog's ID as the list key\n div {\n key: id,\n class: \"favorite-dog\",\n img { src: \"{url}\" }\n }\n }\n }\n }\n }\n}\n````\n\nAs a stretch goal, try adding a button that lets the user also delete items from the database.\n\n![FullDemo](/assets/06_docs/hotdogfull.mp4)" + } + 75usize => { + "# Event Handlers\n\nEvent handlers are used to respond to user actions. For example, an event handler could be triggered when the user clicks, scrolls, moves the mouse, or types a character.\n\nEvent handlers are attached to elements. For example, we usually don't care about all the clicks that happen within an app, only those on a particular button.\n\nEvent 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 it listens for is triggered and will be passed that event.\n\nFor example, to handle clicks on an element, we can specify an `onclick` handler:\n\n````rust, no_run@event_click.rs\nrsx! {\n button { onclick: move |event| log::info!(\"Clicked! Event: {event:?}\"), \"click me!\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n event_click::App {}\n}\n````\n\n## The Event object\n\nEvent handlers receive an [`Event`](https://docs.rs/dioxus-core/latest/dioxus_core/struct.Event.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.\n\nIn the example above, this event data was logged to the terminal:\n\n````\nClicked! Event: UiEvent { bubble_state: Cell { value: true }, data: MouseData { coordinates: Coordinates { screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } }\nClicked! Event: UiEvent { bubble_state: Cell { value: true }, data: MouseData { coordinates: Coordinates { screen: (242.0, 256.0), client: (26.0, 17.0), element: (16.0, 7.0), page: (26.0, 17.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } }\n````\n\nTo learn what the different event types for HTML provide, read the [events module docs](https://docs.rs/dioxus-html/latest/dioxus_html/events/index.html).\n\n### Event propagation\n\nSome events will trigger first on the element the event originated at upward. For example, a click event on a `button` inside a `div` would first trigger the button's event listener and then the div's event listener.\n\n > \n > For more information about event propagation see [the mdn docs on event bubbling](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling)\n\nIf you want to prevent this behavior, you can call `stop_propagation()` on the event:\n\n````rust, no_run@event_nested.rs\nrsx! {\n div { onclick: move |_event| {},\n \"outer\"\n button {\n onclick: move |event| {\n event.stop_propagation();\n },\n \"inner\"\n }\n }\n}\n````\n\n## Prevent Default\n\nSome events have a default behavior. For keyboard events, this might be entering the typed character. For mouse events, this might be selecting some text.\n\nIn 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 can be used for multiple handlers using their name separated by spaces:\n\n````rust, no_run@event_prevent_default.rs\nrsx! {\n a {\n href: \"https://example.com\",\n onclick: |evt| {\n evt.prevent_default();\n log::info!(\"link clicked\")\n },\n \"example.com\"\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n event_prevent_default::App {}\n}\n````\n\nAny event handlers will still be called.\n\n## Handler Props\n\nSometimes, you might want to make a component that accepts an event handler. A simple example would be a `FancyButton` component, which accepts an `onclick` handler:\n\n````rust, no_run@event_handler_prop.rs\n#[derive(PartialEq, Clone, Props)]\npub struct FancyButtonProps {\n onclick: EventHandler,\n}\n\npub fn FancyButton(props: FancyButtonProps) -> Element {\n rsx! {\n button {\n class: \"fancy-button\",\n onclick: move |evt| props.onclick.call(evt),\n \"click me pls.\"\n }\n }\n}\n````\n\nThen, you can use it like any other handler:\n\n````rust, no_run@event_handler_prop.rs\nrsx! {\n FancyButton {\n onclick: move |event| println!(\"Clicked! {event:?}\"),\n }\n}\n````\n\n > \n > Note: just like any other attribute, you can name the handlers anything you want! Any closure you pass in will automatically be turned into an `EventHandler`.\n\n## Custom Data\n\nEvent Handlers are generic over any type, so you can pass in any data you want to them, e.g:\n\n````rust, no_run@event_handler_prop.rs\nstruct ComplexData(i32);\n\n#[derive(PartialEq, Clone, Props)]\npub struct CustomFancyButtonProps {\n onclick: EventHandler,\n}\n\npub fn CustomFancyButton(props: CustomFancyButtonProps) -> Element {\n rsx! {\n button {\n class: \"fancy-button\",\n onclick: move |_| props.onclick.call(ComplexData(0)),\n \"click me pls.\"\n }\n }\n}\n````\n\n## Returning a value from an event handler\n\nIf you want to accept a closure like an event handler that returns a value, you can use the `Callback` type. The callback type accepts two generic arguments, `I`, the input type, and `O`, the output type. Just like `EventHandler`, `Callback` is automatically converted in props and can be easily copied into anywhere in your component:\n\n````rust, no_run@event_handler_prop.rs\n#[derive(PartialEq, Clone, Props)]\npub struct CounterProps {\n modify: Callback,\n}\n\npub fn Counter(props: CounterProps) -> Element {\n let mut count = use_signal(|| 1);\n\n rsx! {\n button {\n onclick: move |_| count.set(props.modify.call(count())),\n \"double\"\n }\n div { \"count: {count}\" }\n }\n}\n````" + } + 28usize => { + "# Building a Nest\n\nIn this chapter, we will begin to build the blog portion of our site which will\ninclude links, nested routes, and route parameters.\n\n## Site Navigation\n\nOur site visitors won't know all the available pages and blogs on our site so we\nshould provide a navigation bar for them. Our navbar will be a list of links going between our pages.\n\nWe want our navbar component to be rendered on several different pages on our site. Instead of duplicating the code, we can create a component that wraps all children routes. This is called a layout component. To tell the router where to render the child routes, we use the [`Outlet`](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html) component.\n\nLet's create a new `NavBar` component:\n\n````rust@nested_routes.rs\n#[component]\nfn NavBar() -> Element {\n rsx! {\n nav {\n ul { li { \"links\" } }\n }\n // The Outlet component will render child routes (In this case just the Home component) inside the Outlet component\n Outlet:: {}\n }\n}\n````\n\nNext, let's add our `NavBar` component as a layout to our Route enum:\n\n````rust@nested_routes.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n // All routes under the NavBar layout will be rendered inside of the NavBar Outlet\n #[layout(NavBar)]\n #[route(\"/\")]\n Home {},\n #[end_layout]\n #[route(\"/:..route\")]\n PageNotFound { route: Vec },\n}\n````\n\nTo add links to our `NavBar`, we could always use an HTML anchor element but that has two issues:\n\n1. It causes a full-page reload\n1. We can accidentally link to a page that doesn't exist\n\nInstead, we want to use the [`Link`] component provided by Dioxus Router.\n\nThe [`Link`] is similar to a regular `` tag. It takes a target and children.\n\nUnlike a regular `` tag, we can pass in our Route enum as the target. Because we annotated our routes with the `#[route(path)]` attribute, the [`Link`] will know how to generate the correct URL. If we use the Route enum, the rust compiler will prevent us from linking to a page that doesn't exist.\n\nLet's add our links:\n\n````rust@links.rs\n#[component]\nfn NavBar() -> Element {\n rsx! {\n nav {\n ul {\n li {\n Link { to: Route::Home {}, \"Home\" }\n }\n }\n }\n Outlet:: {}\n }\n}\n````\n\n > \n > Using this method, the [`Link`] component only works for links within our\n > application. To learn more about navigation targets see\n > [here](./navigation-targets.md).\n\nNow you should see a list of links near the top of your page. Click on one and\nyou should seamlessly travel between pages.\n\n## URL Parameters and Nested Routes\n\nMany websites such as GitHub put parameters in their URL. For example,\n`https://github.com/DioxusLabs` utilizes the text after the domain to\ndynamically search and display content about an organization.\n\nWe want to store our blogs in a database and load them as needed. We also\nwant our users to be able to send people a link to a specific blog post.\nInstead of listing all of the blog titles at compile time, we can make a dynamic route.\n\nWe could utilize a search page that loads a blog when clicked but then our users\nwon't be able to share our blogs easily. This is where URL parameters come in.\n\nThe path to our blog will look like `/blog/myBlogPage`, `myBlogPage` being the\nURL parameter.\n\nFirst, let's create a layout component (similar to the navbar) that wraps the blog content. This allows us to add a heading that tells the user they are on the blog.\n\n````rust@dynamic_route.rs\n#[component]\nfn Blog() -> Element {\n rsx! {\n h1 { \"Blog\" }\n Outlet:: {}\n }\n}\n````\n\nNow we'll create another index component, that'll be displayed when no blog post\nis selected:\n\n````rust@dynamic_route.rs\n#[component]\nfn BlogList() -> Element {\n rsx! {\n h2 { \"Choose a post\" }\n ul {\n li {\n Link {\n to: Route::BlogPost {\n name: \"Blog post 1\".into(),\n },\n \"Read the first blog post\"\n }\n }\n li {\n Link {\n to: Route::BlogPost {\n name: \"Blog post 2\".into(),\n },\n \"Read the second blog post\"\n }\n }\n }\n }\n}\n````\n\nWe also need to create a component that displays an actual blog post. This component will accept the URL parameters as props:\n\n````rust@dynamic_route.rs\n// The name prop comes from the /:name route segment\n#[component]\nfn BlogPost(name: String) -> Element {\n rsx! { h2 { \"Blog Post: {name}\" } }\n}\n````\n\nFinally, let's tell our router about those components:\n\n````rust@dynamic_route.rs\n#[derive(Routable, Clone)]\n#[rustfmt::skip]\nenum Route {\n #[layout(NavBar)]\n #[route(\"/\")]\n Home {},\n #[nest(\"/blog\")]\n #[layout(Blog)]\n #[route(\"/\")]\n BlogList {},\n #[route(\"/post/:name\")]\n BlogPost { name: String },\n #[end_layout]\n #[end_nest]\n #[end_layout]\n #[route(\"/:..route\")]\n PageNotFound {\n route: Vec,\n },\n}\n````\n\nThat's it! If you head to `/blog/1` you should see our sample post.\n\n## Conclusion\n\nIn this chapter, we utilized Dioxus Router's Link, and Route Parameter\nfunctionality to build the blog portion of our application. In the next chapter,\nwe will go over how navigation targets (like the one we passed to our links)\nwork." + } + 29usize => { + "# Navigation Targets\n\nIn the previous chapter, we learned how to create links to pages within our app.\nWe told them where to go using the `target` property. This property takes something that can be converted to a [`NavigationTarget`].\n\n## What is a navigation target?\n\nA [`NavigationTarget`] is similar to the `href` of an HTML anchor element. It\ntells the router where to navigate to. The Dioxus Router knows two kinds of\nnavigation targets:\n\n* [`Internal`]: We used internal links in the previous chapter. It's a link to a page within our\n app represented as a Route enum.\n* [`External`]: This works exactly like an HTML anchors' `href`. Don't use this for in-app\n navigation as it will trigger a page reload by the browser.\n\n## External navigation\n\nIf we need a link to an external page we can do it like this:\n\n````rust@external_link.rs\nfn GoToDioxus() -> Element {\n rsx! {\n Link { to: \"https://dioxuslabs.com\", \"ExternalTarget target\" }\n }\n}\n````" + } + 45usize => { + "# Mobile\n\nThis guide will cover concepts specific to the Dioxus mobile renderer.\n\n## Running Javascript\n\nDioxus provides some ergonomic wrappers over the browser API, but in some cases you may need to access parts of the browser API Dioxus does not expose.\n\nFor these cases, Dioxus desktop exposes the use_eval hook that allows you to run raw Javascript in the webview:\n\n````rust@eval.rs\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app);\n}\n\nfn app() -> Element {\n let future = use_resource(move || async move {\n // You can create as many eval instances as you want\n let mut eval = document::eval(\n r#\"\n // You can send messages from JavaScript to Rust with the dioxus.send function\n dioxus.send(\"Hi from JS!\");\n // You can receive messages from Rust to JavaScript with the dioxus.recv function\n let msg = await dioxus.recv();\n console.log(msg);\n \"#,\n );\n\n // You can send messages to JavaScript with the send method\n eval.send(\"Hi from Rust!\").unwrap();\n\n // You can receive any message from JavaScript with the recv method\n eval.recv::().await.unwrap()\n });\n\n match future.read_unchecked().as_ref() {\n Some(v) => rsx! {\n p { \"{v}\" }\n },\n _ => rsx! {\n p { \"hello\" }\n },\n }\n}\n\n````\n\n## Custom Assets\n\nYou can link to local assets in dioxus mobile instead of using a url:\n\n````rust@custom_assets.rs\nuse dioxus::prelude::*;\n\nfn main() {\n launch(app);\n}\n\nfn app() -> Element {\n rsx! {\n div {\n img { src: asset!(\"/assets/static/scanner.png\") }\n }\n }\n}\n\n````\n\n## Integrating with Wry\n\nIn cases where you need more low level control over your window, you can use wry APIs exposed through the [Desktop Config](https://docs.rs/dioxus-desktop/0.6.0/dioxus_desktop/type.DesktopContext.html) and the [use_window hook](https://docs.rs/dioxus-desktop/0.6.0/dioxus_desktop/fn.use_window.html)" + } + 87usize => { + "# Introduction\n\nThe ✨**Dioxus CLI**✨ is a tool to get Dioxus projects off the ground.\n\nThere's no documentation for commands here, but you can see all commands using `dx --help` once you've installed the CLI! Furthermore, you can run `dx --help` to get help with a specific command.\n\n## Features\n\n* Build and pack a Dioxus project.\n* Format `rsx` code.\n* Hot Reload.\n* Create a Dioxus project from a template repository.\n* And more!" + } + 52usize => { + "# Middleware\n\nExtractors allow you to wrap your server function in some code that changes either the request or the response. Dioxus fullstack integrates with [Tower](https://docs.rs/tower/latest/tower/index.html) to allow you to wrap your server functions in middleware.\n\nYou can use the `#[middleware]` attribute to add a layer of middleware to your server function. Let's add a timeout middleware to a server function. This middleware will stop running the server function if it reaches a certain timeout:\n\n````rust@server_function_middleware.rs\n#[server]\n// Add a timeout middleware to the server function that will return an error if the function takes longer than 1 second to execute\n#[middleware(tower_http::timeout::TimeoutLayer::new(std::time::Duration::from_secs(1)))]\npub async fn timeout() -> Result<(), ServerFnError> {\n tokio::time::sleep(std::time::Duration::from_secs(2)).await;\n Ok(())\n}\n````" + } + 32usize => { + "# Adding the router to your application\n\nIn this chapter, we will learn how to add the router to our app. By itself, this\nis not very useful. However, it is a prerequisite for all the functionality\ndescribed in the other chapters.\n\n > \n > Make sure you added the `dioxus-router` dependency as explained in the\n > [introduction](../index.md).\n\nIn most cases, we want to add the router to the root component of our app. This\nway, we can ensure that we have access to all its functionality everywhere.\n\nFirst, we define the router with the router macro:\n\n````rust@first_route.rs\n#![allow(non_snake_case)]\nuse dioxus::prelude::*;\nuse dioxus_router::prelude::*;\n\n/// An enum of all of the possible routes in the app.\n#[derive(Routable, Clone)]\nenum Route {\n // The home page is at the / route\n #[route(\"/\")]\n Home {},\n}\n````\n\nThen we render the router with the \\[`Router`\\] component.\n\n````rust@first_route.rs\nfn App() -> Element {\n rsx! { Router:: {} }\n}\n````" + } + 40usize => { + "# Routing Update Callback\n\nIn some cases, we might want to run custom code when the current route changes. For this reason, the [`RouterConfig`] exposes an `on_update` field.\n\n## How does the callback behave?\n\nThe `on_update` is called whenever the current routing information changes. It is called after the router updated its internal state, but before dependent components and hooks are updated.\n\nIf the callback returns a [`NavigationTarget`], the router will replace the current location with the specified target. It will not call the `on_update` again.\n\nIf at any point the router encounters a navigation failure, it will go to the appropriate state without calling the `on_update`. It doesn't matter if the invalid target initiated the navigation, was found as a redirect target, or was returned by the `on_update` itself.\n\n## Code Example\n\n````rust@routing_update.rs\n#[derive(Routable, Clone, PartialEq)]\nenum Route {\n #[route(\"/\")]\n Index {},\n #[route(\"/home\")]\n Home {},\n}\n\n#[component]\nfn Home() -> Element {\n rsx! { p { \"Home\" } }\n}\n\n#[component]\nfn Index() -> Element {\n rsx! { p { \"Index\" } }\n}\n\nfn app() -> Element {\n rsx! {\n Router:: {\n config: || {\n RouterConfig::default()\n .on_update(|state| {\n (state.current() == Route::Index {})\n .then_some(NavigationTarget::Internal(Route::Home {}))\n })\n }\n }\n }\n}\n````" + } + 54usize => { + "# Routing\n\nYou can easily integrate your fullstack application with a client side router using Dioxus Router. This allows you to create different scenes in your app and navigate between them. You can read more about the router in the [router reference](../../router/index.md)\n\n````rust@server_router.rs\n#![allow(non_snake_case)]\n\nuse axum::Router;\nuse dioxus::prelude::*;\n\nuse dioxus_router::prelude::*;\nuse serde::{Deserialize, Serialize};\n\nfn main() {\n launch(|| rsx! { Router:: {} });\n}\n\n#[derive(Clone, Routable, Debug, PartialEq, Serialize, Deserialize)]\nenum Route {\n #[route(\"/\")]\n Home {},\n #[route(\"/blog/:id\")]\n Blog { id: i32 },\n}\n\n#[component]\nfn Blog(id: i32) -> Element {\n rsx! {\n Link { to: Route::Home {}, \"Go to counter\" }\n table {\n tbody {\n for _ in 0..id {\n tr {\n for _ in 0..id {\n td { \"hello world!\" }\n }\n }\n }\n }\n }\n }\n}\n\n#[component]\nfn Home() -> Element {\n let mut count = use_signal(|| 0);\n let mut text = use_signal(|| \"...\".to_string());\n\n rsx! {\n Link { to: Route::Blog { id: count() }, \"Go to blog\" }\n div {\n h1 { \"High-Five counter: {count}\" }\n button { onclick: move |_| count += 1, \"Up high!\" }\n button { onclick: move |_| count -= 1, \"Down low!\" }\n button {\n onclick: move |_| {\n async move {\n if let Ok(data) = get_server_data().await {\n println!(\"Client received: {}\", data);\n text.set(data.clone());\n post_server_data(data).await.unwrap();\n }\n }\n },\n \"Run server function!\"\n }\n \"Server said: {text}\"\n }\n }\n}\n\n#[server(PostServerData)]\nasync fn post_server_data(data: String) -> Result<(), ServerFnError> {\n println!(\"Server received: {}\", data);\n\n Ok(())\n}\n\n#[server(GetServerData)]\nasync fn get_server_data() -> Result {\n Ok(\"Hello from the server!\".to_string())\n}\n\n````\n\n````inject-dioxus\nSandBoxFrame {\n\turl: \"https://codesandbox.io/p/sandbox/dioxus-fullstack-router-s75v5q?file=%2Fsrc%2Fmain.rs%3A7%2C1\"\n}\n````" + } + 58usize => { + "# Antipatterns\n\nThis example shows what not to do and provides a reason why a given pattern is considered an \"AntiPattern\". Most anti-patterns are considered wrong for performance or code re-usability reasons.\n\n## Unnecessarily Nested Fragments\n\nFragments 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.\n\nOnly 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.\n\n````rust@anti_patterns.rs\n// ❌ Don't unnecessarily nest fragments\nlet _ = rsx! {\n Fragment {\n Fragment {\n Fragment {\n Fragment {\n Fragment { div { \"Finally have a real node!\" } }\n }\n }\n }\n }\n};\n\n// ✅ Render shallow structures\nrsx! { div { \"Finally have a real node!\" } }\n````\n\n## Incorrect Iterator Keys\n\nAs described in the [dynamic rendering chapter](../reference/dynamic_rendering#the-key-attribute), 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 will never change.\n\n````rust@anti_patterns.rs\nlet data: &HashMap<_, _> = &props.data;\n\n// ❌ No keys\nrsx! {\n ul {\n for value in data.values() {\n li { \"List item: {value}\" }\n }\n }\n};\n\n// ❌ Using index as keys\nrsx! {\n ul {\n for (index , value) in data.values().enumerate() {\n li { key: \"{index}\", \"List item: {value}\" }\n }\n }\n};\n\n// ✅ Using unique IDs as keys:\nrsx! {\n ul {\n for (key , value) in props.data.iter() {\n li { key: \"{key}\", \"List item: {value}\" }\n }\n }\n}\n````\n\n## Avoid Interior Mutability in Props\n\nWhile it is technically acceptable to have a `Mutex` or a `RwLock` in the props, they will be difficult to use.\n\nSuppose 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 write to the `username` field. However, when it does, the parent component will not be aware of the change, and the component will not re-render which causes the UI to be out of sync with the state. Instead, consider passing down a reactive value like a `Signal` or immutable data.\n\n````rust@anti_patterns.rs\n// ❌ Mutex/RwLock/RefCell in props\n#[derive(Props, Clone)]\nstruct AntipatternInteriorMutability {\n map: Rc>>,\n}\n\nimpl PartialEq for AntipatternInteriorMutability {\n fn eq(&self, other: &Self) -> bool {\n std::rc::Rc::ptr_eq(&self.map, &other.map)\n }\n}\n\nfn AntipatternInteriorMutability(map: Rc>>) -> Element {\n rsx! {\n button {\n onclick: {\n let map = map.clone();\n move |_| {\n // Writing to map will not rerun any components\n map.borrow_mut().insert(0, \"Hello\".to_string());\n }\n },\n \"Mutate map\"\n }\n // Since writing to map will not rerun any components, this will get out of date\n \"{map.borrow().get(&0).unwrap()}\"\n }\n}\n\n// ✅ Use a signal to pass mutable state\n#[component]\nfn AntipatternInteriorMutabilitySignal(map: Signal>) -> Element {\n rsx! {\n button {\n onclick: move |_| {\n // Writing to map will rerun any components that read the map\n map.write().insert(0, \"Hello\".to_string());\n },\n \"Mutate map\"\n }\n // Since writing to map will rerun subscribers, this will get updated\n \"{map.read().get(&0).unwrap()}\"\n }\n}\n````\n\n## Avoid Updating State During Render\n\nEvery time you update the state, Dioxus needs to re-render the component – this is inefficient! Consider refactoring your code to avoid this.\n\nAlso, if you unconditionally update the state during render, it will be re-rendered in an infinite loop.\n\n````rust@anti_patterns.rs\n// ❌ Updating state in render\nlet first_signal = use_signal(|| 0);\nlet mut second_signal = use_signal(|| 0);\n\n// Updating the state during a render can easily lead to infinite loops\nif first_signal() + 1 != second_signal() {\n second_signal.set(first_signal() + 1);\n}\n\n// ✅ Update state in an effect\nlet first_signal = use_signal(|| 0);\nlet mut second_signal = use_signal(|| 0);\n\n// The closure you pass to use_effect will be rerun whenever any of the dependencies change without re-rendering the component\nuse_effect(move || {\n if first_signal() + 1 != second_signal() {\n second_signal.set(first_signal() + 1);\n }\n});\n\n// ✅ Deriving state with use_memo\nlet first_signal = use_signal(|| 0);\n// Memos are specifically designed for derived state. If your state fits this pattern, use it.\nlet second_signal = use_memo(move || first_signal() + 1);\n````\n\n## Avoid Large Groups of State\n\nIt can be tempting to have a single large state struct that contains all of your application's state. However, this can lead to issues:\n\n* It can be easy to accidentally mutate the state in a way that causes an infinite loop\n* It can be difficult to reason about when and how the state is updated\n* It can lead to performance issues because many components will need to re-render when the state changes\n\nInstead, consider breaking your state into smaller, more manageable pieces. This will make it easier to reason about the state, avoid update loops, and improve performance.\n\n````rust@anti_patterns.rs\nfn app() -> Element {\n // ❌ Large state struct\n #[derive(Props, Clone, PartialEq)]\n struct LargeState {\n users: Vec,\n logged_in: bool,\n warnings: Vec,\n }\n\n #[derive(Props, Clone, PartialEq)]\n struct User {\n name: String,\n email: String,\n }\n\n let mut all_my_state = use_signal(|| LargeState {\n users: vec![User {\n name: \"Alice\".to_string(),\n email: \"alice@example.com\".to_string(),\n }],\n logged_in: true,\n warnings: vec![],\n });\n\n use_effect(move || {\n // It is very easy to accidentally read and write to the state object if it contains all your state\n let read = all_my_state.read();\n let logged_in = read.logged_in;\n if !logged_in {\n all_my_state\n .write_unchecked()\n .warnings\n .push(\"You are not logged in\".to_string());\n }\n });\n\n // ✅ Use multiple signals to manage state\n let users = use_signal(|| {\n vec![User {\n name: \"Alice\".to_string(),\n email: \"alice@example.com\".to_string(),\n }]\n });\n let logged_in = use_signal(|| true);\n let mut warnings = use_signal(|| vec![]);\n\n use_effect(move || {\n // Now you can read and write to separate signals which will not cause issues\n if !logged_in() {\n warnings.write().push(\"You are not logged in\".to_string());\n }\n });\n\n // ✅ Use memos to create derived state when larger states are unavoidable\n // Notice we didn't split everything into separate signals. Users still make sense as a vec of data\n let users = use_signal(|| {\n vec![User {\n name: \"Alice\".to_string(),\n email: \"alice@example.com\".to_string(),\n }]\n });\n let logged_in = use_signal(|| true);\n let warnings: Signal> = use_signal(|| vec![]);\n\n // In child components, you can use the memo to create derived that will only update when a specific part of the state changes\n // This will help you avoid unnecessary re-renders and infinite loops\n #[component]\n fn FirstUser(users: Signal>) -> Element {\n let first_user = use_memo(move || users.read().first().unwrap().clone());\n\n rsx! {\n div {\n \"First user: {first_user().name}\"\n }\n }\n }\n\n rsx! {\n FirstUser {\n users\n }\n }\n}\n````\n\n## Running Non-Deterministic Code in the Body of a Component\n\nIf you have a component that contains non-deterministic code, that code should generally not be run in the body of the component. If it is put in the body of the component, it will be executed every time the component is re-rendered which can lead to performance issues.\n\nInstead, consider moving the non-deterministic code into a hook that only runs when the component is first created or an effect that reruns when dependencies change.\n\n````rust@anti_patterns.rs\n// ❌ Non-deterministic code in the body of a component\n#[component]\nfn NonDeterministic(name: String) -> Element {\n let my_random_id = rand::random::();\n\n rsx! {\n div {\n // Id will change every single time the component is re-rendered\n id: \"{my_random_id}\",\n \"Hello {name}\"\n }\n }\n}\n\n// ✅ Use a hook to run non-deterministic code\nfn NonDeterministicHook(name: String) -> Element {\n // If you store the result of the non-deterministic code in a hook, it will stay the same between renders\n let my_random_id = use_hook(|| rand::random::());\n\n rsx! {\n div {\n id: \"{my_random_id}\",\n \"Hello {name}\"\n }\n }\n}\n````\n\n## Overly Permissive PartialEq for Props\n\nYou may have noticed that `Props` requires a `PartialEq` implementation. That `PartialEq` is very important for Dioxus to work correctly. It is used to determine if a component should re-render or not when the parent component re-renders.\n\nIf you cannot derive `PartialEq` for your `Props`, you will need to implement it yourself. If you do implement `PartialEq`, make sure to return `false` any time the props change in a way that would cause the UI in the child component to change.\n\nIn general, returning `false` from `PartialEq` if you aren't sure if the props have changed or not is better than returning `true`. This will help you avoid out of date UI in your child components.\n\n````rust@anti_patterns.rs\n// ❌ Permissive PartialEq for Props\n#[derive(Props, Clone)]\nstruct PermissivePartialEqProps {\n name: String,\n}\n\n// This will cause the component to **never** re-render when the parent component re-renders\nimpl PartialEq for PermissivePartialEqProps {\n fn eq(&self, _: &Self) -> bool {\n true\n }\n}\n\nfn PermissivePartialEq(name: PermissivePartialEqProps) -> Element {\n rsx! {\n div {\n \"Hello {name.name}\"\n }\n }\n}\n\n#[component]\nfn PermissivePartialEqParent() -> Element {\n let name = use_signal(|| \"Alice\".to_string());\n\n rsx! {\n PermissivePartialEq {\n // The PermissivePartialEq component will not get the updated value of name because the PartialEq implementation says that the props are the same\n name: name()\n }\n }\n}\n\n// ✅ Derive PartialEq for Props\n#[derive(Props, Clone, PartialEq)]\nstruct DerivePartialEqProps {\n name: String,\n}\n\nfn DerivePartialEq(name: DerivePartialEqProps) -> Element {\n rsx! {\n div {\n \"Hello {name.name}\"\n }\n }\n}\n\n#[component]\nfn DerivePartialEqParent() -> Element {\n let name = use_signal(|| \"Alice\".to_string());\n\n rsx! {\n DerivePartialEq {\n name: name()\n }\n }\n}\n\n// ✅ Return false from PartialEq if you are unsure if the props have changed\n#[derive(Debug)]\nstruct NonPartialEq;\n\n#[derive(Props, Clone)]\nstruct RcPartialEqProps {\n name: Rc,\n}\n\nimpl PartialEq for RcPartialEqProps {\n fn eq(&self, other: &Self) -> bool {\n // This will almost always return false because the Rc will likely point to a different value\n // Implementing PartialEq for NonPartialEq would be better, but if it is controlled by another library, it may not be possible\n // **Always** return false if you are unsure if the props have changed\n std::rc::Rc::ptr_eq(&self.name, &other.name)\n }\n}\n\nfn RcPartialEq(name: RcPartialEqProps) -> Element {\n rsx! {\n div {\n \"Hello {name.name:?}\"\n }\n }\n}\n\nfn RcPartialEqParent() -> Element {\n let name = use_signal(|| Rc::new(NonPartialEq));\n\n rsx! {\n RcPartialEq {\n // Generally, RcPartialEq will rerun even if the value of name hasn't actually changed because the Rc will point to a different value\n name: name()\n }\n }\n}\n````" + } + 36usize => { + "# Links & Navigation\n\nWhen we split our app into pages, we need to provide our users with a way to\nnavigate between them. On regular web pages, we'd use an anchor element for that,\nlike this:\n\n````html\nLink to an other page\n````\n\nHowever, we cannot do that when using the router for three reasons:\n\n1. Anchor tags make the browser load a new page from the server. This takes a\n lot of time, and it is much faster to let the router handle the navigation\n client-side.\n1. Navigation using anchor tags only works when the app is running inside a\n browser. This means we cannot use them inside apps using Dioxus Desktop.\n1. Anchor tags cannot check if the target page exists. This means we cannot\n prevent accidentally linking to non-existent pages.\n\nTo solve these problems, the router provides us with a \\[`Link`\\] component we can\nuse like this:\n\n````rust@links.rs\n#[component]\nfn NavBar() -> Element {\n rsx! {\n nav {\n ul {\n li {\n Link { to: Route::Home {}, \"Home\" }\n }\n }\n }\n Outlet:: {}\n }\n}\n````\n\nThe `target` in the example above is similar to the `href` of a regular anchor\nelement. However, it tells the router more about what kind of navigation it\nshould perform. It accepts something that can be converted into a\n\\[`NavigationTarget`\\]:\n\n* The example uses a Internal route. This is the most common type of navigation.\n It tells the router to navigate to a page within our app by passing a variant of a \\[`Routable`\\] enum. This type of navigation can never fail if the link component is used inside a router component.\n* \\[`External`\\] allows us to navigate to URLs outside of our app. This is useful\n for links to external websites. NavigationTarget::External accepts an URL to navigate to. This type of navigation can fail if the URL is invalid.\n\n > \n > The \\[`Link`\\] accepts several props that modify its behavior. See the API docs\n > for more details." + } + 73usize => { + "# Components\n\nJust 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, you should break down the functionality of an app in logical parts called components.\n\nA component is a Rust function, named in [UpperCamelCase](https://en.wikipedia.org/wiki/Camel_case), that either takes no parameters or a properties struct and returns an `Element` describing the UI it wants to render.\n\n````rust, no_run@hello_world_desktop.rs\n// define a component that renders a div with the text \"Hello, world!\"\nfn App() -> Element {\n rsx! {\n div { \"Hello, world!\" }\n }\n}\n````\n\n > \n > You'll probably want to add `#![allow(non_snake_case)]` to the top of your crate to avoid warnings about `UpperCamelCase` component names\n\nA 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:\n\n````rust, no_run@components.rs\npub fn About() -> Element {\n rsx! {\n p {\n b { \"Dioxus Labs\" }\n \" An Open Source project dedicated to making Rust UI wonderful.\"\n }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\tcomponents::About {}\n}\n````\n\nThen, you can render your component in another component, similarly to how elements are rendered:\n\n````rust, no_run@components.rs\npub fn App() -> Element {\n rsx! {\n About {}\n About {}\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\tcomponents::App {}\n}\n````\n\n > \n > 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!" + } + 83usize => { + "# Spawning Futures\n\nThe `use_resource` 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 `spawn`:\n\n````rust@spawn.rs\nlet mut response = use_signal(|| String::from(\"...\"));\n\nlet log_in = move |_| {\n spawn(async move {\n let resp = reqwest::Client::new()\n .get(\"https://dioxuslabs.com\")\n .send()\n .await;\n\n match resp {\n Ok(_data) => {\n log::info!(\"dioxuslabs.com responded!\");\n response.set(\"dioxuslabs.com responded!\".into());\n }\n Err(err) => {\n log::info!(\"Request failed with error: {err:?}\")\n }\n }\n });\n};\n\nrsx! {\n button { onclick: log_in, \"Response: {response}\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n spawn::App {}\n}\n````\n\n > \n > Note: `spawn` will always spawn a *new* future. You most likely don't want to call it on every render.\n\nCalling `spawn` will give you a `JoinHandle` which lets you cancel or pause the future.\n\n## Spawning Tokio Tasks\n\nSometimes, 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:\n\n````rust@spawn.rs\nspawn(async {\n let _ = tokio::spawn(async {}).await;\n\n let _ = tokio::task::spawn_local(async {\n // some !Send work\n })\n .await;\n});\n````" + } + 56usize => { + "# Axum integration\n\nDioxus fullstack is built on top of Axum under the hood. The launch function will set up a default Axum server for your fullstack project, but if you need more control you can drop down to a custom axum server. This guide will show you how to set up an Axum server with your Dioxus fullstack project.\n\n## Adding axum to your project\n\nFirst, we need to add axum and tokio as dependencies. Since we only need these dependencies on the server, we need to make them optional and enable them in the server feature flag. More information about server only dependencies can be found in the [dependencies guide](./managing_dependencies.md#adding-server-only-dependencies):\n\n````toml\n[dependencies]\ndioxus = { version = \"0.6.0\", features = [\"fullstack\"] }\n# Axum is optional because we only use it on the server\naxum = { version = \"0.7\", optional = true }\ntokio = { version = \"1.0\", features = [\"full\"], optional = true }\n\n[features]\n# ...\nweb = [\"dioxus/web\"]\n# The server feature enables the axum dependency\nserver = [\"dioxus/server\", \"dep:axum\", \"dep:tokio\"]\n````\n\n## Splitting up the main function\n\nNext we to split up our main function into two parts: one that will start dioxus on the client with the launch function, and one that will serve our Axum router. We can use `#[cfg]` attribute to control what code compiles when the web feature or server feature is enabled:\n\n````rust@axum_integration.rs\nfn main() {\n #[cfg(feature = \"web\")]\n // Hydrate the application on the client\n dioxus::launch(App);\n\n // Launch axum on the server\n #[cfg(feature = \"server\")]\n {\n tokio::runtime::Runtime::new()\n .unwrap()\n .block_on(async move {\n launch_server(App).await;\n });\n }\n}\n````\n\n## Starting the axum server\n\nNow we can setup our axum server in the launch_server function. Since this code should only compile when the server is enabled, we need to gate it behind a `#[cfg(feature = \"server\")]` attribute again.\n\nWhen we serve our app the CLI will try to proxy the backend under a random port. The proxy adds devtool endpoints for hot reloading and logging to make it easier to develop your app. We need to look for the port the CLI gives us in dev mode with `server_ip` and `server_port`. If we don't find a port from the CLI, we can serve at our own ip and port for production.\n\nFinally, when building our Axum server, we need to call `serve_dioxus_application` on the router to add all the routes Dioxus needs to serve your app.\n\n````rust@axum_integration.rs\n#[cfg(feature = \"server\")]\nasync fn launch_server(component: fn() -> Element) {\n use std::net::{IpAddr, Ipv4Addr, SocketAddr};\n\n // Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address\n // and we use the generated address the CLI gives us\n let ip =\n dioxus::cli_config::server_ip().unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));\n let port = dioxus::cli_config::server_port().unwrap_or(8080);\n let address = SocketAddr::new(ip, port);\n let listener = tokio::net::TcpListener::bind(address).await.unwrap();\n let router = axum::Router::new()\n // serve_dioxus_application adds routes to server side render the application, serve static assets, and register server functions\n .serve_dioxus_application(ServeConfigBuilder::default(), App)\n .into_make_service();\n axum::serve(listener, router).await.unwrap();\n}\n````\n\n## Running your application\n\nNow we can run our application with the CLI. The CLI will automatically detect the server feature (that enables `dioxus/server`) and the client feature (which enables `dioxus/web`, `dioxus/desktop` or `dioxus/mobile`) and build once for each platform. To run the application, we can use the following command:\n\n````bash\ndx serve\n````\n\n## Conclusion\n\nThis guide showed you how to set up a custom Axum server with your Dioxus fullstack project. You can now use all of the features of Axum in your Dioxus app, including middleware, routing, and more. If you need even more granular control over your router, you can split up the `serve_dioxus_application` function into [`serve_static_assets`](https://docs.rs/dioxus-fullstack/0.6.3/dioxus_fullstack/server/trait.DioxusRouterExt.html#tymethod.serve_static_assets), [`register_server_functions`](https://docs.rs/dioxus-fullstack/0.6.3/dioxus_fullstack/server/trait.DioxusRouterExt.html#method.register_server_functions), and [`render_handler`](https://docs.rs/dioxus-fullstack/0.6.3/dioxus_fullstack/server/fn.render_handler.html). See the documentation on each method for more details\n\nIf you want to learn more about Axum, check out the [Axum documentation](https://docs.rs/axum/latest/axum/). Axum is built on top of the tower ecosystem which means you can use any tower service with your Axum server. The [tower-http](https://docs.rs/tower-http/latest/tower_http/) crate contains many useful utilities for your server like logging, compression, and file serving." + } + 61usize => { + "# Internationalization\n\nIf your application supports multiple languages, the [dioxus-i18n](https://github.com/dioxus-community/dioxus-i18n) crate contains helpers to make working with translations in your application easier.\n\nYou can find an example [here](https://github.com/dioxus-community/dioxus-i18n/blob/main/examples/dioxus-desktop.rs)." + } + 26usize => { + "# Overview\n\nIn this guide, you'll learn to effectively use Dioxus Router whether you're\nbuilding a small todo app or the next FAANG company. We will create a small\nwebsite with a blog, homepage, and more!\n\n > \n > To follow along with the router example, you'll need a working Dioxus app.\n > Check out the [Dioxus book](https://dioxuslabs.com/learn/0.6/getting_started) to get started.\n\n > \n > Make sure to add Dioxus Router as a dependency, as explained in the\n > [introduction](../index.md).\n\n## You'll learn how to\n\n* Create routes and render \"pages\".\n* Utilize nested routes, create a navigation bar, and render content for a\n set of routes.\n* Parse URL parameters to dynamically display content.\n* Redirect visitors to different routes.\n\n > \n > **Disclaimer**\n > \n > The example will only display the features of Dioxus Router. It will not\n > include any actual functionality. To keep things simple we will only be using\n > a single file, this is not the recommended way of doing things with a real\n > application.\n\nYou can find the complete application in the [full code](full-code.md) chapter." + } + 37usize => { + "# Programmatic Navigation\n\nSometimes we want our application to navigate to another page without having the\nuser click on a link. This is called programmatic navigation.\n\n## Using a Navigator\n\nWe can get a navigator with the [`navigator`] function which returns a [`Navigator`].\n\nWe can use the [`Navigator`] to trigger four different kinds of navigation:\n\n* `push` will navigate to the target. It works like a regular anchor tag.\n* `replace` works like `push`, except that it replaces the current history entry\n instead of adding a new one. This means the prior page cannot be restored with the browser's back button.\n* `Go back` works like the browser's back button.\n* `Go forward` works like the browser's forward button.\n\n````rust@navigator.rs\n#[component]\nfn Home() -> Element {\n let nav = navigator();\n\n // push\n nav.push(Route::PageNotFound { route: vec![] });\n\n // replace\n nav.replace(Route::Home {});\n\n // go back\n nav.go_back();\n\n // go forward\n nav.go_forward();\n\n rsx! { h1 { \"Welcome to the Dioxus Blog!\" } }\n}\n````\n\nYou might have noticed that, like [`Link`], the [`Navigator`]s `push` and\n`replace` functions take a [`NavigationTarget`]. This means we can use either\n`Internal`, or `External` targets.\n\n## External Navigation Targets\n\nUnlike a [`Link`], the [`Navigator`] cannot rely on the browser (or webview) to\nhandle navigation to external targets via a generated anchor element.\n\nThis means, that under certain conditions, navigation to external targets can\nfail." + } + 78usize => { + "# Sharing State\n\nOften, multiple components need to access the same state. Depending on your needs, there are several ways to implement this.\n\n## Lifting State\n\nOne approach to share state between components is to \"lift\" it up to the nearest common ancestor. This means putting the `use_signal` hook in a parent component, and passing the needed values down as props.\n\nSuppose 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).\n\n > \n > 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, maintainable, and performant (this is even more important for larger, complex apps).\n\nWe start with a `Meme` component, responsible for rendering a meme with a given caption:\n\n````rust, no_run@meme_editor.rs\n#[component]\nfn Meme(caption: String) -> Element {\n let container_style = r#\"\n position: relative;\n width: fit-content;\n \"#;\n\n let caption_container_style = r#\"\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 16px 8px;\n \"#;\n\n let caption_style = r\"\n font-size: 32px;\n margin: 0;\n color: white;\n text-align: center;\n \";\n\n rsx! {\n div { style: \"{container_style}\",\n img { src: \"https://i.imgflip.com/2zh47r.jpg\", height: \"500px\" }\n div { style: \"{caption_container_style}\", p { style: \"{caption_style}\", \"{caption}\" } }\n }\n }\n}\n````\n\n > \n > Note that the `Meme` component is unaware where the caption is coming from – it could be stored in `use_signal`, or a constant. This ensures that it is very reusable – the same component can be used for a meme gallery without any changes!\n\nWe 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:\n\n````rust, no_run@meme_editor.rs\n#[component]\nfn CaptionEditor(caption: String, oninput: EventHandler) -> Element {\n let input_style = r\"\n border: none;\n background: cornflowerblue;\n padding: 8px 16px;\n margin: 0;\n border-radius: 4px;\n color: white;\n \";\n\n rsx! {\n input {\n style: \"{input_style}\",\n value: \"{caption}\",\n oninput: move |event| oninput.call(event)\n }\n }\n}\n````\n\nFinally, a third component will render the other two as children. It will be responsible for keeping the state and passing down the relevant props.\n\n````rust, no_run@meme_editor.rs\nfn MemeEditor() -> Element {\n let container_style = r\"\n display: flex;\n flex-direction: column;\n gap: 16px;\n margin: 0 auto;\n width: fit-content;\n \";\n\n let mut caption = use_signal(|| \"me waiting for my rust code to compile\".to_string());\n\n rsx! {\n div { style: \"{container_style}\",\n h1 { \"Meme Editor\" }\n Meme { caption: caption }\n CaptionEditor { caption: caption, oninput: move |event: FormEvent| caption.set(event.value()) }\n }\n }\n}\n````\n\n![Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: \"me waiting for a language feature\"](/assets/static/meme_editor_screenshot.png)\n\n## Using Shared State\n\nSometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient.\n\nSuppose 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.\n\n > \n > 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!\n\nNow, we could write another `use_signal` 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!\n\nDioxus 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 provides any Clone context (including Signals!) to any child components. Child components can use the [`use_context`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html) hook to get that context and if it is a Signal, they can read and write to it.\n\nFirst, we have to create a struct for our dark mode configuration:\n\n````rust, no_run@meme_editor_dark_mode.rs\n#[derive(Clone, Copy)]\nstruct DarkMode(bool);\n````\n\nNow, in a top-level component (like `App`), we can provide the `DarkMode` context to all children components:\n\n````rust, no_run@meme_editor_dark_mode.rs\nuse_context_provider(|| Signal::new(DarkMode(false)));\n````\n\nAs a result, any child component of `App` (direct or not), can access the `DarkMode` context.\n\n````rust, no_run@meme_editor_dark_mode.rs\nlet dark_mode_context = use_context::>();\n````\n\n > \n > `use_context` returns `Signal` here, because the Signal was provided by the parent. If the context hadn't been provided `use_context` would have panicked.\n\nIf you have a component where the context might or not be provided, you might want to use `try_consume_context`instead, so you can handle the `None` case. The drawback of this method is that it will not memoize the value between renders, so it won't be as as efficient as `use_context`, you could do it yourself with `use_hook` though.\n\nFor 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):\n\n````rust, no_run@meme_editor_dark_mode.rs\npub fn DarkModeToggle() -> Element {\n let mut dark_mode = use_context::>();\n\n let style = if dark_mode().0 { \"color:white\" } else { \"\" };\n\n rsx! {\n label { style: \"{style}\",\n \"Dark Mode\"\n input {\n r#type: \"checkbox\",\n oninput: move |event| {\n let is_enabled = event.value() == \"true\";\n dark_mode.write().0 = is_enabled;\n }\n }\n }\n }\n}\n````" + } + 8usize => { + "# Interactivity\n\nNow that our *HotDog* app is scaffolded and styled, we can finally add some interactive elements.\n\n## Encapsulating State\n\nBefore we get too far, let's split our app into two parts: the `Title` and the `DogView`. This will help us organize our app and keep the `DogView` state separated from `Title` state.\n\n````rust@guide_state.rs\n#[component]\nfn App() -> Element {\n rsx! {\n document::Stylesheet { href: CSS }\n Title {}\n DogView {}\n }\n}\n\n#[component]\nfn Title() -> Element {\n rsx! {\n div { id: \"title\",\n h1 { \"HotDog! 🌭\" }\n }\n }\n}\n\n#[component]\nfn DogView() -> Element {\n rsx! {\n div { id: \"dogview\",\n img { src: \"https://images.dog.ceo/breeds/pitbull/dog-3981540_1280.jpg\" }\n }\n div { id: \"buttons\",\n button { id: \"skip\", \"skip\" }\n button { id: \"save\", \"save!\" }\n }\n }\n}\n````\n\n## Event Handlers\n\nIn the `DogView` component, we want to attach an action to the click of the buttons. For example: skipping or saving the current dog photo. We can use an [EventHandler](../reference/event_handlers.md) to listen for the `click` events.\n\nEvent 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 its corresponding event is triggered. The listener receives information about the event in the [Event](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Event.html) object.\n\nWe'll add some closures inline and then pass them to the `onclick` attribute for both the *skip* and *save* buttons:\n\n````rust@guide_state.rs\n#[component]\nfn DogView() -> Element {\n let skip = move |evt| {};\n let save = move |evt| {};\n\n rsx! {\n // ...\n div { id: \"buttons\",\n button { onclick: skip, id: \"skip\", \"skip\" }\n button { onclick: save, id: \"save\", \"save!\" }\n }\n }\n}\n````\n\n > \n > You can read more about Event Handlers in the [Event Handler reference](../reference/event_handlers.md)\n\n## State with use_hook\n\nSo far, our components have no internal state. For our `DogView`, we want to change the currently displayed dog photo whenever the user clicks *skip* or *save*.\n\nTo store state in components, Dioxus provides the `use_hook` function. This makes it possible for bare Rust functions to store and load state without the use of an extra struct.\n\nWhen called in a component, the `use_hook` function will return a `.clone()` of the originally stored value:\n\n````rust@guide_state.rs\n#[component]\nfn DogView() -> Element {\n let img_src = use_hook(|| \"https://images.dog.ceo/breeds/pitbull/dog-3981540_1280.jpg\");\n\n // ..\n\n rsx! {\n div { id: \"dogview\",\n img { src: \"{img_src}\" }\n }\n // ..\n }\n}\n````\n\nDioxus hooks are very similar to React's hooks and need to follow some [simple rules](../guides/rules_of_hooks.md#the-rules-of-hooks) to function properly.\n\n## Signals and use_signal\n\nWhile `use_hook` makes it possible to store any value that implements `Clone`, you'll frequently want a more capable form of state management. Built-in to Dioxus are *signals*.\n\n`Signal` is a wrapper type around an ordinary Rust value that tracks reads and writes, bringing your app to life. You can wrap any Rust value in a signal. Signals can be created manually with `Signal::new()` but we strongly recommend using the `use_signal` hook instead.\n\n > \n > 📣 Manually creating Signals requires remembering to call `.manually_drop()` on the signal whereas `use_signal` cleans the Signal up for you automatically.\n\nWhenever a signal's value changes, its containing \"reactive scope\" will be \"marked dirty\" and re-run. By default, Dioxus components are reactive scopes, and thus, will re-render whenever a signal value changes.\n\n![Basic Interactivity](/assets/06_docs/hotdog-interactivity.mp4)\n\nSignals are core to Dioxus and take time to master. We recommend reading the [state management](../essentials/state/index.md) guide in depth before diving into your first large app.\n\n## Global State with Context\n\nWhile hooks are good for state *local* to components, occasionally you'll want to manage state for your *entire* app.\n\nDioxus provides two mechanisms: `Context` and `GlobalSignal`.\n\nThe `Context` API makes it possible for parent components to share state with child components without explicitly declaring an additional property field. This is used by larger apps and libraries to share state across the app without modifying component signatures.\n\nTo \"provide\" context, simply call `use_context_provider()` with a struct that implements `Clone`. To read the context in a child, call `use_context()`.\n\n````rust@guide_state.rs\n// Create a new wrapper type\n#[derive(Clone)]\nstruct TitleState(String);\n\nfn App() -> Element {\n // Provide that type as a Context\n use_context_provider(|| TitleState(\"HotDog\".to_string()));\n rsx! {\n Title {}\n }\n}\n\nfn Title() -> Element {\n // Consume that type as a Context\n let title = use_context::();\n rsx! {\n h1 { \"{title.0}\" }\n }\n}\n````\n\nYou can combine `use_signal` and `Context` to provide reactive state to your app:\n\n````rust@guide_state.rs\n#[derive(Clone, Copy)]\nstruct MusicPlayer {\n song: Signal,\n}\n\nfn use_music_player_provider() {\n let song = use_signal(|| \"Drift Away\".to_string());\n use_context_provider(|| MusicPlayer { song });\n}\n````\n\nWith `use_context` and `consume_context`, you can easily reach up to modify that state:\n\n````rust@guide_state.rs\n#[component]\nfn Player() -> Element {\n rsx! {\n button {\n onclick: move |_| consume_context::().song.set(\"Vienna\".to_string()),\n \"Shuffle\"\n }\n }\n}\n````\n\nAny components that read the song signal will automatically re-render when the value changes.\n\n## Global Signals\n\nOccasionally you'll want a simple global value. This is where `GlobalSignal` helps. GlobalSignals are a combination of the Context system and Signals that require no additional structs or setup.\n\nSimply declare a GlobalSignal somewhere in your app:\n\n````rust@guide_state.rs\nstatic SONG: GlobalSignal = Signal::global(|| \"Drift Away\".to_string());\n````\n\nAnd then read and write to it from anywhere:\n\n````rust@guide_state.rs\n#[component]\nfn Player() -> Element {\n rsx! {\n h3 { \"Now playing {SONG}\" }\n button {\n onclick: move |_| *SONG.write() = \"Vienna\".to_string(),\n \"Shuffle\"\n }\n }\n}\n````\n\n > \n > 📣 GlobalSignals are only global to one app - not the entire program. On the server, every app gets its own GlobalSignal.\n\nWe won't need either GlobalSignal or Context for *HotDog*, but it's important to know that these are available to you." + } + 90usize => { + "# Translating existing HTML\n\nDioxus uses a custom format called RSX to represent the HTML because it is more concise and looks more like Rust code. However, it can be a pain to convert existing HTML to RSX. That's why Dioxus comes with a tool called `dx translate` that can automatically convert HTML to RSX!\n\nDx translate can make converting large chunks of HTML to RSX much easier! Lets try translating some of the HTML from the Dioxus homepage:\n\n````sh\ndx translate --raw \"
Fullstack, crossplatform,lightning fast, fully typed.

Dioxus is a Rust library for building apps that run on desktop, web, mobile, and more.

Trusted by top companies

\"\n````\n\nWe get the following RSX you can easily copy and paste into your code:\n\n````rs\ndiv { class: \"relative w-full mx-4 sm:mx-auto text-gray-600\",\n div { class: \"text-[3em] md:text-[5em] font-semibold dark:text-white text-ghdarkmetal font-sans py-12 flex flex-col\",\n span { \"Fullstack, crossplatform,\" }\n span { \"lightning fast, fully typed.\" }\n }\n h3 { class: \"text-[2em] dark:text-white font-extralight text-ghdarkmetal pt-4 max-w-screen-md mx-auto\",\n \"Dioxus is a Rust library for building apps that run on desktop, web, mobile, and more.\"\n }\n div { class: \"pt-12 text-white text-[1.2em] font-sans font-bold flex flex-row justify-center space-x-4\",\n a {\n href: \"/learn/0.6/getting_started\",\n data_dioxus_id: \"216\",\n dioxus_prevent_default: \"onclick\",\n class: \"bg-red-600 py-2 px-8 hover:-translate-y-2 transition-transform duration-300\",\n \"Quickstart\"\n }\n a {\n dioxus_prevent_default: \"onclick\",\n href: \"/learn/0.6/reference\",\n data_dioxus_id: \"214\",\n class: \"bg-blue-500 py-2 px-8 hover:-translate-y-2 **transition**-transform duration-300\",\n \"Read the docs\"\n }\n }\n div { class: \"max-w-screen-2xl mx-auto pt-36\",\n h1 { class: \"text-md\", \"Trusted by top companies\" }\n div { class: \"pt-4 flex flex-row flex-wrap justify-center\",\n div { class: \"h-12 w-40 p-2 m-4 flex justify-center items-center\",\n img { src: \"/assets/static/futurewei_bw.png\" }\n }\n div { class: \"h-12 w-40 p-2 m-4 flex justify-center items-center\",\n img { src: \"/assets/static/airbuslogo.svg\" }\n }\n div { class: \"h-12 w-40 p-2 m-4 flex justify-center items-center\",\n img { src: \"/assets/static/ESA_logo.svg\" }\n }\n div { class: \"h-12 w-40 p-2 m-4 flex justify-center items-center\",\n img { src: \"/assets/static/yclogo.svg\" }\n }\n div { class: \"h-12 w-40 p-2 m-4 flex justify-center items-center\",\n img { src: \"/assets/static/satellite.webp\" }\n }\n }\n }\n}\n````\n\n## Usage\n\nThe `dx translate` command has several flags you can use to control your html input and rsx output.\n\nYou can use the `--file` flag to translate an HTML file to RSX:\n\n````sh\ndx translate --file index.html\n````\n\nOr you can use the `--raw` flag to translate a string of HTML to RSX:\n\n````sh\ndx translate --raw \"
Hello world
\"\n````\n\nBoth of those commands will output the following RSX:\n\n````rs\ndiv { \"Hello world\" }\n````\n\nThe `dx translate` command will output the RSX to stdout. You can use the `--output` flag to write the RSX to a file instead.\n\n````sh\ndx translate --raw \"
Hello world
\" --output index.rs\n````\n\nYou can automatically create a component with the `--component` flag.\n\n````sh\ndx translate --raw \"
Hello world
\" --component\n````\n\nThis will output the following component:\n\n````rs\nfn component() -> Element {\n rsx! {\n div { \"Hello world\" }\n }\n}\n````\n\nTo learn more about the different flags `dx translate` supports, run `dx translate --help`." + } + 24usize => { + "# The Rules of Hooks\n\nHooks are a powerful way to manage state in Dioxus, but there are some rules you need to follow to insure they work as expected. Dioxus uses the order you call hooks to differentiate between hooks. Because the order you call hooks matters, you must follow these rules:\n\n1. Hooks may be only used in components or other hooks (we'll get to that later)\n1. On every call to the component function\n 1. The same hooks must be called\n 1. In the same order\n1. Hooks name's should start with `use_` so you don't accidentally confuse them with regular functions\n\nThese rules mean that there are certain things you can't do with hooks:\n\n#### No Hooks in Conditionals\n\n````rust@hooks_bad.rs\n// ❌ don't call hooks in conditionals!\n// We must ensure that the same hooks will be called every time\n// But `if` statements only run if the conditional is true!\n// So we might violate rule 2.\nif you_are_happy && you_know_it {\n let something = use_signal(|| \"hands\");\n println!(\"clap your {something}\")\n}\n\n// ✅ instead, *always* call use_signal\n// You can put other stuff in the conditional though\nlet something = use_signal(|| \"hands\");\nif you_are_happy && you_know_it {\n println!(\"clap your {something}\")\n}\n````\n\n#### No Hooks in Closures\n\n````rust@hooks_bad.rs\n// ❌ don't call hooks inside closures!\n// We can't guarantee that the closure, if used, will be called in the same order every time\nlet _a = || {\n let b = use_signal(|| 0);\n b()\n};\n\n// ✅ instead, move hook `b` outside\nlet b = use_signal(|| 0);\nlet _a = || b();\n````\n\n#### No Hooks in Loops\n\n````rust@hooks_bad.rs\n// `names` is a Vec<&str>\n\n// ❌ Do not use hooks in loops!\n// In this case, if the length of the Vec changes, we break rule 2\nfor _name in &names {\n let is_selected = use_signal(|| false);\n println!(\"selected: {is_selected}\");\n}\n\n// ✅ Instead, use a hashmap with use_signal\nlet selection_map = use_signal(HashMap::<&str, bool>::new);\n\nfor name in &names {\n let is_selected = selection_map.read()[name];\n println!(\"selected: {is_selected}\");\n}\n````" + } + 68usize => { + "# Optimizing\n\n*Note: This is written primarily for the web, but the main optimizations will work on other platforms too.*\n\nYou might have noticed that Dioxus binaries are pretty big.\nThe WASM binary of a [TodoMVC app](https://github.com/tigerros/dioxus-todo-app) weighs in at 2.36mb!\nDon't worry; we can get it down to a much more manageable 234kb.\nThis will get obviously lower over time.\nWith nightly features, you can even reduce the binary size of a hello world app to less than 100kb!\n\nWe will also discuss ways to optimize your app for increased speed.\n\nHowever, certain optimizations will sacrifice speed for decreased binary size or the other way around.\nThat's what you need to figure out yourself. Does your app perform performance-intensive tasks, such as graphical processing or tons of DOM manipulations?\nYou could go for increased speed. In most cases, though, decreased binary size is the better choice, especially because Dioxus WASM binaries are quite large.\n\nTo test binary sizes, we will use [this](https://github.com/tigerros/dioxus-todo-app) repository as a sample app.\nThe `no-optimizations` package will serve as the base, which weighs 2.36mb as of right now.\n\nAdditional resources:\n\n* [WASM book - Shrinking `.wasm` code size](https://rustwasm.github.io/docs/book/reference/code-size.html)\n* [min-sized-rust](https://github.com/johnthagen/min-sized-rust)\n\n## Building in release mode\n\nThis is the best way to optimize. In fact, the 2.36mb figure at the start of the guide is with release mode.\nIn debug mode, it's actually a whopping 32mb! It also increases the speed of your app.\n\nWe can use the `--release` flag to create an optimized build of our application which will be both faster and smaller:\n\n`dx build --release`\n\n## UPX\n\nIf you're not targeting web, you can use the [UPX](https://github.com/upx/upx) CLI tool to compress your executables.\n\nSetup:\n\n* Download a [release](https://github.com/upx/upx/releases) and extract the directory inside to a sensible location.\n* Add the executable located in the directory to your path variable.\n\nYou can run `upx --help` to get the CLI options, but you should also view `upx-doc.html` for more detailed information.\nIt's included in the extracted directory.\n\nAn example command might be: `upx --best -o target/release/compressed.exe target/release/your-executable.exe`.\n\n## Build configuration\n\n*Note: Settings defined in `.cargo/config.toml` will override settings in `Cargo.toml`.*\n\nOther than the `--release` flag, this is the easiest way to optimize your projects, and also the most effective way,\nat least in terms of reducing binary size.\n\n### Stable\n\nThis configuration is 100% stable and decreases the binary size from 2.36mb to 310kb.\nAdd this to your `.cargo/config.toml`:\n\n````toml\n[profile.release]\nopt-level = \"z\"\ndebug = false\nlto = true\ncodegen-units = 1\npanic = \"abort\"\nstrip = true\nincremental = false\n````\n\nLinks to the documentation of each value:\n\n* [`opt-level`](https://doc.rust-lang.org/rustc/codegen-options/index.html#opt-level)\n* [`debug`](https://doc.rust-lang.org/rustc/codegen-options/index.html#debuginfo)\n* [`lto`](https://doc.rust-lang.org/rustc/codegen-options/index.html#lto)\n* [`codegen-units`](https://doc.rust-lang.org/rustc/codegen-options/index.html#codegen-units)\n* [`panic`](https://doc.rust-lang.org/rustc/codegen-options/index.html#panic)\n* [`strip`](https://doc.rust-lang.org/rustc/codegen-options/index.html#strip)\n* [`incremental`](https://doc.rust-lang.org/rustc/codegen-options/index.html#incremental)\n\n### Unstable\n\nThis configuration contains some unstable features, but it should work just fine.\nIt decreases the binary size from 310kb to 234kb.\nAdd this to your `.cargo/config.toml`:\n\n````toml\n[unstable]\nbuild-std = [\"std\", \"panic_abort\", \"core\", \"alloc\"]\nbuild-std-features = [\"panic_immediate_abort\"]\n\n[build]\nrustflags = [\n \"-Clto\",\n \"-Zvirtual-function-elimination\",\n \"-Zlocation-detail=none\"\n]\n\n# Same as in the Stable section\n[profile.release]\nopt-level = \"z\"\ndebug = false\nlto = true\ncodegen-units = 1\npanic = \"abort\"\nstrip = true\nincremental = false\n````\n\n*Note: The omitted space in each flag (e.g., `-Clto`) is intentional. It is not a typo.*\n\nThe values in `[profile.release]` are documented in the [Stable](#stable) section. Links to the documentation of each value:\n\n* [`[build.rustflags]`](https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags)\n* [`-C lto`](https://doc.rust-lang.org/rustc/codegen-options/index.html#lto)\n* [`-Z virtual-function-elimination`](https://doc.rust-lang.org/stable/unstable-book/compiler-flags/virtual-function-elimination.html)\n* [`-Z location-detail`](https://doc.rust-lang.org/stable/unstable-book/compiler-flags/location-detail.html)\n\n## wasm-opt\n\n*Note: In the future, `wasm-opt` will be supported natively through the [Dioxus CLI](https://crates.io/crates/dioxus-cli).*\n\n`wasm-opt` is a tool from the [binaryen](https://github.com/WebAssembly/binaryen) library that optimizes your WASM files.\nTo use it, install a [binaryen release](https://github.com/WebAssembly/binaryen/releases) and run this command from the package directory:\n\n````\nwasm-opt dist/assets/dioxus/APP_NAME_bg.wasm -o dist/assets/dioxus/APP_NAME_bg.wasm -Oz\n````\n\nThe `-Oz` flag specifies that `wasm-opt` should optimize for size. For speed, use `-O4`.\n\n## Improving Dioxus code\n\nLet's talk about how you can improve your Dioxus code to be more performant.\n\nIt's important to minimize the number of dynamic parts in your `rsx`, like conditional rendering.\nWhen Dioxus is rendering your component, it will skip parts that are the same as the last render.\nThat means that if you keep dynamic rendering to a minimum, your app will speed up, and quite a bit if it's not just hello world.\nTo see an example of this, check out [Dynamic Rendering](../reference/dynamic_rendering.md).\n\nAlso check out [Anti-patterns](antipatterns.md) for patterns that you should avoid.\nObviously, not all of them are just about performance, but some of them are.\n\n## Optimizing the size of assets\n\nAssets can be a significant part of your app's size. Dioxus includes alpha support for first party [assets](../guides/assets.md). Any assets you include with the `asset!` macro will be optimized for production in release builds." + } + 71usize => { + "# Dioxus Hot-Reloading Reference\n\nThe Dioxus Hot-Reload is very powerful. When used properly, it is by-far the fastest tool to build apps with Rust.\n\nDioxus 0.5 featured a slightly limited form of hot-reloading while Dioxus 0.6 drastically improved it.\n\n > \n > Currently Dioxus cannot hot-reload *Rust* code, only RSX markup. Usually, modifying Rust code requires a full rebuild.\n\nWe provide this text guide as a resource for the details of hot-reloading. This guide also has an accompanying video as well:\n\n\n\n## What can be hot-reloaded?\n\nWithin RSX, all elements and their properties can be hot-reloaded.\n\n````rust\nrsx! {\n div {}\n}\n````\n\n## What causes a full-rebuild?" + } + 72usize => { + "# Describing the UI\n\nDioxus is a *declarative* framework. This means that instead of telling Dioxus what to do (e.g. to \"create an element\" or \"set the color to red\") we simply *declare* what we want the UI to look like using RSX.\n\nYou have already seen a simple example of RSX syntax in the \"hello world\" application:\n\n````rust, no_run@hello_world_desktop.rs\n// define a component that renders a div with the text \"Hello, world!\"\nfn App() -> Element {\n rsx! {\n div { \"Hello, world!\" }\n }\n}\n````\n\nHere, 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.\n\n## RSX Features\n\nRSX is very similar to HTML in that it describes elements with attributes and children. Here's an empty `button` element in RSX, as well as the resulting HTML:\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n button {\n // attributes / listeners\n // children\n \"Hello, World!\"\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Button {}\n}\n````\n\n### Attributes\n\nAttributes (and [event handlers](event_handlers.md)) modify the behavior or appearance of the element they are attached to. They are specified inside the `{}` brackets, using the `name: value` syntax. You can provide the value as a literal in the RSX:\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n img {\n src: \"https://avatars.githubusercontent.com/u/79236386?s=200&v=4\",\n class: \"primary_button\",\n width: \"10px\",\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Attributes {}\n}\n````\n\nSome attributes, such as the `type` attribute for `input` elements won't work on their own in Rust. This is because `type` is a reserved Rust keyword. To get around this, Dioxus uses the `r#` specifier:\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n input { r#type: \"text\", color: \"red\" }\n}\n````\n\n > \n > Note: All attributes defined in `dioxus-html` follow the snake_case naming convention. They transform their `snake_case` names to HTML's `camelCase` attributes.\n\n > \n > Note: Styles can be used directly outside of the `style:` attribute. In the above example, `color: \"red\"` is turned into `style=\"color: red\"`.\n\n#### Conditional Attributes\n\nYou can also conditionally include attributes by using an if statement without an else branch. This is useful for adding an attribute only if a certain condition is met:\n\n````rust, no_run@rsx_overview.rs\nlet large_font = true;\nrsx! {\n div { class: if large_font { \"text-xl\" }, \"Hello, World!\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::ConditionalAttributes {}\n}\n````\n\n#### Custom Attributes\n\nDioxus 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:\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n div { \"style\": \"width: 20px; height: 20px; background-color: red;\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::CustomAttributes {}\n}\n````\n\n### Special Attributes\n\nWhile most attributes are simply passed on to the HTML, some have special behaviors.\n\n#### The HTML Escape Hatch\n\nIf 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`.\n\nFor 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):\n\n````rust, no_run@dangerous_inner_html.rs\n// this should come from a trusted source\nlet contents = \"live dangerously\";\n\nrsx! {\n div { dangerous_inner_html: \"{contents}\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\tdangerous_inner_html::App {}\n}\n````\n\n > \n > 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.\n > \n > 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.\n\n#### Boolean Attributes\n\nMost 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.\n\nSo this RSX wouldn't actually render the `hidden` attribute:\n\n````rust, no_run@boolean_attribute.rs\nrsx! {\n div { hidden: false, \"hello\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\tboolean_attribute::App {}\n}\n````\n\nNot all attributes work like this however. *Only the following attributes* have this behavior:\n\n* `allowfullscreen`\n* `allowpaymentrequest`\n* `async`\n* `autofocus`\n* `autoplay`\n* `checked`\n* `controls`\n* `default`\n* `defer`\n* `disabled`\n* `formnovalidate`\n* `hidden`\n* `ismap`\n* `itemscope`\n* `loop`\n* `multiple`\n* `muted`\n* `nomodule`\n* `novalidate`\n* `open`\n* `playsinline`\n* `readonly`\n* `required`\n* `reversed`\n* `selected`\n* `truespeed`\n\nFor any other attributes, a value of `\"false\"` will be sent directly to the DOM.\n\n### Interpolation\n\nSimilarly 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:\n\n````rust, no_run@rsx_overview.rs\nlet coordinates = (42, 0);\nlet country = \"es\";\nrsx! {\n div {\n class: \"country-{country}\",\n left: \"{coordinates.0:?}\",\n top: \"{coordinates.1:?}\",\n // arbitrary expressions are allowed,\n // as long as they don't contain `{}`\n div { \"{country.to_uppercase()}\" }\n div { \"{7*6}\" }\n // {} can be escaped with {{}}\n div { \"{{}}\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Formatting {}\n}\n````\n\n### Children\n\nTo add children to an element, put them inside the `{}` brackets after all attributes and listeners in the element. They can be other elements, text, or [components](components.md). For example, you could have an `ol` (ordered list) element, containing 3 `li` (list item) elements, each of which contains some text:\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n ol {\n li { \"First Item\" }\n li { \"Second Item\" }\n li { \"Third Item\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Children {}\n}\n````\n\n### Fragments\n\nYou can render multiple elements at the top level of `rsx!` and they will be automatically grouped.\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n p { \"First Item\" }\n p { \"Second Item\" }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::ManyRoots {}\n}\n````\n\n### Expressions\n\nYou can include arbitrary Rust expressions as children within RSX by surrounding your expression with `{}`s. Any expression that implements [IntoDynNode](https://docs.rs/dioxus-core/0.3/dioxus_core/trait.IntoDynNode.html) can be used within rsx. This is useful for displaying data from an [iterator](https://doc.rust-lang.org/stable/book/ch13-02-iterators.html#processing-a-series-of-items-with-iterators):\n\n````rust, no_run@rsx_overview.rs\nlet text = \"Dioxus\";\nrsx! {\n span {\n {text.to_uppercase()}\n // create a list of text from 0 to 9\n {(0..10).map(|i| rsx! {\n \"{i}\"\n })}\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Expression {}\n}\n````\n\n### Loops\n\nIn addition to iterators you can also use for loops directly within RSX:\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n // use a for loop where the body itself is RSX\n div {\n // create a list of text from 0 to 9\n for i in 0..3 {\n // NOTE: the body of the loop is RSX not a rust statement\n div { \"{i}\" }\n }\n }\n // iterator equivalent\n div {\n {(0..3).map(|i| rsx! {\n div { \"{i}\" }\n })}\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::Loops {}\n}\n````\n\n### If statements\n\nYou can also use if statements without an else branch within RSX:\n\n````rust, no_run@rsx_overview.rs\nrsx! {\n // use if statements without an else\n if true {\n div { \"true\" }\n }\n}\n````\n\n````inject-dioxus\nDemoFrame {\n\trsx_overview::IfStatements {}\n}\n````" + } + 69usize => { + "# How to Upgrade to Dioxus 0.6\n\nThis guide will outline the API changes between the `0.5` and `0.6` releases. The `0.6` release contains a breaking changes to:\n\n* The `Element` type\n* Prevent default\n* Assets with Manganis\n* `dioxus_logger` integration with `dioxus`\n* The `launch` function\n* The `eval` function\n* The `dioxus-fullstack` crate\n* The router crate\n* The `derive(Props)` macro\n* The `dioxus-core` crate\n* Custom renderer API\n* Global state management\n\n## Element\n\nThe element type has changed from `Option` to `Result`. This makes it possible to bubble up errors while rendering with the `?` operator, but it does remove the ability to return `None` from a component. Instead of returning `None`, you can return `VNode::empty()` or an empty `rsx!` macro.\n\nDioxus 0.5:\n\n````rust\nuse dioxus::prelude::*;\n\nfn app() -> Element {\n let number = use_signal(|| -1);\n\n if number() < 0 {\n // ❌ In dioxus 0.6, the element type is a result, so None values cannot be returned directly\n return None;\n }\n\n rsx! {\n \"Positive number: {number}\"\n }\n}\n````\n\nDioxus 0.6:\n\n````rust@migration.rs\nuse dioxus::prelude::*;\n\nfn app() -> Element {\n let number = use_signal(|| -1);\n\n if number() < 0 {\n // ✅ You can return VNode::empty() instead\n return VNode::empty();\n }\n if number() < 0 {\n // ✅ Or an empty rsx! macro\n return rsx! {};\n }\n\n rsx! {\n \"Positive number: {number}\"\n }\n}\n````\n\n## Prevent Default\n\nDioxus 0.1-0.5 used the `prevent_default` attribute to prevent default behavior of event handlers for every event. Dioxus 0.6 introduces more fine-grained control over preventing default behavior with the `prevent_default` function on the event type. Instead of setting the `prevent_default` attribute for all events you want to prevent, you can create event handlers that call `event.prevent_default()`.\n\nDioxus 0.5:\n\n````rust@migration.rs\nuse dioxus::prelude::*;\n\nfn app() -> Element {\n rsx! {\n a {\n href: \"https://dioxuslabs.com\",\n // ❌ The prevent default attribute is deprecated in dioxus 0.6\n prevent_default: \"onclick\",\n \"Don't navigate to dioxuslabs.com\"\n }\n }\n}\n````\n\nDioxus 0.6:\n\n````rust@migration.rs\nuse dioxus::prelude::*;\n\nfn app() -> Element {\n rsx! {\n a {\n href: \"https://dioxuslabs.com\",\n // ✅ Instead, you can call event.prevent_default() inside the event handler\n onclick: move |event| event.prevent_default(),\n \"Don't navigate to dioxuslabs.com\"\n }\n }\n}\n````\n\n > \n > Note: Since event handlers run on the server in Liveview, events cannot be prevented quickly inside the event handler. Because of this, the new `prevent_default` method does not prevent default behavior in Liveview.\n > \n > Instead you can use javascript inside the `onclick` handler to prevent default behavior.\n > \n > ````rust@migration.rs\n > use dioxus::prelude::*;\n > \n > fn app() -> Element {\n > rsx! {\n > a {\n > href: \"https://dioxuslabs.com\",\n > // ✅ In liveview, you can use javascript to prevent default behavior\n > \"onclick\": \"event.preventDefault()\",\n > \"Don't navigate to dioxuslabs.com\"\n > }\n > }\n > }\n > ````\n\n## Assets\n\nThe syntax of the `asset!` macro has changed in Dioxus 0.6. Instead of accepting a single argument with both the path and the configuration for the asset, you can now pass in the path as the first argument and the configuration as a optional second argument.\n\nThe path the `asset!` macro accepts has also changed. Previously, the macro used to accept absolute and relative paths where relative paths were relative to the current crate directory. Now the macro only accepts absolute paths which are resolved relative to the root of the crate.\n\nDioxus 0.5:\n\n````rust\nuse dioxus::prelude::*;\n\nfn app() -> Element {\n rsx! {\n img {\n src: asset!(image(\"./assets/static/bundle.png\").size(100, 100))\n }\n }\n}\n````\n\nDioxus 0.6:\n\n````rust@migration.rs\nuse dioxus::prelude::*;\n\nfn app() -> Element {\n rsx! {\n img {\n src: asset!(\"/assets/static/bundle.png\", ImageAssetOptions::new().with_size(ImageSize::Manual { width: 100, height: 100 }))\n }\n }\n}\n````\n\n## Logging\n\nDioxus 0.6 brings the `dioxus-logger` crate directly into dioxus itself.\n\nPreviously, you needed to add `dioxus-logger` to your Cargo.toml and then call its init function:\n\n````rs\n// cargo.toml:\n// dioxus-logger = \"0.5\"\n\nuse dioxus::prelude::*;\nuse tracing::Level;\n\nfn main() {\n // Init logger\n dioxus_logger::init(Level::INFO).expect(\"failed to init logger\");\n\n // Dioxus launch code\n dioxus::launch(app)\n}\n````\n\nNow, in Dioxus 0.6, the logger is implicit with `launch`. Simply call launch and the logger is initialized to a default log level. In development mode, the `Debug` tracing level is set, and in release only the `Info` level is set.\n\n````rust\nuse dioxus::prelude::*;\n\nfn main() {\n dioxus::launch(app);\n}\n````\n\nIf you still need to set the level manually or configure a custom subscriber, do that before `launch`. We expose the `initialize_default` function in case you need additional logging before your `launch` call:\n\n````rust\nuse dioxus::prelude::*;\n\nfn main() {\n dioxus::logger::initialize_default();\n\n tracing::info!(\"Logs received!\");\n\n dioxus::launch(app);\n}\n````\n\n## Launch\n\nThe `launch` function was removed from the prelude. You must now import the launch method from `dioxus` or use it by its full path:\n\n````rust\nuse dioxus::prelude::*;\n\nfn main() {\n // ❌ launch(app);\n dioxus::launch(app); // ✅\n}\n````\n\nSee for more details.\n\n## Eval\n\n* `eval` was moved from the prelude to the `document` module. You must now call it with `document::eval` instead of `eval`:\n\n````rust\nuse dioxus::prelude::*;\n\nfn app() -> Element {\n // ❌ use_effect(|| eval(\"console.log(1)\"));\n use_effect(|| document::eval(\"console.log(1)\")); // ✅\n\n rsx! {}\n}\n````\n\n* The `eval` feature flag was removed from the `dioxus-html` crate and the functionality of `EvalProvider` was moved to the new `dioxus-document` crate. Custom renderers must now provide a `Rc` context to the application to make `eval` and head elements work correctly. See for more details.\n* `Eval::recv` and `Eval::join` now returns any value that implements `DeserializeOwned` instead of `serde_json::Value`. `Eval::send` now accepts any value that implements `Serialize`. See for more details\n\n## Fullstack\n\n* The feature `dioxus/axum` was renamed to `dioxus/server`\n\n````toml\n[features]\ndefault = []\n# ❌ server = [\"dioxus/axum\"]\nserver = [\"dioxus/server\"] # ✅\nweb = [\"dioxus/web\"]\n````\n\nSee for more details\n\n* The `fullstack::Config` item was removed. You can now pass the platform configs into the `LaunchBuilder` directly. For example, if you want to set the rootname on each platform, you can set the root name in each config:\n\n````rust\nLaunchBuilder::new()\n // Only set the server config if the server feature is enabled\n .with_cfg(server_only! {\n ServeConfigBuilder::default().root_id(\"app\")\n })\n // You also need to set the root id in your web config\n .with_cfg(web! {\n dioxus::web::Config::default().rootname(\"app\")\n })\n // And desktop config\n .with_cfg(desktop! {\n dioxus::desktop::Config::default().with_root_name(\"app\")\n })\n .launch(app);\n````\n\nSee for more details.\n\n* The dioxus-cli now proxies fullstack applications at a port behind a reverse proxy. If you have a custom axum server, you must serve your application at the port returned by `dioxus_cli_config::server_port` and the address returned by `dioxus_cli_config::server_ip` or the complete address returned by `dioxus_cli_config::fullstack_address_or_localhost` during development:\n\n````rust\n#[cfg(feature = \"server\")]\n#[tokio::main]\nasync fn main() {\n // Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address\n // and we use the generated address the CLI gives us\n let address = dioxus_cli_config::fullstack_address_or_localhost();\n\n // Launch the fullstack application on the address the CLI is proxying\n let router = axum::Router::new()\n .serve_dioxus_application(ServeConfigBuilder::default(), App);\n\n let router = router.into_make_service();\n let listener = tokio::net::TcpListener::bind(address).await.unwrap();\n axum::serve(listener, router).await.unwrap();\n}\n````\n\nSee for more details.\n\n* `serve_dioxus_application` was changed to accept a component directly instead of a virtual dom factory. See for more details.\n* `register_server_fns` was renamed to `register_server_functions`. See for more details.\n* `RenderHandleState::new` accepts a new `ServeConfig` argument. See for more details.\n* `ServeConfigBuilder::build` returns a result. It may fail during desktop builds if no `index.html` file is found. This error is fine to ignore in desktop builds. You can pass the builder directly to `serve_dioxus_application` to only serve the index.html file if it exists. See for more details.\n* `dioxus_fullstack::Config::addr` was removed. You can now export the `PORT` and `IP` environment variables to set the address the `launch` method uses for the server.\n\n## Router\n\n* The `Routable` derive macro no longer accepts fields that are not present in the `route(\"/route\")` if the web feature is enabled. See for more details.\n* The `ToRouteSegments` trait in the router was changed from accepting `self` to accepting `&self`. This means you can now implement it for `T` directly instead of `&T`. See for more details.\n\n## derive(Props)\n\n* `#[props(into)]` is ignore on any String props. String props already accept `impl ToString` which is implemented for many of the same types, but if you implement `Into` for a specific type, your code may require some changes. See for more details\n* Properties that start with an uppercase letter are no longer accepted. This allows us to autocomplete Components. See for more details.\n\n## State Management\n\n* `use_coroutine` now accepts `impl FnMut` instead of `impl FnOnce`. This was required to support restarting the coroutine without rerunning the component. See for more details.\n* `Signal::global_memo` now requires `T: PartialEq` just like `use_memo`. See for more details.\n* `GlobalMemo` is now a trait alias for `Global, T>` and `GlobalSignal` is now a trait alias for `Global, T>`. To get the underlying `Memo` or `Signal`, you can now use the `resolve` method instead of `signal` or `memo`. See for more details.\n* The `Readable` trait in dioxus signals now requires a `try_peek_unchecked` method instead of `peek_unchecked`. See for more details.\n* The `check_generation` feature flag was removed from the `generational-box` crate. See for more details.\n\n## Core changes\n\n* The `Template::name` field was removed. See for more details.\n* `Properties::into_vcomponent` now accepts only the `render_fn` instead of the `render_fn` and `component_name`. This change fixes the name of re-exported components. Fixes \n* The field `VNode::template` is now `Template` instead of `Cell