Merge branch 'master' into feature/use-shared-state-better-diagnostics

This commit is contained in:
Jonathan Kelley 2023-07-20 10:51:10 -07:00 committed by GitHub
commit 91d4207fa7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
324 changed files with 13509 additions and 6646 deletions

View file

@ -3,6 +3,10 @@ name: docs stable
on: on:
workflow_dispatch: workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
build-deploy: build-deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -29,7 +33,7 @@ jobs:
# cd fermi && mdbook build -d ../nightly/fermi && cd .. # cd fermi && mdbook build -d ../nightly/fermi && cd ..
- name: Deploy 🚀 - name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4.4.2 uses: JamesIves/github-pages-deploy-action@v4.4.3
with: with:
branch: gh-pages # The branch the action should deploy to. branch: gh-pages # The branch the action should deploy to.
folder: docs/nightly # The folder the action should deploy. folder: docs/nightly # The folder the action should deploy.

View file

@ -8,6 +8,10 @@ on:
branches: branches:
- master - master
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
build-deploy: build-deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -34,7 +38,7 @@ jobs:
# cd fermi && mdbook build -d ../nightly/fermi && cd .. # cd fermi && mdbook build -d ../nightly/fermi && cd ..
- name: Deploy 🚀 - name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4.4.2 uses: JamesIves/github-pages-deploy-action@v4.4.3
with: with:
branch: gh-pages # The branch the action should deploy to. branch: gh-pages # The branch the action should deploy to.
folder: docs/nightly # The folder the action should deploy. folder: docs/nightly # The folder the action should deploy.

View file

@ -1,36 +0,0 @@
name: macOS tests
on:
push:
branches:
- master
paths:
- packages/**
- examples/**
- src/**
- .github/**
- lib.rs
- Cargo.toml
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- master
paths:
- packages/**
- examples/**
- src/**
- .github/**
- lib.rs
- Cargo.toml
jobs:
test:
if: github.event.pull_request.draft == false
name: Test Suite
runs-on: macos-latest
steps:
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/checkout@v3
- run: cargo test --all --tests

View file

@ -13,7 +13,7 @@ on:
- lib.rs - lib.rs
- Cargo.toml - Cargo.toml
- Makefile.toml - Makefile.toml
- playwrite-tests/** - playwright-tests/**
pull_request: pull_request:
types: [opened, synchronize, reopened, ready_for_review] types: [opened, synchronize, reopened, ready_for_review]
@ -27,6 +27,10 @@ on:
- lib.rs - lib.rs
- Cargo.toml - Cargo.toml
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
check: check:
if: github.event.pull_request.draft == false if: github.event.pull_request.draft == false
@ -79,6 +83,69 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- run: cargo clippy --workspace --examples --tests -- -D warnings - run: cargo clippy --workspace --examples --tests -- -D warnings
matrix_test:
runs-on: ${{ matrix.platform.os }}
strategy:
matrix:
platform:
- {
target: x86_64-pc-windows-msvc,
os: windows-latest,
toolchain: '1.70.0',
cross: false,
command: 'test',
args: '--all --tests'
}
- {
target: x86_64-apple-darwin,
os: macos-latest,
toolchain: '1.70.0',
cross: false,
command: 'test',
args: '--all --tests'
}
- {
target: aarch64-apple-ios,
os: macos-latest,
toolchain: '1.70.0',
cross: false,
command: 'build',
args: '--package dioxus-mobile'
}
- {
target: aarch64-linux-android,
os: ubuntu-latest,
toolchain: '1.70.0',
cross: true,
command: 'build',
args: '--package dioxus-mobile'
}
steps:
- uses: actions/checkout@v2
- name: install stable
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.platform.toolchain }}
target: ${{ matrix.platform.target }}
override: true
default: true
- uses: Swatinem/rust-cache@v2
with:
workspaces: core -> ../target
save-if: ${{ matrix.features.key == 'all' }}
- name: test
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.platform.cross }}
command: ${{ matrix.platform.command }}
args: --target ${{ matrix.platform.target }} ${{ matrix.platform.args }}
# Coverage is disabled until we can fix it # Coverage is disabled until we can fix it
# coverage: # coverage:
# name: Coverage # name: Coverage
@ -99,3 +166,4 @@ jobs:
# uses: codecov/codecov-action@v2 # uses: codecov/codecov-action@v2
# with: # with:
# fail_ci_if_error: false # fail_ci_if_error: false

View file

@ -6,6 +6,13 @@ on:
branches: branches:
- 'auto' - 'auto'
- 'try' - 'try'
paths:
- packages/**
- examples/**
- src/**
- .github/**
- lib.rs
- Cargo.toml
pull_request: pull_request:
types: [opened, synchronize, reopened, ready_for_review] types: [opened, synchronize, reopened, ready_for_review]
branches: branches:
@ -31,7 +38,9 @@ env:
# - tokio-stream/Cargo.toml # - tokio-stream/Cargo.toml
# rust_min: 1.49.0 # rust_min: 1.49.0
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
test: test:
@ -72,7 +81,7 @@ jobs:
# #[tokio::main] that calls epoll_create1 that Miri does not support. # #[tokio::main] that calls epoll_create1 that Miri does not support.
# run: cargo miri test --features full --lib --no-fail-fast # run: cargo miri test --features full --lib --no-fail-fast
run: | run: |
cargo miri test --package dioxus-core --test miri_stress -- --exact --nocapture cargo miri test --package dioxus-core -- --exact --nocapture
cargo miri test --package dioxus-native-core --test miri_native -- --exact --nocapture cargo miri test --package dioxus-native-core --test miri_native -- --exact --nocapture
# working-directory: tokio # working-directory: tokio

View file

@ -4,22 +4,25 @@ on:
branches: [ main, master ] branches: [ main, master ]
pull_request: pull_request:
branches: [ main, master ] branches: [ main, master ]
defaults:
run:
working-directory: ./playwright-tests
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
test: test:
if: github.event.pull_request.draft == false if: github.event.pull_request.draft == false
timeout-minutes: 60 timeout-minutes: 60
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
# Do our best to cache the toolchain and node install steps
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 16
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npm install -D @playwright/test
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Install Rust - name: Install Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
@ -29,11 +32,18 @@ jobs:
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- name: Install WASM toolchain - name: Install WASM toolchain
run: rustup target add wasm32-unknown-unknown run: rustup target add wasm32-unknown-unknown
- name: Install Dioxus CLI - name: Install dependencies
uses: actions-rs/cargo@v1 run: npm ci
with: - name: Install Playwright
command: install run: npm install -D @playwright/test
args: --path packages/cli - name: Install Playwright Browsers
run: npx playwright install --with-deps
# Cache the CLI by using cargo run internally
# - name: Install Dioxus CLI
# uses: actions-rs/cargo@v1
# with:
# command: install
# args: --path packages/cli
- name: Run Playwright tests - name: Run Playwright tests
run: npx playwright test run: npx playwright test
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3

View file

@ -1,88 +0,0 @@
name: windows
on:
push:
branches:
- master
paths:
- packages/**
- examples/**
- src/**
- .github/**
- lib.rs
- Cargo.toml
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- master
paths:
- packages/**
- examples/**
- src/**
- .github/**
- lib.rs
- Cargo.toml
jobs:
test:
if: github.event.pull_request.draft == false
runs-on: windows-latest
name: (${{ matrix.target }}, ${{ matrix.cfg_release_channel }})
env:
CFG_RELEASE_CHANNEL: ${{ matrix.cfg_release_channel }}
strategy:
# https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions#usage-limits
# There's a limit of 60 concurrent jobs across all repos in the rust-lang organization.
# In order to prevent overusing too much of that 60 limit, we throttle the
# number of rustfmt jobs that will run concurrently.
# max-parallel:
# fail-fast: false
matrix:
target: [x86_64-pc-windows-gnu, x86_64-pc-windows-msvc]
cfg_release_channel: [stable]
steps:
# The Windows runners have autocrlf enabled by default
# which causes failures for some of rustfmt's line-ending sensitive tests
- name: disable git eol translation
run: git config --global core.autocrlf false
# Run build
- name: Install Rustup using win.rustup.rs
run: |
# Disable the download progress bar which can cause perf issues
$ProgressPreference = "SilentlyContinue"
Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe
.\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --default-toolchain=none
del rustup-init.exe
rustup target add ${{ matrix.target }}
shell: powershell
- name: Add mingw64 to path for x86_64-gnu
run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH
if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly'
shell: bash
# - name: checkout
# uses: actions/checkout@v3
# with:
# path: C:/dioxus.git
# fetch-depth: 1
# we need to use the C drive as the working directory
- name: Checkout
run: |
mkdir C:/dioxus.git
git clone https://github.com/dioxuslabs/dioxus.git C:/dioxus.git --depth 1
- name: test
working-directory: C:/dioxus.git
run: |
rustc -Vv
cargo -V
set RUST_BACKTRACE=1
cargo build --all --tests --examples
cargo test --all --tests
shell: cmd

4
.gitignore vendored
View file

@ -1,6 +1,6 @@
/target /target
/playwrite-tests/web/dist /playwright-tests/web/dist
/playwrite-tests/fullstack/dist /playwright-tests/fullstack/dist
/dist /dist
Cargo.lock Cargo.lock
.DS_Store .DS_Store

View file

@ -4,6 +4,7 @@ members = [
"packages/core", "packages/core",
"packages/cli", "packages/cli",
"packages/core-macro", "packages/core-macro",
"packages/router-macro",
"packages/extension", "packages/extension",
"packages/router", "packages/router",
"packages/html", "packages/html",
@ -29,16 +30,19 @@ members = [
"packages/fullstack/examples/axum-hello-world", "packages/fullstack/examples/axum-hello-world",
"packages/fullstack/examples/axum-router", "packages/fullstack/examples/axum-router",
"packages/fullstack/examples/axum-desktop", "packages/fullstack/examples/axum-desktop",
"packages/fullstack/examples/axum-auth",
"packages/fullstack/examples/salvo-hello-world", "packages/fullstack/examples/salvo-hello-world",
"packages/fullstack/examples/warp-hello-world", "packages/fullstack/examples/warp-hello-world",
"packages/fullstack/examples/static-hydrated",
"docs/guide", "docs/guide",
"docs/router",
# Full project examples # Full project examples
"examples/tailwind", "examples/tailwind",
"examples/PWA-example", "examples/PWA-example",
# Playwrite tests # Playwright tests
"playwrite-tests/liveview", "playwright-tests/liveview",
"playwrite-tests/web", "playwright-tests/web",
"playwrite-tests/fullstack", "playwright-tests/fullstack",
] ]
exclude = ["examples/mobile_demo"] exclude = ["examples/mobile_demo"]
@ -48,6 +52,7 @@ dioxus = { path = "packages/dioxus" }
dioxus-core = { path = "packages/core" } dioxus-core = { path = "packages/core" }
dioxus-core-macro = { path = "packages/core-macro" } dioxus-core-macro = { path = "packages/core-macro" }
dioxus-router = { path = "packages/router" } dioxus-router = { path = "packages/router" }
dioxus-router-macro = { path = "packages/router-macro" }
dioxus-html = { path = "packages/html" } dioxus-html = { path = "packages/html" }
dioxus-hooks = { path = "packages/hooks" } dioxus-hooks = { path = "packages/hooks" }
dioxus-web = { path = "packages/web" } dioxus-web = { path = "packages/web" }

View file

@ -3,7 +3,7 @@ name = "dioxus-guide"
version = "0.0.1" version = "0.0.1"
edition = "2021" edition = "2021"
description = "Dioxus guide, including testable examples" description = "Dioxus guide, including testable examples"
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
publish = false publish = false
[dev-dependencies] [dev-dependencies]

View file

@ -74,10 +74,9 @@ fn app(cx: Scope<usize>) -> Element {
button { button {
onclick: move |_| { onclick: move |_| {
to_owned![count]; to_owned![count];
let sc = cx.sc();
async move { async move {
// Call the server function just like a local async function // Call the server function just like a local async function
if let Ok(new_count) = double_server(sc, *count.current()).await { if let Ok(new_count) = double_server(*count.current()).await {
count.set(new_count); count.set(new_count);
} }
} }
@ -89,7 +88,8 @@ fn app(cx: Scope<usize>) -> Element {
// We use the "getcbor" encoding to make caching easier // We use the "getcbor" encoding to make caching easier
#[server(DoubleServer, "", "getcbor")] #[server(DoubleServer, "", "getcbor")]
async fn double_server(cx: DioxusServerContext, number: usize) -> Result<usize, ServerFnError> { async fn double_server(number: usize) -> Result<usize, ServerFnError> {
let cx = server_context();
// Perform some expensive computation or access a database on the server // Perform some expensive computation or access a database on the server
tokio::time::sleep(std::time::Duration::from_secs(1)).await; tokio::time::sleep(std::time::Duration::from_secs(1)).await;
let result = number * 2; let result = number * 2;

View file

@ -92,10 +92,9 @@ fn app(cx: Scope<usize>) -> Element {
button { button {
onclick: move |_| { onclick: move |_| {
to_owned![count]; to_owned![count];
let sc = cx.sc();
async move { async move {
// Call the server function just like a local async function // Call the server function just like a local async function
if let Ok(new_count) = double_server(sc, *count.current()).await { if let Ok(new_count) = double_server(*count.current()).await {
count.set(new_count); count.set(new_count);
} }
} }
@ -107,10 +106,11 @@ fn app(cx: Scope<usize>) -> Element {
// We use the "getcbor" encoding to make caching easier // We use the "getcbor" encoding to make caching easier
#[server(DoubleServer, "", "getcbor")] #[server(DoubleServer, "", "getcbor")]
async fn double_server(cx: DioxusServerContext, number: usize) -> Result<usize, ServerFnError> { async fn double_server(number: usize) -> Result<usize, ServerFnError> {
// Perform some expensive computation or access a database on the server // Perform some expensive computation or access a database on the server
tokio::time::sleep(std::time::Duration::from_secs(1)).await; tokio::time::sleep(std::time::Duration::from_secs(1)).await;
let result = number * 2; let result = number * 2;
let cx = server_context();
println!( println!(
"User Agent {:?}", "User Agent {:?}",

View file

@ -21,10 +21,12 @@
- [Hooks & Component State](interactivity/hooks.md) - [Hooks & Component State](interactivity/hooks.md)
- [User Input](interactivity/user_input.md) - [User Input](interactivity/user_input.md)
- [Sharing State](interactivity/sharing_state.md) - [Sharing State](interactivity/sharing_state.md)
- [Memoization](interactivity/memoization.md)
- [Custom Hooks](interactivity/custom_hooks.md) - [Custom Hooks](interactivity/custom_hooks.md)
- [Dynamic Rendering](interactivity/dynamic_rendering.md) - [Dynamic Rendering](interactivity/dynamic_rendering.md)
- [Routing](interactivity/router.md) - [Routing](interactivity/router.md)
- [Async](async/index.md) - [Async](async/index.md)
- [UseEffect](async/use_effect.md)
- [UseFuture](async/use_future.md) - [UseFuture](async/use_future.md)
- [UseCoroutine](async/use_coroutine.md) - [UseCoroutine](async/use_coroutine.md)
- [Spawning Futures](async/spawn.md) - [Spawning Futures](async/spawn.md)

View file

@ -94,8 +94,10 @@ Calling `deref` or `deref_mut` is actually more complex than it seems. When a va
Sometimes you want a signal to propagate across your app, either through far-away siblings or through deeply-nested components. In these cases, we use Dirac: Dioxus's first-class state management toolkit. Dirac atoms automatically implement the Signal API. This component will bind the input element to the `TITLE` atom. Sometimes you want a signal to propagate across your app, either through far-away siblings or through deeply-nested components. In these cases, we use Dirac: Dioxus's first-class state management toolkit. Dirac atoms automatically implement the Signal API. This component will bind the input element to the `TITLE` atom.
```rust, no_run ```rust, no_run
const TITLE: Atom<String> = || "".to_string(); const TITLE: Atom<String> = Atom(|| "".to_string());
const Provider: Component = |cx|{ const Provider: Component = |cx|{
let title = use_signal(cx, &TITLE); let title = use_signal(cx, &TITLE);
render!(input { value: title }) render!(input { value: title })
@ -131,7 +133,8 @@ By default, Dioxus is limited when you use iter/map. With the `For` component, y
Dioxus automatically understands how to use your signals when mixed with iterators through `Deref`/`DerefMut`. This lets you efficiently map collections while avoiding the re-rendering of lists. In essence, signals act as a hint to Dioxus on how to avoid un-necessary checks and renders, making your app faster. Dioxus automatically understands how to use your signals when mixed with iterators through `Deref`/`DerefMut`. This lets you efficiently map collections while avoiding the re-rendering of lists. In essence, signals act as a hint to Dioxus on how to avoid un-necessary checks and renders, making your app faster.
```rust, no_run ```rust, no_run
const DICT: AtomFamily<String, String> = |_| {}; const DICT: AtomFamily<String, String> = AtomFamily(|_| {});
const List: Component = |cx|{ const List: Component = |cx|{
let dict = use_signal(cx, &DICT); let dict = use_signal(cx, &DICT);
cx.render(rsx!( cx.render(rsx!(
@ -142,14 +145,6 @@ const List: Component = |cx|{
}; };
``` ```
## Remote Signals
Apps that use signals will enjoy a pleasant hybrid of server-side and client-side rendering.
```rust, no_run
```
## How does it work? ## How does it work?
Signals internally use Dioxus' asynchronous rendering infrastructure to perform updates out of the tree. Signals internally use Dioxus' asynchronous rendering infrastructure to perform updates out of the tree.

View file

@ -21,7 +21,7 @@ fn runs_in_browser() {
Then, when you run Then, when you run
```console ```console
dioxus test --chrome dx test --chrome
``` ```
Dioxus will build and test your code using the Chrome browser as a harness. Dioxus will build and test your code using the Chrome browser as a harness.

View file

@ -143,7 +143,7 @@ async fn editor_service(rx: UnboundedReceiver<EditorCommand>) {
We 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. We 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.
```rust, no_run ```rust, no_run
static USERNAME: Atom<String> = |_| "default".to_string(); static USERNAME: Atom<String> = Atom(|_| "default".to_string());
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let atoms = use_atom_root(cx); let atoms = use_atom_root(cx);
@ -156,7 +156,7 @@ fn app(cx: Scope) -> Element {
} }
fn Banner(cx: Scope) -> Element { fn Banner(cx: Scope) -> Element {
let username = use_read(cx, USERNAME); let username = use_read(cx, &USERNAME);
cx.render(rsx!{ cx.render(rsx!{
h1 { "Welcome back, {username}" } h1 { "Welcome back, {username}" }
@ -174,8 +174,8 @@ enum SyncAction {
} }
async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) { async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
let username = atoms.write(USERNAME); let username = atoms.write(&USERNAME);
let errors = atoms.write(ERRORS); let errors = atoms.write(&ERRORS);
while let Ok(msg) = rx.next().await { while let Ok(msg) = rx.next().await {
match msg { match msg {

View file

@ -0,0 +1,41 @@
# UseEffect
[`use_effect`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_effect.html) lets you run a callback that returns a future, which will be re-run when its [dependencies](#dependencies) change. This is useful to syncrhonize with external events.
## Dependencies
You can make the callback re-run when some value changes. For example, you might want to fetch a user's data only when the user id changes. You can provide a tuple of "dependencies" to the hook. It will automatically re-run it when any of those dependencies change.
## Example
```rust, no_run
#[inline_props]
fn Profile(cx: Scope, id: usize) -> Element {
let name = use_state(cx, || None);
// Only fetch the user data when the id changes.
use_effect(cx, (id,), |(id,)| {
to_owned![name];
async move {
let user = fetch_user(id).await;
name.set(user.name);
}
});
// Because the dependencies are empty, this will only run once.
// An empty tuple is always equal to an empty tuple.
use_effect(cx, (), |()| async move {
println!("Hello, World!");
});
let name = name.get().clone().unwrap_or("Loading...".to_string());
render!(
p { "{name}" }
)
}
fn app(cx: Scope) -> Element {
render!(Profile { id: 0 })
}
```

View file

@ -113,14 +113,14 @@ enum InputError {
TooShort, TooShort,
} }
static INPUT_ERROR: Atom<InputError> = |_| InputError::None; static INPUT_ERROR: Atom<InputError> = Atom(|_| InputError::None);
``` ```
Then, in our top level component, we want to explicitly handle the possible error state for this part of the tree. Then, in our top level component, we want to explicitly handle the possible error state for this part of the tree.
```rust, no_run ```rust, no_run
fn TopLevel(cx: Scope) -> Element { fn TopLevel(cx: Scope) -> Element {
let error = use_read(cx, INPUT_ERROR); let error = use_read(cx, &INPUT_ERROR);
match error { match error {
TooLong => return cx.render(rsx!{ "FAILED: Too long!" }), TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
@ -134,7 +134,7 @@ Now, whenever a downstream component has an error in its actions, it can simply
```rust, no_run ```rust, no_run
fn Commandline(cx: Scope) -> Element { fn Commandline(cx: Scope) -> Element {
let set_error = use_set(cx, INPUT_ERROR); let set_error = use_set(cx, &INPUT_ERROR);
cx.render(rsx!{ cx.render(rsx!{
input { input {

View file

@ -14,13 +14,13 @@ We start will a hello world program. This program renders a desktop app with the
## The rsx! Macro ## The rsx! Macro
Before the Rust compiler runs the program, it will expand all macros. Here is what the hello world example looks like expanded: Before 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:
```rust, no_run ```rust, no_run
{{#include ../../../examples/readme_expanded.rs}} {{#include ../../../examples/readme_expanded.rs}}
``` ```
The rsx macro separates the static parts of the rsx (the template) and the dynamic parts (the dynamic_nodes and dynamic_attributes). The 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)).
The static template only contains the parts of the rsx that cannot change at runtime with holes for the dynamic parts: The static template only contains the parts of the rsx that cannot change at runtime with holes for the dynamic parts:
@ -32,17 +32,17 @@ The dynamic_nodes and dynamic_attributes are the parts of the rsx that can chang
## Launching the App ## Launching the App
The 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. This guide will not explain the renderer in-depth, but you can read more about it in the [custom renderer](/guide/custom-renderer) section. The 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](/guide/custom-renderer) section.
## The Virtual DOM ## The Virtual DOM
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. 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.
The Virtual Dom roughly looks like this: The Virtual DOM roughly looks like this:
```rust, no_run ```rust, no_run
pub struct VirtualDom { pub struct VirtualDom {
// All the templates that have been created or set durring hot reloading // All the templates that have been created or set during hot reloading
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>, pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
// A slab of all the scopes that have been created // A slab of all the scopes that have been created
@ -63,64 +63,74 @@ pub struct VirtualDom {
``` ```
> What is a [slab](https://docs.rs/slab/latest/slab/)? > What is a [slab](https://docs.rs/slab/latest/slab/)?
>
> 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. > 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.
> How does Dioxus use slabs? > How does Dioxus use slabs?
> Dioxus uses "synchronized slabs" to communicate between the renderer and the VDOM. When an node is created in the Virtual Dom, a ElementId is passed along with the mutation to the renderer to identify the node. These ids are used by the Virtual Dom to reference that nodes 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 the node in the slab and then run the necessary event handlers. > 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.
The 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. The 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.
Scopes serve three main purposes: Scopes serve three main purposes:
1. They store the state of hooks used by the component 1. They store the state of hooks used by the component
2. They store the state for the context API 2. They store the state for the context API (for example: using
3. They store the current and previous VNode that was rendered for diffing [use_shared_state_provider](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_shared_state_provider.html)).
3. They store the current and previous versions of the `VNode` that was rendered, so they can be
diffed to generate the set of mutations needed to re-render it.
### The Initial Render ### The Initial Render
The root scope is created and rebuilt: The root scope is created and rebuilt:
1. The root component is run 1. The root component is run
2. The root component returns a VNode 2. The root component returns a `VNode`
3. Mutations for the VNode are created and added to the mutation list (this may involve creating new child components) 3. Mutations for this `VNode` are created and added to the mutation list (this may involve creating new child components)
4. The VNode is stored in the root scope 4. The `VNode` is stored in the root's `Scope`.
After the root scope is built, the mutations are sent to the renderer to be applied to the dom. After the root's `Scope` is built, all generated mutations are sent to the renderer, which applies them to the DOM.
After the initial render, the root scope looks like this: After the initial render, the root `Scope` looks like this:
[![](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) [![](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)
### Waiting for Events ### Waiting for Events
The Virtual Dom will only ever rerender 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. The 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.
There are generally two ways a scope is marked as dirty: There are generally two ways a scope is marked as dirty:
1. The renderer triggers an event: This causes an event listener to be called if needed which may mark a component as dirty 1. The renderer triggers an event: An event listener on this event may be called, which may mark a
2. The renderer calls wait for work: This polls futures which may mark a component as dirty component as dirty, if processing the event resulted in any generated any mutations.
2. The renderer calls
[`wait_for_work`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.VirtualDom.html#method.wait_for_work):
This polls dioxus internal future queue. One of these futures may mark a component as dirty.
Once at least one scope is marked as dirty, the renderer can call `render_with_deadline` to diff the dirty scopes. Once 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.
### Diffing Scopes ### Diffing Scopes
If the user clicked the "up high" button, the root scope would be marked as dirty by the use_state hook. Once the desktop renderer calls `render_with_deadline`, the root scope would be diffed. When 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`.
To start the diffing process, the component is run. After the root component is run it will look like this: To start the diffing process, the component function is run. After the root component is run it, the root `Scope` will look like this:
[![](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) [![](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)
Next, the Virtual Dom will compare the new VNode with the previous VNode and only update the parts of the tree that have changed. 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.
When a component is re-rendered, the Virtual Dom will compare the new VNode with the previous VNode and only update the parts of the tree that have changed.
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. 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.
Here 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) Here 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)
[![](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) [![](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)
## Conclusion ## Conclusion
This is only a brief overview of how the Virtual Dom works. There are several aspects not yet covered in this guide including how the Virtual Dom handles async-components, keyed diffing, and how it uses [bump allocation](https://github.com/fitzgen/bumpalo) to efficiently allocate VNodes. If 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). This is only a brief overview of how the Virtual Dom works. There are several aspects not yet covered in this guide including:
* How the Virtual DOM handles async-components
* Keyed diffing
* Using [bump allocation](https://github.com/fitzgen/bumpalo) to efficiently allocate VNodes.
If 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).

View file

@ -76,7 +76,7 @@ Next, we need to modify our `main.rs` to use either hydrate on the client or ren
{{#include ../../../examples/hydration.rs}} {{#include ../../../examples/hydration.rs}}
``` ```
Now, build your client-side bundle with `dioxus build --features web` and run your server with `cargo run --features ssr`. You should see the same page as before, but now you can interact with the buttons! Now, build your client-side bundle with `dx build --features web` and run your server with `cargo run --features ssr`. You should see the same page as before, but now you can interact with the buttons!
## Sycronizing props between the server and client ## Sycronizing props between the server and client
@ -99,4 +99,4 @@ The only thing we need to change on the client is the props. `dioxus-fullstack`
{{#include ../../../examples/hydration_props.rs}} {{#include ../../../examples/hydration_props.rs}}
``` ```
Now, build your client-side bundle with `dioxus build --features web` and run your server with `cargo run --features ssr`. Navigate to `http://localhost:8080/1` and you should see the counter start at 1. Navigate to `http://localhost:8080/2` and you should see the counter start at 2. Now, build your client-side bundle with `dx build --features web` and run your server with `cargo run --features ssr`. Navigate to `http://localhost:8080/1` and you should see the counter start at 1. Navigate to `http://localhost:8080/2` and you should see the counter start at 2.

View file

@ -24,7 +24,7 @@ Next, add the server function to your `main.rs`:
{{#include ../../../examples/server_function.rs}} {{#include ../../../examples/server_function.rs}}
``` ```
Now, build your client-side bundle with `dioxus build --features web` and run your server with `cargo run --features ssr`. You should see a new button that multiplies the count by 2. Now, 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.
## Conclusion ## Conclusion

View file

@ -18,7 +18,7 @@ Hot reloading is automatically enabled when using the web renderer on debug buil
1. Run: 1. Run:
```bash ```bash
dioxus serve --hot-reload dx serve --hot-reload
``` ```
2. Change some code within a rsx or render macro 2. Change some code within a rsx or render macro

View file

@ -59,5 +59,5 @@ Edit your `main.rs`:
And to serve our app: And to serve our app:
```bash ```bash
dioxus serve dx serve
``` ```

View file

@ -2,15 +2,16 @@
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. 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.
Hooks 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. Hooks 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.
## `use_state` Hook ## `use_state` Hook
[`use_state`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_state.html) is one of the simplest hooks. [`use_state`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_state.html) is one of the simplest hooks.
- You provide a closure that determines the initial value - You provide a closure that determines the initial value: `let mut count = use_state(cx, || 0);`
- `use_state` gives you the current value, and a way to update it by setting it to something else - `use_state` gives you the current value, and a way to update it by setting it to something else
- When the value updates, `use_state` makes the component re-render, and provides you with the new value - 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.
For example, you might have seen the counter example, in which state (a number) is tracked using the `use_state` hook: For example, you might have seen the counter example, in which state (a number) is tracked using the `use_state` hook:
@ -45,10 +46,11 @@ But how can Dioxus differentiate between multiple hooks in the same component? A
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: 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:
1. Hooks may be only used in components or other hooks (we'll get to that later) 1. Hooks may be only used in components or other hooks (we'll get to that later)
2. On every call to the component function 2. On every call to a component function
1. The same hooks must be called (except in the case of early returns, as explained later in the [Error Handling chapter](../best_practices/error_handling.md)) 1. The same hooks must be called (except in the case of early returns, as explained later in the [Error Handling chapter](../best_practices/error_handling.md))
2. In the same order 2. In the same order
3. Hooks name's should start with `use_` so you don't accidentally confuse them with regular functions 3. Hook names should start with `use_` so you don't accidentally confuse them with regular
functions (`use_state()`, `use_ref()`, `use_future()`, etc...)
These rules mean that there are certain things you can't do with hooks: These rules mean that there are certain things you can't do with hooks:
@ -74,9 +76,12 @@ These rules mean that there are certain things you can't do with hooks:
`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? `use_state` is great for tracking simple values. However, you may notice in the [`UseState` API](https://docs.rs/dioxus/latest/dioxus/hooks/struct.UseState.html) that the only way to modify its value is to replace it with something else (e.g., by calling `set`, or through one of the `+=`, `-=` operators). This works well when it is cheap to construct a value (such as any primitive). But what if you want to maintain more complex data in the components state?
For example, suppose we want to maintain a `Vec` of values. If we stored it with `use_state`, the only way to add a new value to the list would be to create a new `Vec` with the additional value, and put it in the state. This is expensive! We want to modify the existing `Vec` instead. For example, suppose we want to maintain a `Vec` of values. If we stored it with `use_state`, the
only way to add a new value to the list would be to copy the existing `Vec`, add our value to it,
and then replace the existing `Vec` in the state with it. This is expensive! We want to modify the
existing `Vec` instead.
Thankfully, there is another hook for that, `use_ref`! It is similar to `use_state`, but it lets you get a mutable reference to the contained data. Thankfully, there is another hook for that, `use_ref`! It **is** similar to `use_state`, but it lets you get a mutable reference to the contained data.
Here's a simple example that keeps a list of events in a `use_ref`. We can acquire write access to the state with `.with_mut()`, and then just `.push` a new value to the state: Here'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:
@ -84,4 +89,18 @@ Here's a simple example that keeps a list of events in a `use_ref`. We can acqui
{{#include ../../../examples/hooks_use_ref.rs:component}} {{#include ../../../examples/hooks_use_ref.rs:component}}
``` ```
> 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. > The return values of `use_state` and `use_ref` (
> [`UseState`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseState.html) and
> [`UseRef`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.UseRef.html), 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.
## Additional Resources
- [**dioxus_hooks** ](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/) rustdoc
- Documents all hook types included with dioxus by default Most of these are also covered in
later chapters of this guide.
- [Hooks Package](https://github.com/DioxusLabs/dioxus/tree/master/packages/hooks)

View file

@ -0,0 +1,19 @@
# Memoization
[`use_memo`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_memo.html) let's you memorize values and thus save computation time. This is useful for expensive calculations.
```rust, no_run
#[inline_props]
fn Calculator(cx: Scope, number: usize) -> Element {
let bigger_number = use_memo(cx, (number,), |(number,)| {
// This will only be calculated when `number` has changed.
number * 100
});
render!(
p { "{bigger_number}" }
)
}
fn app(cx: Scope) -> Element {
render!(Calculator { number: 0 })
}
```

View file

@ -32,7 +32,7 @@ Finally, a third component will render the other two as children. It will be res
![Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: "me waiting for a language feature"](./images/meme_editor_screenshot.png) ![Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: "me waiting for a language feature"](./images/meme_editor_screenshot.png)
## Using Context ## Using Shared State
Sometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient. Sometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient.
@ -42,7 +42,7 @@ Suppose now that we want to implement a dark mode toggle for our app. To achieve
Now, we could write another `use_state` in the top component, and pass `is_dark_mode` down to every component through props. But think about what will happen as the app grows in complexity almost every component that renders any CSS is going to need to know if dark mode is enabled or not so they'll all need the same dark mode prop. And every parent component will need to pass it down to them. Imagine how messy and verbose that would get, especially if we had components several levels deep! Now, we could write another `use_state` in the top component, and pass `is_dark_mode` down to every component through props. But think about what will happen as the app grows in complexity almost every component that renders any CSS is going to need to know if dark mode is enabled or not so they'll all need the same dark mode prop. And every parent component will need to pass it down to them. Imagine how messy and verbose that would get, especially if we had components several levels deep!
Dioxus offers a better solution than this "prop drilling" providing context. The [`use_context_provider`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html) hook is similar to `use_ref`, but it makes it available through [`use_context`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html) for all children components. Dioxus 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.
First, we have to create a struct for our dark mode configuration: First, we have to create a struct for our dark mode configuration:
@ -62,7 +62,7 @@ As a result, any child component of `App` (direct or not), can access the `DarkM
{{#include ../../../examples/meme_editor_dark_mode.rs:use_context}} {{#include ../../../examples/meme_editor_dark_mode.rs:use_context}}
``` ```
> `use_context` returns `Option<UseSharedState<DarkMode>>` here. If the context has been provided, the value is `Some(UseSharedState<DarkMode>)`, which you can call `.read` or `.write` on, similarly to `UseRef`. Otherwise, the value is `None`. > `use_shared_state` returns `Option<UseSharedState<DarkMode>>` here. If the context has been provided, the value is `Some(UseSharedState<DarkMode>)`, which you can call `.read` or `.write` on, similarly to `UseRef`. Otherwise, the value is `None`.
For example, here's how we would implement the dark mode toggle, which both reads the context (to determine what color it should render) and writes to it (to toggle dark mode): 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):

View file

@ -105,7 +105,7 @@ async fn editor_service(rx: UnboundedReceiver<EditorCommand>) {
Podemos combinar corrotinas com `Fermi` para emular o sistema `Thunk` do **Redux Toolkit** com muito menos dor de cabeça. Isso nos permite armazenar todo o estado do nosso aplicativo _dentro_ de uma tarefa e, em seguida, simplesmente atualizar os valores de "visualização" armazenados em `Atoms`. Não pode ser subestimado o quão poderosa é essa técnica: temos todas as vantagens das tarefas nativas do Rust com as otimizações e ergonomia do estado global. Isso significa que seu estado _real_ não precisa estar vinculado a um sistema como `Fermi` ou `Redux` os únicos `Atoms` que precisam existir são aqueles que são usados para controlar a interface. Podemos combinar corrotinas com `Fermi` para emular o sistema `Thunk` do **Redux Toolkit** com muito menos dor de cabeça. Isso nos permite armazenar todo o estado do nosso aplicativo _dentro_ de uma tarefa e, em seguida, simplesmente atualizar os valores de "visualização" armazenados em `Atoms`. Não pode ser subestimado o quão poderosa é essa técnica: temos todas as vantagens das tarefas nativas do Rust com as otimizações e ergonomia do estado global. Isso significa que seu estado _real_ não precisa estar vinculado a um sistema como `Fermi` ou `Redux` os únicos `Atoms` que precisam existir são aqueles que são usados para controlar a interface.
```rust, no_run ```rust, no_run
static USERNAME: Atom<String> = |_| "default".to_string(); static USERNAME: Atom<String> = Atom(|_| "default".to_string());
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let atoms = use_atom_root(cx); let atoms = use_atom_root(cx);
@ -118,7 +118,7 @@ fn app(cx: Scope) -> Element {
} }
fn Banner(cx: Scope) -> Element { fn Banner(cx: Scope) -> Element {
let username = use_read(cx, USERNAME); let username = use_read(cx, &USERNAME);
cx.render(rsx!{ cx.render(rsx!{
h1 { "Welcome back, {username}" } h1 { "Welcome back, {username}" }
@ -134,8 +134,8 @@ enum SyncAction {
} }
async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) { async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
let username = atoms.write(USERNAME); let username = atoms.write(&USERNAME);
let errors = atoms.write(ERRORS); let errors = atoms.write(&ERRORS);
while let Ok(msg) = rx.next().await { while let Ok(msg) = rx.next().await {
match msg { match msg {

View file

@ -113,14 +113,14 @@ enum InputError {
TooShort, TooShort,
} }
static INPUT_ERROR: Atom<InputError> = |_| InputError::None; static INPUT_ERROR: Atom<InputError> = Atom(|_| InputError::None);
``` ```
Então, em nosso componente de nível superior, queremos tratar explicitamente o possível estado de erro para esta parte da árvore. Então, em nosso componente de nível superior, queremos tratar explicitamente o possível estado de erro para esta parte da árvore.
```rust, no_run ```rust, no_run
fn TopLevel(cx: Scope) -> Element { fn TopLevel(cx: Scope) -> Element {
let error = use_read(cx, INPUT_ERROR); let error = use_read(cx, &INPUT_ERROR);
match error { match error {
TooLong => return cx.render(rsx!{ "FAILED: Too long!" }), TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
@ -134,7 +134,7 @@ Agora, sempre que um componente _downstream_ tiver um erro em suas ações, ele
```rust, no_run ```rust, no_run
fn Commandline(cx: Scope) -> Element { fn Commandline(cx: Scope) -> Element {
let set_error = use_set(cx, INPUT_ERROR); let set_error = use_set(cx, &INPUT_ERROR);
cx.render(rsx!{ cx.render(rsx!{
input { input {

View file

@ -18,7 +18,7 @@ dioxus = { version = "*", features = ["hot-reload"] }
1. Execute: 1. Execute:
``` ```
dioxus serve --hot-reload dx serve --hot-reload
``` ```
2. alterar algum código dentro de uma macro `rsx` 2. alterar algum código dentro de uma macro `rsx`

View file

@ -1 +0,0 @@
book

View file

@ -1,25 +0,0 @@
# Dioxus: Guias Avançados e Referência
![dioxuslogo](./images/dioxuslogo_full.png)
**Dioxus** é um framework e ecossistema para desenvolver interfaces rápidas, escaláveis e robustas com a linguagem de Programação Rust. Este guia irá ajudar você a começar com o Dioxus para Web, Desktop, Móvel e mais.
> Este livro é a Referência e Guias Avançados para o framework Dioxus. Para um tutorial em como de fato _usar_ o Dioxus, procure o [guia oficial](https://dioxuslabs.com/guide/en/).
## Guias e Referência
Com a referência nós procuramos manter a documentar a funcionalidade que pode não ter sido mencionada no guia oficial para manter uma carga de informação mínima. Alguns tópicos não estão inclusos pelo guia, mas discutidos nesta referência incluindo:
- Processo seguro (`ThreadSafe`) da `VirtualDOM`
- Abordagem complete sobre o uso do `rsx!` e funções inclusas
- Padrão `spread` para as propriedades dos componentes
- Testes
- Memoization à fundo
- Elementos personalizados
- Renderizadores personalizados
## Contribuindo
Se nesse documento estiver de algum forma confuso, contém erros de digitação ou você gostaria de ajudar a melhorar algo, sinta-se à vontade para fazer um PR no [repositório do Dioxus](https://github.com/DioxusLabs/dioxus/tree/master/docs/reference).
Todas as contribuições serão licenciadas sob a licença MIT/Apache2.

View file

@ -1,47 +0,0 @@
# Summary
- [Introdução](README.md)
- [Platformas](platforms/index.md)
- [Web](platforms/web.md)
- [Renderização por Servidor(SSR)](platforms/ssr.md)
- [Desktop](platforms/desktop.md)
- [Móvel](platforms/mobile.md)
- [TUI](platforms/tui.md)
- [Guias Avançados](guide/index.md)
- [RSX à fundo](guide/rsx_in_depth.md)
- [Componentes](guide/components.md)
- [Propriedades](guide/props.md)
- [Memoization](guide/memoization.md)
- [Desempenho](guide/performance.md)
- [Testes](guide/testing.md)
- [Construindo Elementos com o NodeFactory](guide/rsx.md)
- [Elementos Personalizados](guide/custom_elements.md)
- [Renderizadores Personalizados](guide/custom_renderer.md)
- [Componentes Renderizados por Servidor](guide/server_side_components.md)
- [Empacotando e Distribuindo](guide/bundline.md)
- [Recarregamento em Tempo-Real com RSX](guide/hot_reloading.md)
- [Guia de Referência](reference/reference.md)
- [Anti-padrões](reference/anti.md)
- [Filhos](reference/children.md)
- [Renderização Condicional](reference/conditional.md)
- [Entradas Controladas](reference/controlled.md)
- [Elementos Personalizados](reference/custom.md)
- [Componentes Vazios](reference/empty.md)
- [Tratamento de Errors](reference/error.md)
- [Fragmentos](reference/fragments.md)
- [CSS Globais](reference/global.md)
- [Estilos em Linha](reference/inline.md)
- [Iteradores](reference/iterators.md)
- [Ouvintes](reference/listeners.md)
- [Memoization](reference/memoization.md)
- [Nós de Referência](reference/node.md)
- [Padrão Propagado (Spread)](reference/spread.md)
- [Gerenciamento de Estado](reference/state.md)
- [Suspensão](reference/suspense.md)
- [Tarefas](reference/task.md)
- [Testes](reference/testing.md)

View file

@ -1,497 +0,0 @@
# Renderizador Personalizado
Dioxus é uma estrutura incrivelmente portátil para desenvolvimento de interface do usuário. As lições, conhecimentos, hooks e componentes que você adquire ao longo do tempo sempre podem ser aproveitados para projetos futuros. No entanto, às vezes, esses projetos não podem aproveitar um renderizador compatível ou você precisa implementar seu próprio renderizador melhor.
Ótimas notícias: o design do renderizador depende inteiramente de você! Nós fornecemos sugestões e inspiração com os renderizadores originais, mas só realmente precisamos processar `DomEdits` e enviar `UserEvents`.
## Em Detalhes:
A implementação do renderizador é bastante simples. O renderizador precisa:
1. Lidar com o fluxo de edições gerado por atualizações no DOM virtual
2. Registrar ouvintes e passe eventos para o sistema de eventos do DOM virtual
Essencialmente, seu renderizador precisa implementar o traço `RealDom` e gerar objetos `EventTrigger` para atualizar o `VirtualDOM`. A partir daí, você terá tudo o que precisa para renderizar o `VirtualDOM` na tela.
Internamente, o Dioxus lida com o relacionamento da árvore, `diffing`, gerenciamento de memória e o sistema de eventos, deixando o mínimo necessário para que os renderizadores se implementem.
Para referência, confira o interpretador JavaScript ou o renderizador TUI como ponto de partida para seu renderizador personalizado.
## DomEdições
O tipo "DomEdit" é uma enumeração serializada que representa uma operação atômica que ocorre no `RealDom`. As variantes seguem aproximadamente este conjunto:
```rust, no_run
enum DomEdit {
PushRoot,
AppendChildren,
ReplaceWith,
InsertAfter,
InsertBefore,
Remove,
CreateTextNode,
CreateElement,
CreateElementNs,
CreatePlaceholder,
NewEventListener,
RemoveEventListener,
SetText,
SetAttribute,
RemoveAttribute,
PopRoot,
}
```
O mecanismo de diferenciação do Dioxus opera como uma [máquina de pilha] (https://en.wikipedia.org/wiki/Stack_machine) onde o método `push_root` empilhar um novo nó DOM "real" para a pilha e `append_child` e `replace_with` ambos removem nós da pilha.
### Um exemplo
Por uma questão de compreensão, vamos considerar este exemplo - uma declaração de interface do usuário muito simples:
```rust, no_run
rsx!( h1 {"hello world"} )
```
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 `PushRoot` where the ID of the root is the container.
When the renderer receives this instruction, it pushes the actual Node onto its own stack. The real renderer's stack will look like this:
```rust, no_run
instructions: [
PushRoot(Container)
]
stack: [
ContainerNode,
]
```
Em seguida, o Dioxus encontrará o nó `h1`. O algoritmo `diff` decide que este nó precisa ser criado, então o Dioxus irá gerar o DomEdit `CreateElement`. Quando o renderizador receber esta instrução, ele criará um nó desmontado e o enviará para sua própria pilha:
```rust, no_run
instructions: [
PushRoot(Container),
CreateElement(h1),
]
stack: [
ContainerNode,
h1,
]
```
Em seguida, Dioxus vê o nó de texto e gera o DomEdit `CreateTextNode`:
```rust, no_run
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world")
]
stack: [
ContainerNode,
h1,
"hello world"
]
```
Lembre-se, o nó de texto não está anexado a nada (ele está desmontado), então o Dioxus precisa gerar um `Edit` que conecte o nó de texto ao elemento `h1`. Depende da situação, mas neste caso usamos `AppendChildren`. Isso remove o nó de texto da pilha, deixando o elemento `h1` como o próximo elemento na linha.
```rust, no_run
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world"),
AppendChildren(1)
]
stack: [
ContainerNode,
h1
]
```
Chamamos `AppendChildren` novamente, retirando o nó `h1` e anexando-o ao pai:
```rust, no_run
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world"),
AppendChildren(1),
AppendChildren(1)
]
stack: [
ContainerNode,
]
```
Finalmente, o contêiner é aberto, pois não precisamos mais dele.
```rust, no_run
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world"),
AppendChildren(1),
AppendChildren(1),
PopRoot
]
stack: []
```
Com o tempo, nossa pilha ficou assim:
```rust, no_run
[]
[Container]
[Container, h1]
[Container, h1, "hello world"]
[Container, h1]
[Container]
[]
```
Observe como nossa pilha fica vazia depois que a interface do usuário é montada. Convenientemente, essa abordagem separa completamente o `VirtualDOM` e o `RealDOM`. Além disso, essas edições são serializáveis, o que significa que podemos até gerenciar UIs em uma conexão de rede. Esta pequena máquina de pilha e edições serializadas tornam o Dioxus independente das especificidades da plataforma.
Dioxus também é muito rápido. Como o Dioxus divide a fase de `diff` e `patch`, ele é capaz de fazer todas as edições no `RealDOM` em um período de tempo muito curto (menos de um único quadro), tornando a renderização muito rápida. Ele também permite que o Dioxus cancele grandes operações de diferenciação se ocorrer um trabalho de prioridade mais alta durante a diferenciação.
É importante notar que há uma camada de conexão entre o Dioxus e o renderizador. O Dioxus salva e carrega elementos (a edição `PushRoot`) com um ID. Dentro do `VirtualDOM`, isso é rastreado apenas como um `u64`.
Sempre que uma edição `CreateElement` é gerada durante a comparação, o Dioxus incrementa seu contador de nós e atribui a esse novo elemento seu `NodeCount` atual. O `RealDom` é responsável por lembrar este ID e enviar o nó correto quando `PushRoot(ID)` é gerado. Dioxus recupera os IDs de elementos quando removidos. Para ficar em sincronia com Dioxus, você pode usar um `Sparce Vec` (`Vec<Option<T>>`) com itens possivelmente desocupados. Você pode usar os ids como índices no `Vec` para elementos e aumentar o `Vec` quando um id não existir.
Esta pequena demonstração serve para mostrar exatamente como um renderizador precisaria processar um stream de edição para construir UIs. Um conjunto de DOMEdits serializados para várias demos está disponível para você testar seu renderizador personalizado.
## Ciclo de eventos
Como a maioria das GUIs, o Dioxus conta com um `loop` de eventos para progredir no `VirtualDOM`. O próprio `VirtualDOM` também pode produzir eventos, por isso é importante que seu renderizador personalizado também possa lidar com eles.
O código para a implementação do `WebSys` é direto, então vamos adicioná-lo aqui para demonstrar como um `loop` de eventos é simples:
```rust, no_run
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
// Push the body element onto the WebsysDom's stack machine
let mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom());
websys_dom.stack.push(root_node);
// Rebuild or hydrate the virtualdom
let mutations = self.internal_dom.rebuild();
websys_dom.apply_mutations(mutations);
// Wait for updates from the real dom and progress the virtual dom
loop {
let user_input_future = websys_dom.wait_for_event();
let internal_event_future = self.internal_dom.wait_for_work();
match select(user_input_future, internal_event_future).await {
Either::Left((_, _)) => {
let mutations = self.internal_dom.work_with_deadline(|| false);
websys_dom.apply_mutations(mutations);
},
Either::Right((event, _)) => websys_dom.handle_event(event),
}
// render
}
}
```
É importante que você decodifique os eventos reais do seu sistema de eventos no sistema de eventos sintético do Dioxus (significado sintético abstraído). Isso significa simplesmente combinar seu tipo de evento e criar um tipo Dioxus `UserEvent`. No momento, o sistema `VirtualEvent` é modelado quase inteiramente em torno da especificação HTML, mas estamos interessados em reduzi-lo.
```rust, no_run
fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
match event.type_().as_str() {
"keydown" => {
let event: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();
UserEvent::KeyboardEvent(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name: "keydown",
// This should be whatever element is focused
element: Some(ElementId(0)),
data: Arc::new(KeyboardData{
char_code: event.char_code(),
key: event.key(),
key_code: event.key_code(),
alt_key: event.alt_key(),
ctrl_key: event.ctrl_key(),
meta_key: event.meta_key(),
shift_key: event.shift_key(),
locale: "".to_string(),
location: event.location(),
repeat: event.repeat(),
which: event.which(),
})
})
}
_ => todo!()
}
}
```
## Elementos brutos personalizados
Se você precisar ir mais longe a ponto de confiar em elementos personalizados para o seu renderizador - você pode. Isso ainda permitiria que você usasse a natureza reativa do Dioxus, sistema de componentes, estado compartilhado e outros recursos, mas acabará gerando nós diferentes. Todos os atributos e ouvintes para o namespace HTML e SVG são transportados por meio de estruturas auxiliares que essencialmente compilam (não representam sobrecarga de tempo de execução). Você pode colocar seus próprios elementos a qualquer hora que quiser, sem problemas. No entanto, você deve ter certeza absoluta de que seu renderizador pode lidar com o novo tipo, ou ele irá "bater e queimar".
Esses elementos personalizados são definidos como `unit struct` com implementações de `traits`.
Por exemplo, o elemento `div` é (aproximadamente!) definido assim:
```rust, no_run
struct div;
impl div {
/// Some glorious documentation about the class property.
const TAG_NAME: &'static str = "div";
const NAME_SPACE: Option<&'static str> = None;
// define the class attribute
pub fn class<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr("class", val, None, false)
}
// more attributes
}
```
Você provavelmente notou que muitos elementos nas macros `rsx!` suportam documentação em foco. A abordagem que adotamos para elementos personalizados significa que a estrutura da unidade é criada imediatamente onde o elemento é usado no macro. Quando o macro é expandido, os comentários doc ainda se aplicam à estrutura da unidade, dando toneladas de feedback no editor, mesmo dentro de uma macro procedural.
# Núcleo Nativo
Os renderizadores dão muito trabalho. Se você estiver criando um renderizador em Rust, o núcleo nativo fornece alguns utilitários para implementar um renderizador. Ele fornece uma abstração sobre DomEdits e manipula o layout para você.
## RealDom
O `RealDom` é uma abstração de nível superior sobre a atualização do DOM. Ele é atualizado com `DomEdits` e fornece uma maneira de atualizar lentamente o estado dos nós com base em quais atributos mudam.
### Exemplo
Vamos construir um renderizador de brinquedo com bordas, tamanho e cor do texto.
Antes de começarmos, vamos dar uma olhada em um elemento de exemplo que podemos renderizar:
```rust, no_run
cx.render(rsx!{
div{
color: "red",
p{
border: "1px solid black",
"hello world"
}
}
})
```
Nesta árvore a cor depende da cor do pai. O tamanho depende do tamanho das crianças, do texto atual e do tamanho do texto. A borda depende apenas do nó atual.
```mermaid
flowchart TB
subgraph context
text_width(text width)
end
subgraph div
state1(state)-->color1(color)
state1(state)-->border1(border)
border1-.->text_width
linkStyle 2 stroke:#5555ff,stroke-width:4px;
state1(state)-->layout_width1(layout width)
end
subgraph p
state2(state)-->color2(color)
color2-.->color1(color)
linkStyle 5 stroke:#0000ff,stroke-width:4px;
state2(state)-->border2(border)
border2-.->text_width
linkStyle 7 stroke:#5555ff,stroke-width:4px;
state2(state)-->layout_width2(layout width)
layout_width1-.->layout_width2
linkStyle 9 stroke:#aaaaff,stroke-width:4px;
end
subgraph hello world
state3(state)-->color3(color)
color3-.->color2(color)
linkStyle 11 stroke:#0000ff,stroke-width:4px;
state3(state)-->border3(border)
border3-.->text_width
linkStyle 13 stroke:#5555ff,stroke-width:4px;
state3(state)-->layout_width3(layout width)
layout_width2-.->layout_width3
linkStyle 15 stroke:#aaaaff,stroke-width:4px;
end
```
Para ajudar na construção de um Dom, o núcleo nativo fornece quatro características: `State`, `ChildDepState`, `ParentDepState` e `NodeDepState` e uma estrutura `RealDom`.
```rust, no_run
use dioxus_native_core::node_ref::*;
use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State};
use dioxus_native_core_macro::{sorted_str_slice, State};
#[derive(Default, Copy, Clone)]
struct Size(f32, f32);
// Size only depends on the current node and its children, so it implements ChildDepState
impl ChildDepState for Size {
// Size accepts a font size context
type Ctx = f32;
// Size depends on the Size part of each child
type DepState = Self;
// Size only cares about the width, height, and text parts of the current node
const NODE_MASK: NodeMask =
NodeMask::new_with_attrs(AttributeMask::Static(&sorted_str_slice!(["width", "height"]))).with_text();
fn reduce<'a>(
&mut self,
node: NodeView,
children: impl Iterator<Item = &'a Self::DepState>,
ctx: &Self::Ctx,
) -> bool
where
Self::DepState: 'a,
{
let mut width;
let mut height;
if let Some(text) = node.text() {
// if the node has text, use the text to size our object
width = text.len() as f32 * ctx;
height = ctx;
} else {
// otherwise, the size is the maximum size of the children
width = *children
.reduce(|accum, item| if accum >= item.0 { accum } else { item.0 })
.unwrap_or(0.0));
height = *children
.reduce(|accum, item| if accum >= item.1 { accum } else { item.1 })
.unwrap_or(&0.0);
}
// if the node contains a width or height attribute it overrides the other size
for a in node.attibutes(){
match a.name{
"width" => width = a.value.parse().unwrap(),
"height" => height = a.value.parse().unwrap(),
// because Size only depends on the width and height, no other attributes will be passed to the member
_ => panic!()
}
}
// to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed
let changed = (width != self.0) || (height != self.1);
*self = Self(width, height);
changed
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
struct TextColor {
r: u8,
g: u8,
b: u8,
}
// TextColor only depends on the current node and its parent, so it implements ParentDepState
impl ParentDepState for TextColor {
type Ctx = ();
// TextColor depends on the TextColor part of the parent
type DepState = Self;
// TextColor only cares about the color attribute of the current node
const NODE_MASK: NodeMask = NodeMask::new_with_attrs(AttributeMask::Static(&["color"]));
fn reduce(
&mut self,
node: NodeView,
parent: Option<&Self::DepState>,
_ctx: &Self::Ctx,
) -> bool {
// TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
let new = match node.attributes().next() {
// if there is a color tag, translate it
Some("red") => TextColor { r: 255, g: 0, b: 0 },
Some("green") => TextColor { r: 0, g: 255, b: 0 },
Some("blue") => TextColor { r: 0, g: 0, b: 255 },
Some(_) => panic!("unknown color"),
// otherwise check if the node has a parent and inherit that color
None => match parent {
Some(parent) => *parent,
None => Self::default(),
},
};
// check if the member has changed
let changed = new != *self;
*self = new;
changed
}
}
#[derive(Debug, Clone, PartialEq, Default)]
struct Border(bool);
// TextColor only depends on the current node, so it implements NodeDepState
impl NodeDepState for Border {
type Ctx = ();
// Border does not depended on any other member in the current node
type DepState = ();
// Border does not depended on any other member in the current node
const NODE_MASK: NodeMask =
NodeMask::new_with_attrs(AttributeMask::Static(&["border"]));
fn reduce(&mut self, node: NodeView, _sibling: &Self::DepState, _ctx: &Self::Ctx) -> bool {
// check if the node contians a border attribute
let new = Self(node.attributes().next().map(|a| a.name == "border").is_some());
// check if the member has changed
let changed = new != *self;
*self = new;
changed
}
}
// State provides a derive macro, but anotations on the members are needed in the form #[dep_type(dep_member, CtxType)]
#[derive(State, Default, Clone)]
struct ToyState {
// the color member of it's parent and no context
#[parent_dep_state(color)]
color: TextColor,
// depends on the node, and no context
#[node_dep_state()]
border: Border,
// depends on the layout_width member of children and f32 context (for text size)
#[child_dep_state(size, f32)]
size: Size,
}
```
Agora que temos nosso estado, podemos colocá-lo em uso em nosso DOM. Nós podemos atualizar o DOM com `update_state` para atualizar a estrutura do `DOM` (adicionando, removendo e alterando as propriedades dos nós) e então `apply_mutations` para atualizar o `ToyState` para cada um dos nós que foram alterados.
```rust, no_run
fn main(){
fn app(cx: Scope) -> Element {
cx.render(rsx!{
div{
color: "red",
"hello world"
}
})
}
let vdom = VirtualDom::new(app);
let rdom: RealDom<ToyState> = RealDom::new();
let mutations = dom.rebuild();
// update the structure of the real_dom tree
let to_update = rdom.apply_mutations(vec![mutations]);
let mut ctx = AnyMap::new();
// set the font size to 3.3
ctx.insert(3.3);
// update the ToyState for nodes in the real_dom tree
let _to_rerender = rdom.update_state(&dom, to_update, ctx).unwrap();
// we need to run the vdom in a async runtime
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?
.block_on(async {
loop{
let wait = vdom.wait_for_work();
let mutations = vdom.work_with_deadline(|| false);
let to_update = rdom.apply_mutations(mutations);
let mut ctx = AnyMap::new();
ctx.insert(3.3);
let _to_rerender = rdom.update_state(vdom, to_update, ctx).unwrap();
// render...
}
})
}
```
## Disposição
Para a maioria das plataformas, o layout dos Elementos permanecerá o mesmo. O módulo layout_attributes fornece uma maneira de aplicar atributos `html` a um estilo de layout estendido.
## Conclusão
Pronto, é isso! Você deve ter quase todo o conhecimento necessário sobre como implementar seu próprio renderizador. Estamos super interessados em ver os aplicativos Dioxus trazidos para renderizadores de desktop personalizados, renderizador para dispositivos móveis, interface do usuário para videogames e até realidade aumentada! Se você estiver interessado em contribuir para qualquer um desses projetos, não tenha medo de entrar em contato ou se juntar à comunidade.

View file

@ -1,31 +0,0 @@
# Recarregamento a Quente
1. O recarregamento a quente permite tempos de iteração muito mais rápidos dentro de chamadas rsx, interpretando-as e transmitindo as edições.
2. É útil ao alterar o estilo/layout de um programa, mas não ajudará na alteração da lógica de um programa.
3. Atualmente, o cli implementa apenas o recarregamento a quente para o renderizador da web.
# Configurar
Instale o [dioxus-cli](https://github.com/DioxusLabs/cli).
Habilite o recurso hot_reload no dioxus:
```toml
dioxus = { version = "*", features = ["web", "hot_reload"] }
```
# Como Usar
1. run:
```
dioxus serve --hot-reload
```
2. alterar algum código dentro de uma macro rsx
3. abra seu localhost em um navegador
4. salve e observe a mudança de estilo sem recompilar
# Limitações
1. O intérprete só pode usar expressões que existiam na última recompilação completa. Se você introduzir uma nova variável ou expressão na chamada rsx, ela acionará uma recompilação completa para capturar a expressão.
2. Componentes e Iteradores podem conter código de Rust arbitrário e acionarão uma recompilação completa quando alterados.

View file

@ -1 +0,0 @@
# Guias Avançados

View file

@ -1,228 +0,0 @@
# RSX in Depth
The RSX macro makes it very easy to assemble complex UIs with a very natural Rust syntax:
```rust, no_run
rsx!(
div {
button {
onclick: move |e| todos.write().new_todo(),
"Add todo"
}
ul {
class: "todo-list",
todos.iter().map(|(key, todo)| rsx!(
li {
class: "beautiful-todo"
key: "f"
h3 { "{todo.title}" }
p { "{todo.contents}"}
}
))
}
}
)
```
In this section, we'll cover the `rsx!` macro in depth. If you prefer to learn through examples, the `code reference` guide has plenty of examples on how to use `rsx!` effectively.
### Element structure
Attributes must come before child elements
```rust, no_run
div {
hidden: "false",
"some text"
child {}
Component {} // uppercase
component() // lowercase is treated like a function call
(0..10).map(|f| rsx!{ "hi {f}" }) // arbitrary rust expressions
}
```
Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work:
- `name: value` sets a property on this element.
- `"text"` adds a new text element
- `tag {}` adds a new child element
- `CustomTag {}` adds a new child component
- `{expr}` pastes the `expr` tokens literally. They must be `IntoIterator<T> where T: IntoVnode` to work properly
Commas are entirely optional, but might be useful to delineate between elements and attributes.
The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. To push you in the right direction, all text-based attributes take `std::fmt::Arguments` directly, so you'll want to reach for `format_args!` when the built-in `f-string` interpolation just doesn't cut it.
### Ignoring `cx.render` with `render!(...)`
Sometimes, writing `cx.render` is a hassle. The `rsx! macro will accept any token followed by a comma as the target to call "render" on:
```rust, no_run
cx.render(rsx!( div {} ))
// becomes
render!(div {})
```
### Conditional Rendering
Sometimes, you might not want to render an element given a condition. The rsx! macro will accept any tokens directly contained with curly braces, provided they resolve to a type that implements `IntoIterator<VNode>`. This lets us write any Rust expression that resolves to a VNode:
```rust, no_run
rsx!({
if enabled {
render!(div {"enabled"})
} else {
render!(li {"disabled"})
}
})
```
A convenient way of hiding/showing an element is returning an `Option<VNode>`. When combined with `and_then`, we can succinctly control the display state given some boolean:
```rust, no_run
rsx!({
a.and_then(rsx!(div {"enabled"}))
})
```
It's important to note that the expression `rsx!()` is typically lazy - this expression must be _rendered_ to produce a VNode. When using match statements, we must render every arm as to avoid the `no two closures are identical` rule that Rust imposes:
```rust, no_run
// this will not compile!
match case {
true => rsx!(div {}),
false => rsx!(div {})
}
// the nodes must be rendered first
match case {
true => render!(div {}),
false => render!(div {})
}
```
### Lists
Again, because anything that implements `IntoIterator<VNode>` is valid, we can use lists directly in our `rsx!`:
```rust, no_run
let items = vec!["a", "b", "c"];
cx.render(rsx!{
ul {
{items.iter().map(|f| rsx!(li { "a" }))}
}
})
```
Sometimes, it makes sense to render VNodes into a list:
```rust, no_run
let mut items = vec![];
for _ in 0..5 {
items.push(render!(li {} ))
}
render!({items} )
```
#### Lists and Keys
When rendering the VirtualDom to the screen, Dioxus needs to know which elements have been added and which have been removed. These changes are determined through a process called "diffing" - an old set of elements is compared to a new set of elements. If an element is removed, then it won't show up in the new elements, and Dioxus knows to remove it.
However, with lists, Dioxus does not exactly know how to determine which elements have been added or removed if the order changes or if an element is added or removed from the middle of the list.
In these cases, it is vitally important to specify a "key" alongside the element. Keys should be persistent between renders.
```rust, no_run
fn render_list(cx: Scope, items: HashMap<String, Todo>) -> DomTree {
render!(ul {
{items.iter().map(|key, item| {
li {
key: key,
h2 { "{todo.title}" }
p { "{todo.contents}" }
}
})}
})
}
```
There have been many guides made for keys in React, so we recommend reading up to understand their importance:
- [React guide on keys](https://reactjs.org/docs/lists-and-keys.html)
- [Importance of keys (Medium)](https://kentcdodds.com/blog/understanding-reacts-key-prop)
### Complete Reference
```rust, no_run
let text = "example";
cx.render(rsx!{
div {
h1 { "Example" },
{title}
// fstring interpolation
"{text}"
p {
// Attributes
tag: "type",
// Anything that implements display can be an attribute
abc: 123,
enabled: true,
// attributes also supports interpolation
// `class` is not a restricted keyword unlike JS and ClassName
class: "big small wide short {text}",
class: format_args!("attributes take fmt::Arguments. {}", 99),
tag: {"these tokens are placed directly"}
// Children
a { "abcder" },
// Children with attributes
h2 { "hello", class: "abc-123" },
// Child components
CustomComponent { a: 123, b: 456, key: "1" },
// Child components with paths
crate::components::CustomComponent { a: 123, b: 456, key: "1" },
// Iterators
{ (0..3).map(|i| rsx!( h1 {"{:i}"} )) },
// More rsx!, or even html!
{ rsx! { div { } } },
{ html! { <div> </div> } },
// Matching
// Requires rendering the nodes first.
// rsx! is lazy, and the underlying closures cannot have the same type
// Rendering produces the VNode type
{match rand::gen_range::<i32>(1..3) {
1 => render!(h1 { "big" })
2 => render!(h2 { "medium" })
_ => render!(h3 { "small" })
}}
// Optionals
{true.and_then(|f| rsx!( h1 {"Conditional Rendering"} ))}
// Child nodes
{cx.props.children}
// Any expression that is `IntoVNode`
{expr}
}
}
})
```

View file

@ -1,228 +0,0 @@
# RSX à Fundo
A macro RSX facilita muito a montagem de interfaces de usuário complexas com uma sintaxe Rust muito natural:
```rust, no_run
rsx!(
div {
button {
onclick: move |e| todos.write().new_todo(),
"Add todo"
}
ul {
class: "todo-list",
todos.iter().map(|(key, todo)| rsx!(
li {
class: "beautiful-todo"
key: "f"
h3 { "{todo.title}" }
p { "{todo.contents}"}
}
))
}
}
)
```
Nesta seção, abordaremos a macro `rsx!` em profundidade. Se você preferir aprender através de exemplos, o guia `referência de código` tem muitos exemplos sobre como usar `rsx!` efetivamente.
### Estrutura do elemento
Os atributos devem vir antes dos elementos filhos
```rust, no_run
div {
hidden: "false",
"some text"
child {}
Component {} // uppercase
component() // lowercase is treated like a function call
(0..10).map(|f| rsx!{ "hi {f}" }) // arbitrary rust expressions
}
```
Cada elemento usa uma lista de expressões separadas por vírgulas para construir o nó. A grosso modo, veja como eles funcionam:
- `name: value` define uma propriedade neste elemento.
- `text` adiciona um novo elemento de texto
- `tag {}` adiciona um novo elemento filho
- `CustomTag {}` adiciona um novo componente filho
- `{expr}` cola os tokens `expr` literalmente. Eles devem ser `IntoIterator<T> where T: IntoVnode` para funcionar corretamente
As vírgulas são totalmente opcionais, mas podem ser úteis para delinear entre elementos e atributos.
A função `render` fornece um alocador **extremamente eficiente** para `VNodes` e `text`, então tente não usar a macro `format!` em seus componentes. Os métodos `ToString` padrão do Rust passam pelo alocador global, mas todo o texto nos componentes é alocado dentro de uma ""arena Bump"" gerenciada manualmente. Para levá-lo na direção certa, todos os atributos baseados em texto recebem `std::fmt::Arguments` diretamente, então você vai querer usar `format_args!` quando a interpolação interna `f-string` simplesmente não funcionar.
### Ignorando `cx.render` com `render!(...)`
Às vezes, escrever `cx.render` é um aborrecimento. O `rsx!` macro aceitará qualquer token seguido por uma vírgula como destino para chamar "render" em:
```rust, no_run
cx.render(rsx!( div {} ))
// becomes
render!(div {})
```
### Renderização Condicional
Às vezes, você pode não querer renderizar um elemento dada uma condição. O `rsx!` macro aceitará quaisquer tokens contidos diretamente com chaves, desde que resolvam para um tipo que implemente `IntoIterator<VNode>`. Isso nos permite escrever qualquer expressão Rust que resolva para um `VNode`:
```rust, no_run
rsx!({
if enabled {
render!(div {"enabled"})
} else {
render!(li {"disabled"})
}
})
```
Uma maneira conveniente de ocultar/mostrar um elemento é retornar um `Option<VNode>`. Quando combinado com `and_then`, podemos controlar sucintamente o estado de exibição dado alguns booleanos:
```rust, no_run
rsx!({
a.and_then(rsx!(div {"enabled"}))
})
```
É importante notar que a expressão `rsx!()` é tipicamente tardia - esta expressão deve ser _renderizada_ para produzir um `VNode`. Ao usar declarações de `match`, devemos renderizar todos os braços para evitar a regra 'não há dois fechamentos idênticos' que o Rust impõe:
```rust, no_run
// this will not compile!
match case {
true => rsx!(div {}),
false => rsx!(div {})
}
// the nodes must be rendered first
match case {
true => render!(div {}),
false => render!(div {})
}
```
### Listas
Novamente, porque qualquer coisa que implemente `IntoIterator<VNode>` é válida, podemos usar listas diretamente em nosso `rsx!`:
```rust, no_run
let items = vec!["a", "b", "c"];
cx.render(rsx!{
ul {
{items.iter().map(|f| rsx!(li { "a" }))}
}
})
```
Às vezes, faz sentido renderizar `VNodes` em uma lista:
```rust, no_run
let mut items = vec![];
for _ in 0..5 {
items.push(render!(li {} ))
}
render!({items} )
```
#### Listas e chaves
Ao renderizar o `VirtualDom` na tela, o Dioxus precisa saber quais elementos foram adicionados e quais foram removidos. Essas mudanças são determinadas através de um processo chamado "diffing" - um antigo conjunto de elementos é comparado a um novo conjunto de elementos. Se um elemento for removido, ele não aparecerá nos novos elementos, e Dioxus sabe removê-lo.
No entanto, com listas, Dioxus não sabe exatamente como determinar quais elementos foram adicionados ou removidos se a ordem mudar ou se um elemento for adicionado ou removido do meio da lista.
Nesses casos, é de vital importância especificar uma "chave" ao lado do elemento. As chaves devem ser persistentes entre as renderizações.
```rust, no_run
fn render_list(cx: Scope, items: HashMap<String, Todo>) -> DomTree {
render!(ul {
{items.iter().map(|key, item| {
li {
key: key,
h2 { "{todo.title}" }
p { "{todo.contents}" }
}
})}
})
}
```
Existem muitos guias feitos para chaves no React, então recomendamos a leitura para entender sua importância:
- [React guide on keys](https://reactjs.org/docs/lists-and-keys.html)
- [Importância das chaves (Média)](https://kentcdodds.com/blog/understanding-reacts-key-prop)
### Referência Completa
```rust, no_run
let text = "example";
cx.render(rsx!{
div {
h1 { "Example" },
{title}
// fstring interpolation
"{text}"
p {
// Attributes
tag: "type",
// Anything that implements display can be an attribute
abc: 123,
enabled: true,
// attributes also supports interpolation
// `class` is not a restricted keyword unlike JS and ClassName
class: "big small wide short {text}",
class: format_args!("attributes take fmt::Arguments. {}", 99),
tag: {"these tokens are placed directly"}
// Children
a { "abcder" },
// Children with attributes
h2 { "hello", class: "abc-123" },
// Child components
CustomComponent { a: 123, b: 456, key: "1" },
// Child components with paths
crate::components::CustomComponent { a: 123, b: 456, key: "1" },
// Iterators
{ (0..3).map(|i| rsx!( h1 {"{:i}"} )) },
// More rsx!, or even html!
{ rsx! { div { } } },
{ html! { <div> </div> } },
// Matching
// Requires rendering the nodes first.
// rsx! is lazy, and the underlying closures cannot have the same type
// Rendering produces the VNode type
{match rand::gen_range::<i32>(1..3) {
1 => render!(h1 { "big" })
2 => render!(h2 { "medium" })
_ => render!(h3 { "small" })
}}
// Optionals
{true.and_then(|f| rsx!( h1 {"Conditional Rendering"} ))}
// Child nodes
{cx.props.children}
// Any expression that is `IntoVNode`
{expr}
}
}
})
```

View file

@ -1,45 +0,0 @@
# Getting Started: Desktop
One of Dioxus' killer features is the ability to quickly build a native desktop app that looks and feels the same across platforms. Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory.
Dioxus Desktop is built off Tauri. Right now there aren't any Dioxus abstractions over the menubar, handling, etc, so you'll want to leverage Tauri - mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly. The next major release of Dioxus-Desktop will include components and hooks for notifications, global shortcuts, menubar, etc.
## Getting Set up
Getting Set up with Dioxus-Desktop is quite easy. Make sure you have Rust and Cargo installed, and then create a new project:
```shell
$ cargo new --bin demo
$ cd demo
```
Add Dioxus with the `desktop` feature:
```shell
$ cargo add dioxus --features desktop
```
Edit your `main.rs`:
```rust, no_run
// main.rs
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx!{
div {
"hello world!"
}
})
}
```
To configure the webview, menubar, and other important desktop-specific features, checkout out some of the launch configuration in the [API reference](https://docs.rs/dioxus-desktop/).
## Future Steps
Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide/en) if you already haven't!

View file

@ -1,11 +0,0 @@
# Platforms
Dioxus supports many different platforms. Below are a list of guides that walk you through setting up Dioxus for a specific platform.
### Setup Guides
- [Web](web.md)
- [Server Side Rendering](ssr.md)
- [Desktop](desktop.md)
- [Mobile](mobile.md)
- [TUI](tui.md)

14
docs/router/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "dioxus-router-guide"
version = "0.0.1"
edition = "2021"
description = "Dioxus router guide, including testable examples"
license = "MIT OR Apache-2.0"
publish = false
[dev-dependencies]
dioxus = { path = "../../packages/dioxus" }
dioxus-desktop = { path = "../../packages/desktop" }
dioxus-web = { path = "../../packages/web" }
dioxus-ssr = { path = "../../packages/ssr" }
dioxus-router = { path = "../../packages/router" }

7
docs/router/README.md Normal file
View file

@ -0,0 +1,7 @@
# The router book
## How to run the tests
- Navigate your terminal to this directory
- Run `cargo clean`
- Run `cargo build --all --F regex -F serde -F web`
- Run `mdbook test -L ../../target/debug/deps/`

View file

@ -4,3 +4,6 @@ language = "en"
multilingual = false multilingual = false
src = "src" src = "src"
title = "Dioxus Router" title = "Dioxus Router"
[rust]
edition = "2021"

View file

@ -0,0 +1,48 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
// ANCHOR: router
#[derive(Routable, Clone)]
enum Route {
#[route("/")]
Home {},
// PageNotFound is a catch all route that will match any route and placing the matched segments in the route field
#[route("/:..route")]
PageNotFound { route: Vec<String> },
}
// ANCHOR_END: router
// ANCHOR: app
#[inline_props]
fn App(cx: Scope) -> Element {
render! {
Router {}
}
}
// ANCHOR_END: app
// ANCHOR: home
#[inline_props]
fn Home(cx: Scope) -> Element {
render! {
h1 { "Welcome to the Dioxus Blog!" }
}
}
// ANCHOR_END: home
// ANCHOR: fallback
#[inline_props]
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
render! {
h1 { "Page not found" }
p { "We are terribly sorry, but the page you requested doesn't exist." }
pre {
color: "red",
"log:\nattemped to navigate to: {route:?}"
}
}
}
// ANCHOR_END: fallback
fn main() {}

View file

@ -0,0 +1,24 @@
#![allow(non_snake_case, unused)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
// ANCHOR: route
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
// segments that start with :... are catch all segments
#[route("/blog/:..segments")]
BlogPost {
// You must include catch all segment in child variants
segments: Vec<String>,
},
}
// Components must contain the same catch all segments as their corresponding variant
#[inline_props]
fn BlogPost(cx: Scope, segments: Vec<String>) -> Element {
todo!()
}
// ANCHOR_END: route
fn main() {}

View file

@ -0,0 +1,115 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
fn main() {
#[cfg(target_arch = "wasm32")]
dioxus_web::launch(App);
#[cfg(not(target_arch = "wasm32"))]
dioxus_desktop::launch(App);
}
// ANCHOR: router
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
#[layout(NavBar)]
#[route("/")]
Home {},
#[nest("/blog")]
#[layout(Blog)]
#[route("/")]
BlogList {},
#[route("/blog/:name")]
BlogPost { name: String },
#[end_layout]
#[end_nest]
#[end_layout]
#[route("/:..route")]
PageNotFound {
route: Vec<String>,
},
}
// ANCHOR_END: router
fn App(cx: Scope) -> Element {
render! {
Router {}
}
}
#[inline_props]
fn NavBar(cx: Scope) -> Element {
render! {
nav {
ul {
li { Link { target: Route::Home {}, "Home" } }
li { Link { target: Route::BlogList {}, "Blog" } }
}
}
Outlet {}
}
}
#[inline_props]
fn Home(cx: Scope) -> Element {
render! {
h1 { "Welcome to the Dioxus Blog!" }
}
}
// ANCHOR: blog
#[inline_props]
fn Blog(cx: Scope) -> Element {
render! {
h1 { "Blog" }
Outlet {}
}
}
// ANCHOR_END: blog
// ANCHOR: blog_list
#[inline_props]
fn BlogList(cx: Scope) -> Element {
render! {
h2 { "Choose a post" }
ul {
li {
Link {
target: Route::BlogPost { name: "Blog post 1".into() },
"Read the first blog post"
}
}
li {
Link {
target: Route::BlogPost { name: "Blog post 2".into() },
"Read the second blog post"
}
}
}
}
}
// ANCHOR_END: blog_list
// ANCHOR: blog_post
// The name prop comes from the /:name route segment
#[inline_props]
fn BlogPost(cx: Scope, name: String) -> Element {
render! {
h2 { "Blog Post: {name}"}
}
}
// ANCHOR_END: blog_post
#[inline_props]
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
render! {
h1 { "Page not found" }
p { "We are terribly sorry, but the page you requested doesn't exist." }
pre {
color: "red",
"log:\nattemped to navigate to: {route:?}"
}
}
}

View file

@ -0,0 +1,35 @@
#![allow(non_snake_case, unused)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
// ANCHOR: route
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
// segments that start with : are dynamic segments
#[route("/blog/:name")]
BlogPost {
// You must include dynamic segments in child variants
name: String,
},
#[route("/document/:id")]
Document {
// You can use any type that implements FromStr
// If the segment can't be parsed, the route will not match
id: usize,
},
}
// Components must contain the same dynamic segments as their corresponding variant
#[inline_props]
fn BlogPost(cx: Scope, name: String) -> Element {
todo!()
}
#[inline_props]
fn Document(cx: Scope, id: usize) -> Element {
todo!()
}
// ANCHOR_END: route
fn main() {}

View file

@ -0,0 +1,28 @@
#![allow(non_snake_case, unused)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
#[derive(Routable, Clone)]
enum Route {
#[route("/")]
Home {},
}
#[inline_props]
fn Home(cx: Scope) -> Element {
todo!()
}
fn main() {}
// ANCHOR: component
fn GoToDioxus(cx: Scope) -> Element {
render! {
Link {
target: NavigationTarget::External("https://dioxuslabs.com".into()),
"ExternalTarget target"
}
}
}
// ANCHOR_END: component

View file

@ -0,0 +1,36 @@
// ANCHOR: router
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
/// An enum of all of the possible routes in the app.
#[derive(Routable, Clone)]
enum Route {
// The home page is at the / route
#[route("/")]
// If the name of the component and variant are the same you can omit the component and props name
// If they are different you can specify them like this:
// #[route("/", ComponentName, PropsName)]
Home {},
}
// ANCHOR_END: router
// ANCHOR: app
#[inline_props]
fn App(cx: Scope) -> Element {
render! {
Router {}
}
}
// ANCHOR_END: app
// ANCHOR: home
#[inline_props]
fn Home(cx: Scope) -> Element {
render! {
h1 { "Welcome to the Dioxus Blog!" }
}
}
// ANCHOR_END: home
fn main() {}

View file

@ -0,0 +1,112 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
fn main() {
#[cfg(target_arch = "wasm32")]
dioxus_web::launch(App);
#[cfg(not(target_arch = "wasm32"))]
dioxus_desktop::launch(App);
}
// ANCHOR: router
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
#[layout(NavBar)]
#[route("/")]
Home {},
#[nest("/blog")]
#[layout(Blog)]
#[route("/")]
BlogList {},
#[route("/blog/:name")]
BlogPost { name: String },
#[end_layout]
#[end_nest]
#[end_layout]
#[nest("/myblog")]
#[redirect("/", || Route::BlogList {})]
#[redirect("/:name", |name: String| Route::BlogPost { name })]
#[end_nest]
#[route("/:..route")]
PageNotFound {
route: Vec<String>,
},
}
// ANCHOR_END: router
fn App(cx: Scope) -> Element {
render! {
Router {}
}
}
#[inline_props]
fn NavBar(cx: Scope) -> Element {
render! {
nav {
ul {
li { Link { target: Route::Home {}, "Home" } }
li { Link { target: Route::BlogList {}, "Blog" } }
}
}
Outlet {}
}
}
#[inline_props]
fn Home(cx: Scope) -> Element {
render! {
h1 { "Welcome to the Dioxus Blog!" }
}
}
#[inline_props]
fn Blog(cx: Scope) -> Element {
render! {
h1 { "Blog" }
Outlet {}
}
}
#[inline_props]
fn BlogList(cx: Scope) -> Element {
render! {
h2 { "Choose a post" }
ul {
li {
Link {
target: Route::BlogPost { name: "Blog post 1".into() },
"Read the first blog post"
}
}
li {
Link {
target: Route::BlogPost { name: "Blog post 2".into() },
"Read the second blog post"
}
}
}
}
}
#[inline_props]
fn BlogPost(cx: Scope, name: String) -> Element {
render! {
h2 { "Blog Post: {name}"}
}
}
#[inline_props]
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
render! {
h1 { "Page not found" }
p { "We are terribly sorry, but the page you requested doesn't exist." }
pre {
color: "red",
"log:\nattemped to navigate to: {route:?}"
}
}
}

View file

@ -0,0 +1,30 @@
#![allow(non_snake_case, unused)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
#[route("/")]
Home {},
}
#[inline_props]
fn Home(cx: Scope) -> Element {
todo!()
}
// ANCHOR: history_buttons
fn HistoryNavigation(cx: Scope) -> Element {
render! {
GoBackButton {
"Back to the Past"
}
GoForwardButton {
"Back to the Future" /* You see what I did there? 😉 */
}
}
}
// ANCHOR_END: history_buttons
fn main() {}

View file

@ -0,0 +1,29 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
#[derive(Routable, Clone)]
enum Route {
#[route("/")]
Home {},
}
// ANCHOR: app
#[inline_props]
fn App(cx: Scope) -> Element {
render! {
Router {
config: || RouterConfig::default().history(WebHistory::default())
}
}
}
// ANCHOR_END: app
#[inline_props]
fn Home(cx: Scope) -> Element {
render! {
h1 { "Welcome to the Dioxus Blog!" }
}
}
fn main() {}

View file

@ -0,0 +1,72 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
// ANCHOR: router
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
// All routes under the NavBar layout will be rendered inside of the NavBar Outlet
#[layout(NavBar)]
#[route("/")]
Home {},
#[end_layout]
#[route("/:..route")]
PageNotFound { route: Vec<String> },
}
// ANCHOR_END: router
// ANCHOR: nav
#[inline_props]
fn NavBar(cx: Scope) -> Element {
render! {
nav {
ul {
li {
Link {
// The Link component will navigate to the route specified
// in the target prop which is checked to exist at compile time
target: Route::Home {},
"Home"
}
}
}
}
Outlet {}
}
}
// ANCHOR_END: nav
// ANCHOR: app
#[inline_props]
fn App(cx: Scope) -> Element {
render! {
Router {}
}
}
// ANCHOR_END: app
// ANCHOR: home
#[inline_props]
fn Home(cx: Scope) -> Element {
render! {
h1 { "Welcome to the Dioxus Blog!" }
}
}
// ANCHOR_END: home
// ANCHOR: fallback
#[inline_props]
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
render! {
h1 { "Page not found" }
p { "We are terribly sorry, but the page you requested doesn't exist." }
pre {
color: "red",
"log:\nattemped to navigate to: {route:?}"
}
}
}
// ANCHOR_END: fallback
fn main() {}

View file

@ -0,0 +1,56 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
#[route("/")]
Home {},
#[route("/:..route")]
PageNotFound { route: Vec<String> },
}
#[inline_props]
fn App(cx: Scope) -> Element {
render! {
Router {}
}
}
// ANCHOR: nav
#[inline_props]
fn Home(cx: Scope) -> Element {
let nav = use_navigator(cx);
// push
nav.push(Route::PageNotFound { route: vec![] });
// replace
nav.replace(Route::Home {});
// go back
nav.go_back();
// go forward
nav.go_forward();
render! {
h1 { "Welcome to the Dioxus Blog!" }
}
}
// ANCHOR_END: nav
#[inline_props]
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
render! {
h1 { "Page not found" }
p { "We are terribly sorry, but the page you requested doesn't exist." }
pre {
color: "red",
"log:\nattemped to navigate to: {route:?}"
}
}
}
fn main() {}

View file

@ -0,0 +1,40 @@
#![allow(non_snake_case, unused)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
// ANCHOR: route
#[derive(Routable, Clone)]
// Skipping formatting allows you to indent nests
#[rustfmt::skip]
enum Route {
// Start the /blog nest
#[nest("/blog")]
// You can nest as many times as you want
#[nest("/:id")]
#[route("/post")]
PostId {
// You must include parent dynamic segments in child variants
id: usize,
},
// End nests manually with #[end_nest]
#[end_nest]
#[route("/:id")]
// The absolute route of BlogPost is /blog/:name
BlogPost {
id: usize,
},
// Or nests are ended automatically at the end of the enum
}
#[inline_props]
fn BlogPost(cx: Scope, id: usize) -> Element {
todo!()
}
#[inline_props]
fn PostId(cx: Scope, id: usize) -> Element {
todo!()
}
// ANCHOR_END: route
fn main() {}

View file

@ -0,0 +1,66 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
// ANCHOR: router
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
// All routes under the NavBar layout will be rendered inside of the NavBar Outlet
#[layout(NavBar)]
#[route("/")]
Home {},
#[end_layout]
#[route("/:..route")]
PageNotFound { route: Vec<String> },
}
// ANCHOR_END: router
// ANCHOR: nav
#[inline_props]
fn NavBar(cx: Scope) -> Element {
render! {
nav {
ul {
li { "links" }
}
}
// The Outlet component will render child routes (In this case just the Home component) inside the Outlet component
Outlet {}
}
}
// ANCHOR_END: nav
// ANCHOR: app
#[inline_props]
fn App(cx: Scope) -> Element {
render! {
Router {}
}
}
// ANCHOR_END: app
// ANCHOR: home
#[inline_props]
fn Home(cx: Scope) -> Element {
render! {
h1 { "Welcome to the Dioxus Blog!" }
}
}
// ANCHOR_END: home
// ANCHOR: fallback
#[inline_props]
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
render! {
h1 { "Page not found" }
p { "We are terribly sorry, but the page you requested doesn't exist." }
pre {
color: "red",
"log:\nattemped to navigate to: {route:?}"
}
}
}
// ANCHOR_END: fallback
fn main() {}

View file

@ -0,0 +1,46 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
// ANCHOR: outlet
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
#[layout(Wrapper)]
#[route("/")]
Index {},
}
#[inline_props]
fn Wrapper(cx: Scope) -> Element {
render! {
header { "header" }
// The index route will be rendered here
Outlet { }
footer { "footer" }
}
}
#[inline_props]
fn Index(cx: Scope) -> Element {
render! {
h1 { "Index" }
}
}
// ANCHOR_END: outlet
fn App(cx: Scope) -> Element {
render! {
Router {}
}
}
fn main() {
let mut vdom = VirtualDom::new(App);
let _ = vdom.rebuild();
let html = dioxus_ssr::render(&vdom);
assert_eq!(
html,
"<header>header</header><h1>Index</h1><footer>footer</footer>"
);
}

View file

@ -0,0 +1,24 @@
#![allow(non_snake_case, unused)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
// ANCHOR: route
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
// segments that start with ?: are query segments
#[route("/blog?:name")]
BlogPost {
// You must include query segments in child variants
name: String,
},
}
// Components must contain the same query segments as their corresponding variant
#[inline_props]
fn BlogPost(cx: Scope, name: String) -> Element {
todo!()
}
// ANCHOR_END: route
fn main() {}

View file

@ -0,0 +1,37 @@
// ANCHOR: router
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
/// An enum of all of the possible routes in the app.
#[derive(Routable, Clone)]
enum Route {
// The home page is at the / route
#[route("/")]
// If the name of the component and variant are the same you can omit the component and props name
// #[route("/", ComponentName, PropsName)]
Home {},
}
// ANCHOR_END: router
// ANCHOR: app
#[inline_props]
fn App(cx: Scope) -> Element {
render! {
Router {
config: || RouterConfig::default().history(WebHistory::default())
}
}
}
// ANCHOR_END: app
// ANCHOR: home
#[inline_props]
fn Home(cx: Scope) -> Element {
render! {
h1 { "Welcome to the Dioxus Blog!" }
}
}
// ANCHOR_END: home
fn main() {}

View file

@ -0,0 +1,41 @@
#![allow(non_snake_case, unused)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
// ANCHOR: router
#[derive(Routable, Clone, PartialEq)]
enum Route {
#[route("/")]
Index {},
#[route("/home")]
Home {},
}
#[inline_props]
fn Home(cx: Scope) -> Element {
render! {
p { "Home" }
}
}
#[inline_props]
fn Index(cx: Scope) -> Element {
render! {
p { "Index" }
}
}
fn app(cx: Scope) -> Element {
render! {
Router {
config: || RouterConfig::default().on_update(|state|{
(state.current() == Route::Index {}).then_some(
NavigationTarget::Internal(Route::Home {})
)
})
}
}
}
// ANCHOR_END: router
fn main() {}

View file

@ -0,0 +1,28 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_router::prelude::*;
// ANCHOR: route
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
// Routes always start with a slash
#[route("/")]
Home {},
// You can have multiple segments in a route
#[route("/hello/world")]
HelloWorld {},
}
#[inline_props]
fn Home(cx: Scope) -> Element {
todo!()
}
#[inline_props]
fn HelloWorld(cx: Scope) -> Element {
todo!()
}
// ANCHOR_END: route
fn main() {}

View file

@ -1,11 +0,0 @@
# Dioxus Router: Introduction
Whether or not you're building a website, desktop app, or mobile app, organizing your app's views into "pages" can be an effective method for organization and maintainability.
The `dioxus-router` crate contains the Router module. To add it to your project run:
cargo add dioxus-router
> **Be sure to include the `web` feature (`--feature web`) for deployment into a browser!**
In this book you'll find a short [guide](./guide/index.md) to get up to speed with Dioxus Router, as well as the router's [reference](./reference/index.md).

View file

@ -1,11 +1,25 @@
# Summary # Summary
- [Introduction](./README.md) [Introduction](./index.md)
- [Guide](./guide/index.md) # Example Project
- [Getting Started](./guide/getting-started.md)
- [Creating Our First Route](./guide/first-route.md) - [Overview](./example/index.md)
- [Building a Nest](./guide/building-a-nest.md) - [Creating Our First Route](./example/first-route.md)
- [Redirection Perfection](./guide/redirection-perfection.md) - [Building a Nest](./example/building-a-nest.md)
- [Reference](./reference/index.md) - [Navigation Targets](./example/navigation-targets.md)
- [X]() - [Redirection Perfection](./example/redirection-perfection.md)
- [Full Code](./example/full-code.md)
# Reference
- [Adding the Router to Your Application](./reference/index.md)
- [Defining Routes](./reference/routes/index.md)
- [Nested Routes](./reference/routes/nested.md)
- [Layouts](./reference/layouts.md)
- [Navigation](./reference/navigation/index.md)
- [Programmatic Navigation](./reference/navigation/programmatic.md)
- [History Providers](./reference/history-providers.md)
- [History Buttons](./reference/history-buttons.md)
- [Static Generation](./reference/static-generation.md)
- [Routing Update Callback](./reference/routing-update-callback.md)

View file

@ -0,0 +1,99 @@
# Building a Nest
In this chapter, we will begin to build the blog portion of our site which will
include links, nested routes, and route parameters.
## Site Navigation
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.
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 [`Outlet`] component.
Let's create a new `NavBar` component:
```rust, no_run
{{#include ../../examples/nested_routes.rs:nav}}
```
Next, let's add our `NavBar` component as a layout to our Route enum:
```rust, no_run
{{#include ../../examples/nested_routes.rs:router}}
```
To add links to our `NavBar`, we could always use an HTML anchor element but that has two issues:
1. It causes a full-page reload
2. We can accidentally link to a page that doesn't exist
Instead, we want to use the [`Link`] component provided by Dioxus Router.
The [`Link`] is similar to a regular `<a>` tag. It takes a target and children.
Unlike a regular `<a>` 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.
Let's add our links:
```rust, no_run
{{#include ../../examples/links.rs:nav}}
```
> Using this method, the [`Link`] component only works for links within our
> application. To learn more about navigation targets see
> [here](./navigation-targets.md).
Now you should see a list of links near the top of your page. Click on one and
you should seamlessly travel between pages.
## URL Parameters and Nested Routes
Many websites such as GitHub put parameters in their URL. For example,
`https://github.com/DioxusLabs` utilizes the text after the domain to
dynamically search and display content about an organization.
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.
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.
The path to our blog will look like `/blog/myBlogPage`, `myBlogPage` being the
URL parameter.
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.
```rust, no_run
{{#include ../../examples/dynamic_route.rs:blog}}
```
Now we'll create another index component, that'll be displayed when no blog post
is selected:
```rust, no_run
{{#include ../../examples/dynamic_route.rs:blog_list}}
```
We also need to create a component that displays an actual blog post. This component will accept the URL parameters as props:
```rust, no_run
{{#include ../../examples/dynamic_route.rs:blog_post}}
```
Finally, let's tell our router about those components:
```rust, no_run
{{#include ../../examples/dynamic_route.rs:router}}
```
That's it! If you head to `/blog/1` you should see our sample post.
## Conclusion
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.
[`Link`]: https://docs.rs/dioxus-router/latest/dioxus_router/prelude/fn.GenericLink<R>.html

View file

@ -0,0 +1,62 @@
# Creating Our First Route
In this chapter, we will start utilizing Dioxus Router and add a homepage and a
404 page to our project.
## Fundamentals
The core of the Dioxus Router is the [`Routable`] macro and the [`Router`] component.
First, we need an actual page to route to! Let's add a homepage component:
```rust, no_run
{{#include ../../examples/first_route.rs:home}}
```
## Creating Routes
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.
To start using Dioxus Router, we need to use the [`Routable`] macro.
The [`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.
```rust, no_run
{{#include ../../examples/first_route.rs:router}}
```
All other hooks and components the router provides can only be used as a descendant of a [`Router`] component.
If you head to your application's browser tab, you should now see the text
`Welcome to Dioxus Blog!` when on the root URL (`http://localhost:8080/`). If
you enter a different path for the URL, nothing should be displayed.
This is because we told Dioxus Router to render the `Home` component only when
the URL path is `/`.
## Fallback Route
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.
First, we create a new `PageNotFound` component.
```rust, no_run
{{#include ../../examples/catch_all.rs:fallback}}
```
Next, register the route in the Route enum to match if all other routes fail.
```rust, no_run
{{#include ../../examples/catch_all.rs:router}}
```
Now when you go to a route that doesn't exist, you should see the page not found
text.
## Conclusion
In this chapter, we learned how to create a route and tell Dioxus Router what
component to render when the URL path is `/`. 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.

View file

@ -0,0 +1,5 @@
# Full Code
```rust, no_run
{{#include ../../examples/full_example.rs}}
```

View file

@ -0,0 +1,29 @@
# Overview
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!
> To follow along with the router example, you'll need a working Dioxus app.
> Check out the [Dioxus book](https://dioxuslabs.com/docs/0.3/guide/en/) to get started.
> Make sure to add Dioxus Router as a dependency, as explained in the
> [introduction](../index.md).
## You'll learn how to
- Create routes and render "pages".
- Utilize nested routes, create a navigation bar, and render content for a
set of routes.
- Parse URL parameters to dynamically display content.
- Redirect visitors to different routes.
> **Disclaimer**
>
> 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.
You can find the complete application in the [full code](./full-code.md)
chapter.

View file

@ -0,0 +1,27 @@
# Navigation Targets
In the previous chapter, we learned how to create links to pages within our app.
We told them where to go using the `target` property. This property takes something that can be converted to a [`NavigationTarget`].
## What is a navigation target?
A [`NavigationTarget`] is similar to the `href` of an HTML anchor element. It
tells the router where to navigate to. The Dioxus Router knows two kinds of
navigation targets:
- [`Internal`]: We used internal links in the previous chapter. It's a link to a page within our
app represented as a Route enum.
- [`External`]: This works exactly like an HTML anchors' `href`. Don't use this for in-app
navigation as it will trigger a page reload by the browser.
## External navigation
If we need a link to an external page we can do it like this:
```rust, no_run
{{#include ../../examples/external_link.rs:component}}
```
[`External`]: https://docs.rs/dioxus-router-core/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.External
[`Internal`]: https://docs.rs/dioxus-router-core/latest/dioxus_router/navigation/enum.NavigationTarget.html#variant.Internal
[`NavigationTarget`]: https://docs.rs/dioxus-router-core/latest/dioxus_router/navigation/enum.NavigationTarget.html

View file

@ -0,0 +1,41 @@
# Redirection Perfection
You're well on your way to becoming a routing master!
In this chapter, we will cover creating redirects
## Creating Redirects
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.
As a simple example, let's say you want user to still land on your blog, even
if they used the path `/myblog` or `/myblog/:name`.
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.
Let's add a redirect to our router enum:
```rust, no_run
{{#include ../../examples/full_example.rs:router}}
```
That's it! Now your users will be redirected to the blog.
### Conclusion
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 [router examples](https://github.com/DioxusLabs/dioxus/tree/master/packages/router/examples), or
can check out the [API reference](https://docs.rs/dioxus-router/).
### Challenges
- Organize your components into separate files for better maintainability.
- Give your app some style if you haven't already.
- Build an about page so your visitors know who you are.
- Add a user system that uses URL parameters.
- Create a simple admin system to create, delete, and edit blogs.
- If you want to go to the max, hook up your application to a rest API and database.

View file

@ -1,201 +0,0 @@
# Building a Nest
Not a bird's nest! A nest of routes!
In this chapter we will begin to build the blog portion of our site which will include links, nested URLs, and URL parameters. We will also explore the use case of rendering components outside of routes.
### Site Navigation
Our site visitors won't know all the available pages and blogs on our site so we should provide a navigation bar for them.
Let's create a new ``navbar`` component:
```rs
fn navbar(cx: Scope) -> Element {
cx.render(rsx! {
ul {
}
})
}
```
Our navbar will be a list of links going between our pages. We could always use an HTML anchor element but that would cause our page to unnecessarily reload. Instead we want to use the ``Link`` component provided by Dioxus Router.
The Link component is very similar to the Route component. It takes a path and an element. Add the Link component into your use statement and then add some links:
```rs
use dioxus::{
prelude::*,
router::{Route, Router, Link}, // UPDATED
};
...
fn navbar(cx: Scope) -> Element {
cx.render(rsx! {
ul {
// NEW
Link { to: "/", "Home"}
br {}
Link { to: "/blog", "Blog"}
}
})
}
```
>By default, the Link component only works for links within your application. To link to external sites, add the ``external: true`` property.
>```rs
>Link { to: "https://github.com", external: true, "GitHub"}
>```
And finally, use the navbar component in your app component:
```rs
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Router {
p { "-- Dioxus Blog --" }
self::navbar {} // NEW
Route { to: "/", self::homepage {}}
Route { to: "", self::page_not_found {}}
}
})
}
```
Now you should see a list of links near the top of your page. Click on one and you should seamlessly travel between pages.
##### WIP: Active Link Styling
### URL Parameters and Nested Routes
Many websites such as GitHub put parameters in their URL. For example, ``github.com/DioxusLabs`` utilizes the text after the domain to dynamically search and display content about an organization.
We want to store our blogs in a database and load them as needed. This'll help prevent our app from being bloated therefor providing faster load times. We also want our users to be able to send people a link to a specific blog post.
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. And finally, we also want our site to tell users they are on a blog page whenever the URL starts with``/blog``.
The path to our blog will look like ``/blog/myBlogPage``. ``myBlogPage`` being the URL parameter.
Dioxus Router uses the ``:name`` pattern so our route will look like ``/blog/:post``.
First, lets tell users when they are on a blog page. Add a new route in your app component.
```rs
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Router {
p { "-- Dioxus Blog --" }
self::navbar {}
Route { to: "/", self::homepage {}}
// NEW
Route {
to: "/blog",
}
Route { to: "", self::page_not_found {}}
}
})
}
```
Routes can take components as parameters and we know that a route is a component. We nest routes by doing exactly what they are called, nesting them:
```rs
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Router {
p { "-- Dioxus Blog --" }
self::navbar {}
Route { to: "/", self::homepage {}}
Route {
to: "/blog",
Route { to: "/:post", "This is my blog post!" } // NEW
}
Route { to: "", self::page_not_found {}}
}
})
}
```
Nesting our route like this isn't too helpful at first, but remember we want to tell users they are on a blog page. Let's move our ``p { "-- Dioxus Blog --" }`` inside of our ``/blog`` route.
```rs
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Router {
self::navbar {}
Route { to: "/", self::homepage {}}
Route {
to: "/blog",
p { "-- Dioxus Blog --" } // MOVED
Route { to: "/:post", "This is my blog post!" }
}
Route { to: "", self::page_not_found {}}
}
})
}
```
Now our ``-- Dioxus Blog --`` text will be displayed whenever a user is on a path that starts with ``/blog``. Displaying content in a way that is page-agnostic is useful when building navigation menus, footers, and similar.
All that's left is to handle our URL parameter. We will begin by creating a ``get_blog_post`` function. In a real site, this function would call an API endpoint to get a blog post from the database. However, that is out of the scope of this guide so we will be utilizing static text.
```rs
fn get_blog_post(id: &str) -> String {
match id {
"foo" => "Welcome to the foo blog post!".to_string(),
"bar" => "This is the bar blog post!".to_string(),
id => format!("Blog post '{id}' does not exist!")
}
}
```
Now that we have established our helper function, lets create a new ``blog_post`` component.
```rs
fn blog_post(cx: Scope) -> Element {
let blog_text = "";
cx.render(rsx! {
p { "{blog_text}" }
})
}
```
All that's left is to extract the blog id from the URL and to call our helper function to get the blog text. To do this we need to utilize Dioxus Router's ``use_route`` hook.
First start by adding ``use_route`` to your imports and then utilize the hook in your ``blog_post`` component.
```rs
use dioxus::{
prelude::*,
router::{use_route, Link, Route, Router}, // UPDATED
};
...
fn blog_post(cx: Scope) -> Element {
let route = use_route(cx); // NEW
let blog_text = "";
cx.render(rsx! {
p { "{blog_text}" }
})
}
```
Dioxus Router provides built in methods to extract information from a route. We could utilize the ``segments``, ``nth_segment``, or ``last_segment`` method for our case but we'll use the ``segment`` method which extracts a specific URL parameter.
The ``segment`` method also parses the parameter into any type for us. We'll use a match expression that handles a parsing error and on success, uses our helper function to grab the blog post.
```rs
fn blog_post(cx: Scope) -> Element {
let route = use_route(cx);
// NEW
let blog_text = match route.segment::<String>("post").unwrap() {
Ok(val) => get_blog_post(&val),
Err(_) => "An unknown error occured".to_string(),
};
cx.render(rsx! {
p { "{blog_text}" }
})
}
```
And finally add the ``blog_post`` component to your ``app`` component:
```rs
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Router {
self::navbar {}
Route { to: "/", self::homepage {}}
Route {
to: "/blog",
p { "-- Dioxus Blog --" }
Route { to: "/:post", self::blog_post {} } // UPDATED
}
Route { to: "", self::page_not_found {}}
}
})
}
```
That's it! If you head to ``/blog/foo`` you should see ``Welcome to the foo blog post!``.
### Conclusion
In this chapter we utilized Dioxus Router's Link, URL Parameter, and ``use_route`` functionality to build the blog portion of our application. In the next and final chapter, we will go over the ``Redirect`` component to redirect non-authorized users to another page.

View file

@ -1,95 +0,0 @@
# Creating Our First Route
In this chapter, we will continue off of our new Dioxus project to create a homepage and start utilizing Dioxus Router!
### Fundamentals
Dioxus Router works based on a router and route component. If you've ever used [React Router](https://reactrouter.com/), you should feel at home with Dioxus Router.
To get started, import the ``Router`` and ``Route`` components.
```rs
use dioxus::{
prelude::*,
router::{Route, Router}
}
```
We also need an actual page to route to! Add a homepage component:
```rs
fn homepage(cx: Scope) -> Element {
cx.render(rsx! {
p { "Welcome to Dioxus Blog!" }
})
}
```
### To Route or Not to Route
We want to use Dioxus Router to seperate our application into different "pages". Dioxus Router will then determine which page to render based on the URL path.
To start using Dioxus Router, we need to use the ``Router`` component.
Replace the ``p { "Hello, wasm!" }`` in your ``app`` component with a Router component:
```rs
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Router {} // NEW
})
}
```
Now we have established a router and we can create our first route. We will be creating a route for our homepage component we created earlier.
```rs
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Router {
Route { to: "/", self::homepage {}} // NEW
}
})
}
```
If you head to your application's browser tab, you should see the text ``Welcome to Dioxus Blog!`` when on the root URL (``http://localhost:8080/``). If you enter a different path for the URL, nothing should be displayed.
This is because we told Dioxus Router to render the ``homepage`` component only when the URL path is ``/``. You can tell Dioxus Router to render any kind of component such as a ``div {}``.
### What if a Route Doesn't Exist?
In our example Dioxus Router doesn't render anything. If we wanted to, we could tell Dioxus Router to render a component all the time! Try it out:
```rs
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Router {
p { "-- Dioxus Blog --" } // NEW
Route { to: "/", self::homepage {}}
}
})
}
```
We will go into more detail about this in the next chapter.
Many sites also have a "404" page for when a URL path leads to nowhere. Dioxus Router can do this too! Create a new ``page_not_found`` component.
```rs
fn page_not_found(cx: Scope) -> Element {
cx.render(rsx! {
p { "Oops! The page you are looking for doesn't exist!" }
})
}
```
Now to tell Dioxus Router to render our new component when no route exists. Create a new route with a path of nothing:
```rs
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Router {
p { "-- Dioxus Blog --" }
Route { to: "/", self::homepage {}}
Route { to: "", self::page_not_found {}} // NEW
}
})
}
```
Now when you go to a route that doesn't exist, you should see the page not found text and the text we told Dioxus Router to render all the time.
```
// localhost:8080/abc
-- Dioxus Blog --
Oops! The page you are looking for doesn't exist!
```
> Make sure you put your empty route at the bottom or else it'll override any routes below it!
### Conclusion
In this chapter we learned how to create a route and tell Dioxus Router what component to render when the URL path is equal to what we specified. 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.

View file

@ -1,68 +0,0 @@
# Getting Started
Before we start utilizing Dioxus Router, we need to initialize a Dioxus web application.
#### Required Tools
If you haven't already, make sure you install the [dioxus-cli](https://dioxuslabs.com/nightly/cli/) build tool and the rust ``wasm32-unknown-unknown`` target:
```
$ cargo install dioxus-cli
...
$ rustup target add wasm32-unkown-unknown
...
```
### Creating the Project
First, create a new cargo binary project:
```
cargo new --bin dioxus-blog
```
Next, we need to add dioxus with the web and router feature to our ``Cargo.toml`` file.
```toml
[package]
name = "dioxus-blog"
version = "0.1.0"
edition = "2021"
[dependencies]
dioxus = { version = "0.1.8", features = ["web", "router"] }
```
Now we can start coding! Create an ``index.html`` file in the root of your project:
```html
<html>
<head>
<title>Dioxus Blog</title>
</head>
<body>
<div id="main"></div>
</body>
</html>
```
You can add whatever you want to this file, just ensure that you have a ``div`` with the id of ``main`` in the root of your body element. This is essentially a handle to where Dioxus will render your components.
Now move to ``src/main.rs`` and replace its contents with:
```rs
use dioxus::prelude::*;
fn main() {
// Launch Dioxus web app
dioxus_web::launch(app);
}
// Our root component.
fn app(cx: Scope) -> Element {
// Render "Hello, wasm!" to the screen.
cx.render(rsx! {
p { "Hello, wasm!"}
})
}
```
Our project is now setup! To make sure everything is running correctly, in the root of your project run:
```
dioxus serve --platform web
```
Then head to [http://localhost:8080](http://localhost:8080) in your browser, and you should see ``Hello, wasm!`` on your screen.
#### Conclusion
We setup a new project with Dioxus and got everything running correctly. Next we'll create a small homepage and start our journey with Dioxus Router.

View file

@ -1,14 +0,0 @@
# Dioxus Router: Guide
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!
#### You'll learn how to
- Create routes and render "pages".
- Utilize nested routes, create a navigation bar, and render content for a set of routes.
- Gather URL parameters to dynamically display content.
- Redirect your visitors wherever you want.
> Disclaimer
>
> This site 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.
You can find the complete application [here](https://github.com/DogeDark/dioxus-router-example).

View file

@ -1,51 +0,0 @@
# Redirection Perfection
You're well on your way to becoming a routing master!
In this chapter we will cover utilizing the ``Redirect`` component so you can take Rickrolling to the next level. We will also provide some optional challenges at the end if you want to continue your practice with not only Dioxus Router but with Dioxus in general.
### What Is This Redirect Thing?
The ``Redirect`` component is simple! When Dioxus determines that it should be rendered, it will redirect your application visitor to wherever you want.
In this example, let's say that you added a secret page to your site but didn't have time to program in the permission system. As a quick fix you add a redirect.
As always, let's first create a new component named ``secret_page``.
```rs
fn secret_page(cx: Scope) -> Element {
cx.render(rsx! {
p { "This page is not to be viewed!" }
})
}
```
To redirect our visitors, all we have to do is render the ``Redirect`` component. The ``Redirect`` component is very similar to the ``Link`` component. The main difference is it doesn't display anything new.
First import the ``Redirect`` component and then update your ``secret_page`` component:
```rs
use dioxus::{
prelude::*,
router::{use_route, Link, Redirect, Route, Router}, // UPDATED
};
...
fn secret_page(cx: Scope) -> Element {
cx.render(rsx! {
p { "This page is not to be viewed!" }
Redirect { to: "/" } // NEW
})
}
```
That's it! Now your users will be redirected away from the secret page.
>Similar to the ``Link`` component, the ``Redirect`` component needs to be explicitly set to redirect to an external site. To link to external sites, add the ``external: true`` property.
>```rs
>Redirect { to: "https://github.com", external: true}
>```
### Conclusion
Well done! You've completed the Dioxus Router guide book. You've built a small application and learned about the many things you can do with Dioxus Router. To continue your journey, you can find a list of challenges down below, or you can check out the [reference](../reference/index.md).
### Challenges
- Organize your components into seperate files for better maintainability.
- Give your app some style if you haven't already.
- Build an about page so your visitors know who you are.
- Add a user system that uses URL parameters.
- Create a simple admin system to create, delete, and edit blogs.
- If you want to go to the max, hook up your application to a rest API and database.

27
docs/router/src/index.md Normal file
View file

@ -0,0 +1,27 @@
# Introduction
> If you are not familiar with Dioxus itself, check out the [Dioxus book](https://dioxuslabs.com/docs/0.3/guide/en/) first.
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.
For this purpose, Dioxus provides a router. Use the `cargo add` command to add the dependency:
```sh
cargo add dioxus-router
```
This book is intended to get you up to speed with Dioxus Router. It is split
into two sections:
1. The [reference](./reference/index.md) 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.
2. If you prefer a learning-by-doing approach, you can check out the
_[example project](./example/index.md)_. It guides you through
creating a dioxus app, setting up the router, and using some of its
functionality.
> 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/).

1
docs/router/src/lib.rs Normal file
View file

@ -0,0 +1 @@
// empty (we only need this crate for the examples)

View file

@ -0,0 +1,32 @@
# History Buttons
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.
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:
- [`GoBackButton`](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoBackButton.html)
- [`GoForwardButton`](https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoForwardButton.html)
> If you want to navigate through the history programmatically, take a look at
> [`programmatic navigation`](./navigation/programmatic.md).
```rust, no_run
{{#include ../../examples/history_buttons.rs:history_buttons}}
```
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.
Importantly, neither [`WebHistory`] supports that feature.
This is due to limitations of the browser History API.
However, in both cases, the router will just ignore button presses, if there is
no history to navigate to.
Also, when using [`WebHistory`], the history buttons might
navigate a user to a history entry outside your app.

View file

@ -0,0 +1,20 @@
# History Providers
[`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).
The router provides two [`HistoryProvider`]s, but you can also create your own.
The two default implementations are:
- The [`MemoryHistory`] is a custom implementation that works in memory.
- The [`WebHistory`] integrates with the browser's URL.
By default, the router uses the [`MemoryHistory`]. It might be changed to use
[`WebHistory`] when the `web` feature is active, but that is not guaranteed.
You can override the default history:
```rust, no_run
{{#include ../../examples/history_provider.rs:app}}
```

View file

@ -1,2 +1,23 @@
# Dioxus Router: Reference # Adding the Router to Your Application
This section includes a reference to Dioxus Router's API and functionality.
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.
> Make sure you added the `dioxus-router` dependency as explained in the
> [introduction](../index.md).
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.
First, we define the router with the router macro:
```rust, no_run
{{#include ../../examples/first_route.rs:router}}
```
Then we render the router with the [`Router`] component.
```rust, no_run
{{#include ../../examples/first_route.rs:app}}
```

View file

@ -0,0 +1,19 @@
# Layouts
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.
[`Outlet`] tells the router where to render content in layouts. In the following example,
the Index will be rendered within the [`Outlet`].
```rust, no_run
{{#include ../../examples/outlet.rs:outlet}}
```
The example above will output the following HTML (line breaks added for
readability):
```html
<header>header</header>
<h1>Index</h1>
<footer>footer</footer>
```

View file

@ -0,0 +1,39 @@
# Links & Navigation
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:
```html
<a href="/other">Link to an other page</a>
```
However, we cannot do that when using the router for three reasons:
1. 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.
2. 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.
3. Anchor tags cannot check if the target page exists. This means we cannot
prevent accidentally linking to non-existent pages.
To solve these problems, the router provides us with a [`Link`] component we can
use like this:
```rust, no_run
{{#include ../../../examples/links.rs:nav}}
```
The `target` in the example above is similar to the `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
[`NavigationTarget`]:
- 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 [`Routable`] enum. This type of navigation can never fail if the link component is used inside a router component.
- [`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.
> The [`Link`] accepts several props that modify its behavior. See the API docs
> for more details.

View file

@ -0,0 +1,32 @@
# Programmatic Navigation
Sometimes we want our application to navigate to another page without having the
user click on a link. This is called programmatic navigation.
## Using a Navigator
We can get a navigator with the [`use_navigator`] hook. This hook returns a [`Navigator`].
We can use the [`Navigator`] to trigger four different kinds of navigation:
- `push` will navigate to the target. It works like a regular anchor tag.
- `replace` works like `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.
- `Go back` works like the browser's back button.
- `Go forward` works like the browser's forward button.
```rust, no_run
{{#include ../../../examples/navigator.rs:nav}}
```
You might have noticed that, like [`Link`], the [`Navigator`]s `push` and
`replace` functions take a [`NavigationTarget`]. This means we can use either
[`Internal`], or [`External`] targets.
## External Navigation Targets
Unlike a [`Link`], the [`Navigator`] cannot rely on the browser (or webview) to
handle navigation to external targets via a generated anchor element.
This means, that under certain conditions, navigation to external targets can
fail.

View file

@ -0,0 +1,13 @@
# Redirects
In some cases, we may want to redirect our users to another page whenever they
open a specific path. We can tell the router to do this with the `#[redirect]`
attribute.
The `#[redirect]` attribute accepts a route and a closure with all of the parameters defined in the route. The closure must return a [`NavigationTarget`].
In the following example, we will redirect everybody from `/myblog` and `/myblog/:id` to `/blog` and `/blog/:id` respectively
```rust, no_run
{{#include ../../examples/full_example.rs:router}}
```

View file

@ -0,0 +1,65 @@
# Defining Routes
When creating a [`Routable`] enum, we can define routes for our application using the `route("path")` attribute.
## Route Segments
Each route is made up of segments. Most segments are separated by `/` characters in the path.
There are four fundamental types of segments:
1. [Static segments](#static-segments) are fixed strings that must be present in the path.
2. [Dynamic segments](#dynamic-segments) are types that can be parsed from a segment.
3. [Catch-all segments](#catch-all-segments) are types that can be parsed from multiple segments.
4. [Query segments](#query-segments) are types that can be parsed from the query string.
Routes are matched:
- First, from most specific to least specific (Static then Dynamic then Catch All) (Query is always matched)
- Then, if multiple routes match the same path, the order in which they are defined in the enum is followed.
## Static segments
Fixed routes match a specific path. For example, the route `#[route("/about")]` will match the path `/about`.
```rust, no_run
{{#include ../../../examples/static_segments.rs:route}}
```
## Dynamic Segments
Dynamic segments are in the form of `:name` where `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.
The segment can be of any type that implements `FromStr`.
```rust, no_run
{{#include ../../../examples/dynamic_segments.rs:route}}
```
## Catch All Segments
Catch 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.
The segment can be of any type that implements `FromSegments`. (Vec<String> implements this by default)
Catch All segments must be the _last route segment_ in the path (query segments are not counted) and cannot be included in nests.
```rust, no_run
{{#include ../../../examples/catch_all_segments.rs:route}}
```
## Query Segments
Query segments are in the form of `?:name` where `name` is the name of the field in the route variant.
Unlike [Dynamic Segments](#dynamic-segments) and [Catch All Segments](#catch-all-segments), parsing a Query segment must not fail.
The segment can be of any type that implements `FromQuery`.
Query segments must be the _after all route segments_ and cannot be included in nests.
```rust, no_run
{{#include ../../../examples/query_segments.rs:route}}
```

View file

@ -0,0 +1,39 @@
# Nested Routes
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:
```plain
└ Settings
├ General Settings (displayed when opening the settings)
├ Change Password
└ Privacy Settings
```
We might want to map this structure to these paths and components:
```plain
/settings -> Settings { GeneralSettings }
/settings/password -> Settings { PWSettings }
/settings/privacy -> Settings { PrivacySettings }
```
Nested routes allow us to do this without repeating /settings in every route.
## Nesting
To nest routes, we use the `#[nest("path")]` and `#[end_nest]` attributes.
The path in nest must not:
1. Contain a [Catch All Segment](index.md#catch-all-segments)
2. Contain a [Query Segment](index.md#query-segments)
If you define a dynamic segment in a nest, it will be available to all child routes and layouts.
To finish a nest, we use the `#[end_nest]` attribute or the end of the enum.
```rust, no_run
{{#include ../../../examples/nest.rs:route}}
```

View file

@ -0,0 +1,25 @@
# Routing Update Callback
In some cases, we might want to run custom code when the current route changes.
For this reason, the [`RouterConfig`] exposes an `on_update` field.
## How does the callback behave?
The `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.
If the callback returns a [`NavigationTarget`], the router will replace the
current location with the specified target. It will not call the
`on_update` again.
If at any point the router encounters a
[navigation failure](./failures/index.md), 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.
## Code Example
```rust, no_run
{{#include ../../examples/routing_update.rs:router}}
```

View file

@ -0,0 +1,15 @@
# Static Generation
## Getting the Sitemap
The [`Routable`] trait includes an associated [`SITE_MAP`] constant that contains the map of all of the routes in the enum.
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 `.flatten()` method.
## Generating a Sitemap
To statically render pages, we need to flatten the route tree and generate a file for each route that contains only static segments:
```rust, no_run
{{#include ../../../../packages/router/examples/static_generation.rs}}
```

View file

@ -9,8 +9,8 @@ It is also very much usable as a template for your projects, if you're aiming to
Make sure you have Dioxus CLI installed (if you're unsure, run `cargo install dioxus-cli`). Make sure you have Dioxus CLI installed (if you're unsure, run `cargo install dioxus-cli`).
You can run `dioxus serve` in this directory to start the web server locally, or run You can run `dx serve` in this directory to start the web server locally, or run
`dioxus build --release` to build the project so you can deploy it on a separate web-server. `dx build --release` to build the project so you can deploy it on a separate web-server.
## Project Structure ## Project Structure
``` ```

View file

@ -1,11 +1,21 @@
/* //! Tiny CRM: A port of the Yew CRM example to Dioxus.
Tiny CRM: A port of the Yew CRM example to Dioxus. #![allow(non_snake_case)]
*/
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_router::{Link, Route, Router}; use dioxus_router::prelude::*;
fn main() { fn main() {
dioxus_desktop::launch(app); dioxus_desktop::launch(App);
}
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
#[route("/")]
ClientList {},
#[route("/new")]
ClientAdd {},
#[route("/settings")]
Settings {},
} }
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
@ -15,92 +25,174 @@ pub struct Client {
pub description: String, pub description: String,
} }
fn app(cx: Scope) -> Element { type ClientContext = Vec<Client>;
let clients = use_ref(cx, || vec![] as Vec<Client>);
let firstname = use_state(cx, String::new);
let lastname = use_state(cx, String::new);
let description = use_state(cx, String::new);
cx.render(rsx!( fn App(cx: Scope) -> Element {
body { use_shared_state_provider::<ClientContext>(cx, Default::default);
margin_left: "35%",
render! {
link { link {
rel: "stylesheet", rel: "stylesheet",
href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css", href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css",
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5", integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
crossorigin: "anonymous", crossorigin: "anonymous",
} }
style { "
.red {{
background-color: rgb(202, 60, 60) !important;
}}
" }
h1 { "Dioxus CRM Example" } h1 { "Dioxus CRM Example" }
Router {
Route { to: "/", Router {}
div { class: "crm", }
h2 { margin_bottom: "10px", "List of clients" } }
div { class: "clients", margin_left: "10px",
clients.read().iter().map(|client| rsx!( #[inline_props]
div { class: "client", style: "margin-bottom: 50px", fn ClientList(cx: Scope) -> Element {
p { "First Name: {client.first_name}" } let clients = use_shared_state::<ClientContext>(cx).unwrap();
p { "Last Name: {client.last_name}" }
cx.render(rsx! {
h2 { "List of Clients" }
Link {
target: Route::ClientAdd {},
class: "pure-button pure-button-primary",
"Add Client"
}
Link {
target: Route::Settings {},
class: "pure-button",
"Settings"
}
clients.read().iter().map(|client| rsx! {
div {
class: "client",
style: "margin-bottom: 50px",
p { "Name: {client.first_name} {client.last_name}" }
p { "Description: {client.description}" } p { "Description: {client.description}" }
}
})
}) })
)
} }
Link { to: "/new", class: "pure-button pure-button-primary", "Add New" }
Link { to: "/settings", class: "pure-button", "Settings" } #[inline_props]
} fn ClientAdd(cx: Scope) -> Element {
} let clients = use_shared_state::<ClientContext>(cx).unwrap();
Route { to: "/new", let first_name = use_state(cx, String::new);
div { class: "crm", let last_name = use_state(cx, String::new);
h2 { margin_bottom: "10px", "Add new client" } let description = use_state(cx, String::new);
form { class: "pure-form",
input { let navigator = use_navigator(cx);
class: "new-client firstname",
placeholder: "First name", cx.render(rsx! {
value: "{firstname}", h2 { "Add new Client" }
oninput: move |e| firstname.set(e.value.clone())
form {
class: "pure-form pure-form-aligned",
onsubmit: move |_| {
let mut clients = clients.write();
clients.push(Client {
first_name: first_name.to_string(),
last_name: last_name.to_string(),
description: description.to_string(),
});
navigator.push(Route::ClientList {});
},
fieldset {
div {
class: "pure-control-group",
label {
"for": "first_name",
"First Name"
} }
input { input {
class: "new-client lastname", id: "first_name",
placeholder: "Last name", "type": "text",
value: "{lastname}", placeholder: "First Name…",
oninput: move |e| lastname.set(e.value.clone()) required: "",
value: "{first_name}",
oninput: move |e| first_name.set(e.value.clone())
}
}
div {
class: "pure-control-group",
label {
"for": "last_name",
"Last Name"
}
input {
id: "last_name",
"type": "text",
placeholder: "Last Name…",
required: "",
value: "{last_name}",
oninput: move |e| last_name.set(e.value.clone())
}
}
div {
class: "pure-control-group",
label {
"for": "description",
"Description"
} }
textarea { textarea {
class: "new-client description", id: "description",
placeholder: "Description", placeholder: "Description",
value: "{description}", value: "{description}",
oninput: move |e| description.set(e.value.clone()) oninput: move |e| description.set(e.value.clone())
} }
} }
button {
class: "pure-button pure-button-primary",
onclick: move |_| {
clients.write().push(Client {
description: description.to_string(),
first_name: firstname.to_string(),
last_name: lastname.to_string(),
});
description.set(String::new());
firstname.set(String::new());
lastname.set(String::new());
},
"Add New"
}
Link { to: "/", class: "pure-button", "Go Back" }
}
}
Route { to: "/settings",
div { div {
h2 { margin_bottom: "10px", "Settings" } class: "pure-controls",
button { button {
background: "rgb(202, 60, 60)", "type": "submit",
class: "pure-button pure-button-primary", class: "pure-button pure-button-primary",
onclick: move |_| clients.write().clear(), "Save"
"Remove all clients"
} }
Link { to: "/", class: "pure-button pure-button-primary", "Go Back" } Link {
target: Route::ClientList {},
class: "pure-button pure-button-primary red",
"Cancel"
} }
} }
} }
} }
)) })
}
#[inline_props]
fn Settings(cx: Scope) -> Element {
let clients = use_shared_state::<ClientContext>(cx).unwrap();
cx.render(rsx! {
h2 { "Settings" }
button {
class: "pure-button pure-button-primary red",
onclick: move |_| {
let mut clients = clients.write();
clients.clear();
},
"Remove all Clients"
}
Link {
target: Route::ClientList {},
class: "pure-button",
"Go back"
}
})
} }

View file

@ -10,7 +10,7 @@ struct ListBreeds {
message: HashMap<String, Vec<String>>, message: HashMap<String, Vec<String>>,
} }
async fn app_root(cx: Scope<'_>) -> Element { fn app_root(cx: Scope<'_>) -> Element {
let breed = use_state(cx, || "deerhound".to_string()); let breed = use_state(cx, || "deerhound".to_string());
let breeds = use_future!(cx, || async move { let breeds = use_future!(cx, || async move {
@ -21,13 +21,13 @@ async fn app_root(cx: Scope<'_>) -> Element {
.await .await
}); });
match breeds.await { match breeds.value()? {
Ok(breeds) => cx.render(rsx! { Ok(breed_list) => cx.render(rsx! {
div { height: "500px", div { height: "500px",
h1 { "Select a dog breed!" } h1 { "Select a dog breed!" }
div { display: "flex", div { display: "flex",
ul { flex: "50%", ul { flex: "50%",
for cur_breed in breeds.message.keys().take(10) { for cur_breed in breed_list.message.keys().take(10) {
li { key: "{cur_breed}", li { key: "{cur_breed}",
button { button {
onclick: move |_| breed.set(cur_breed.clone()), onclick: move |_| breed.set(cur_breed.clone()),
@ -50,7 +50,7 @@ struct DogApi {
} }
#[inline_props] #[inline_props]
async fn breed_pic(cx: Scope, breed: String) -> Element { fn breed_pic(cx: Scope, breed: String) -> Element {
let fut = use_future!(cx, |breed| async move { let fut = use_future!(cx, |breed| async move {
reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random")) reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
.await .await
@ -59,7 +59,7 @@ async fn breed_pic(cx: Scope, breed: String) -> Element {
.await .await
}); });
match fut.await { match fut.value()? {
Ok(resp) => render! { Ok(resp) => render! {
div { div {
button { button {

View file

@ -7,11 +7,11 @@ fn main() {
dioxus_desktop::launch(app) dioxus_desktop::launch(app)
} }
static NAME: Atom<String> = |_| "world".to_string(); static NAME: Atom<String> = Atom(|_| "world".to_string());
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
use_init_atom_root(cx); use_init_atom_root(cx);
let name = use_read(cx, NAME); let name = use_read(cx, &NAME);
cx.render(rsx! { cx.render(rsx! {
div { "hello {name}!" } div { "hello {name}!" }
@ -21,7 +21,7 @@ fn app(cx: Scope) -> Element {
} }
fn Child(cx: Scope) -> Element { fn Child(cx: Scope) -> Element {
let set_name = use_set(cx, NAME); let set_name = use_set(cx, &NAME);
cx.render(rsx! { cx.render(rsx! {
button { button {
@ -31,10 +31,10 @@ fn Child(cx: Scope) -> Element {
}) })
} }
static NAMES: AtomRef<Vec<String>> = |_| vec!["world".to_string()]; static NAMES: AtomRef<Vec<String>> = AtomRef(|_| vec!["world".to_string()]);
fn ChildWithRef(cx: Scope) -> Element { fn ChildWithRef(cx: Scope) -> Element {
let names = use_atom_ref(cx, NAMES); let names = use_atom_ref(cx, &NAMES);
cx.render(rsx! { cx.render(rsx! {
div { div {

View file

@ -1,32 +1,47 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use dioxus::prelude::*; use dioxus::prelude::*;
use tokio::time::sleep;
fn main() { fn main() {
dioxus_desktop::launch(App); dioxus_desktop::launch(App);
} }
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
let enable_directory_upload = use_state(cx, || false);
let files_uploaded: &UseRef<Vec<String>> = use_ref(cx, Vec::new); let files_uploaded: &UseRef<Vec<String>> = use_ref(cx, Vec::new);
cx.render(rsx! { cx.render(rsx! {
label {
input {
r#type: "checkbox",
checked: "{enable_directory_upload}",
oninput: move |evt| {
enable_directory_upload.set(evt.value.parse().unwrap());
},
},
"Enable directory upload"
}
input { input {
r#type: "file", r#type: "file",
accept: ".txt,.rs", accept: ".txt,.rs",
multiple: true, multiple: true,
directory: **enable_directory_upload,
onchange: |evt| { onchange: |evt| {
to_owned![files_uploaded]; to_owned![files_uploaded];
async move { async move {
if let Some(file_engine) = &evt.files { if let Some(file_engine) = &evt.files {
let files = file_engine.files(); let files = file_engine.files();
for file_name in &files { for file_name in files {
if let Some(file) = file_engine.read_file_to_string(file_name).await{ sleep(std::time::Duration::from_secs(1)).await;
files_uploaded.write().push(file); files_uploaded.write().push(file_name);
}
} }
} }
} }
}, },
} },
div { "progress: {files_uploaded.read().len()}" },
ul { ul {
for file in files_uploaded.read().iter() { for file in files_uploaded.read().iter() {

View file

@ -1,6 +1,8 @@
#![allow(non_snake_case)]
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_desktop::{tao::dpi::LogicalSize, Config, WindowBuilder}; use dioxus_desktop::{tao::dpi::LogicalSize, Config, WindowBuilder};
use dioxus_router::{Link, Route, Router}; use dioxus_router::prelude::*;
fn main() { fn main() {
env_logger::init(); env_logger::init();
@ -15,24 +17,63 @@ fn main() {
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
cx.render(rsx! { render! {
div { Router {}
Router { }
Route { to: "/", "Home" } }
Route { to: "/games", "Games" }
Route { to: "/play", "Play" } #[derive(Routable, Clone)]
Route { to: "/settings", "Settings" } #[rustfmt::skip]
enum Route {
#[layout(Footer)]
#[route("/")]
Home {},
#[route("/games")]
Games {},
#[route("/play")]
Play {},
#[route("/settings")]
Settings {},
}
#[inline_props]
fn Footer(cx: Scope) -> Element {
render! {
div {
Outlet { }
p {
"----"
}
p { "----" }
nav { nav {
ul { ul {
Link { to: "/", li { "Home" } } li { Link { target: Route::Home {}, "Home" } }
Link { to: "/games", li { "Games" } } li { Link { target: Route::Games {}, "Games" } }
Link { to: "/play", li { "Play" } } li { Link { target: Route::Play {}, "Play" } }
Link { to: "/settings", li { "Settings" } } li { Link { target: Route::Settings {}, "Settings" } }
} }
} }
} }
} }
}) }
#[inline_props]
fn Home(cx: Scope) -> Element {
render!("Home")
}
#[inline_props]
fn Games(cx: Scope) -> Element {
render!("Games")
}
#[inline_props]
fn Play(cx: Scope) -> Element {
render!("Play")
}
#[inline_props]
fn Settings(cx: Scope) -> Element {
render!("Settings")
} }

View file

@ -1,5 +1,7 @@
#![allow(non_snake_case)]
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_router::{Link, Route, Router}; use dioxus_router::prelude::*;
fn main() { fn main() {
dioxus_desktop::launch(app); dioxus_desktop::launch(app);
@ -21,15 +23,39 @@ fn app(cx: Scope) -> Element {
} }
} }
div { div {
Router { Router {}
Route { to: "/", h1 { "Home" } },
Route { to: "/settings", h1 { "settings" } },
p { "----"}
ul {
Link { to: "/", li { "Router link to home" } },
Link { to: "/settings", li { "Router link to settings" } },
}
}
} }
)) ))
} }
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
#[layout(Header)]
#[route("/")]
Home {},
#[route("/settings")]
Settings {},
}
#[inline_props]
fn Header(cx: Scope) -> Element {
render! {
h1 { "Your app here" }
ul {
li { Link { target: Route::Home {}, "home" } }
li { Link { target: Route::Settings {}, "settings" } }
}
Outlet {}
}
}
#[inline_props]
fn Home(cx: Scope) -> Element {
render!(h1 { "Home" })
}
#[inline_props]
fn Settings(cx: Scope) -> Element {
render!(h1 { "Settings" })
}

View file

@ -1,67 +1,112 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_router::{Link, Route, Router}; use dioxus_router::prelude::*;
use serde::Deserialize;
fn main() { fn main() {
dioxus_desktop::launch(app); #[cfg(target_arch = "wasm32")]
dioxus_web::launch(App);
#[cfg(not(target_arch = "wasm32"))]
dioxus_desktop::launch(App);
} }
fn app(cx: Scope) -> Element { // ANCHOR: router
cx.render(rsx! { #[derive(Routable, Clone)]
Router { #[rustfmt::skip]
enum Route {
#[layout(NavBar)]
#[route("/")]
Home {},
#[nest("/blog")]
#[layout(Blog)]
#[route("/")]
BlogList {},
#[route("/blog/:name")]
BlogPost { name: String },
#[end_layout]
#[end_nest]
#[end_layout]
#[nest("/myblog")]
#[redirect("/", || Route::BlogList {})]
#[redirect("/:name", |name: String| Route::BlogPost { name })]
#[end_nest]
#[route("/:..route")]
PageNotFound {
route: Vec<String>,
},
}
// ANCHOR_END: router
fn App(cx: Scope) -> Element {
render! {
Router {}
}
}
#[inline_props]
fn NavBar(cx: Scope) -> Element {
render! {
nav {
ul { ul {
Link { to: "/", li { "Go home!" } } li { Link { target: Route::Home {}, "Home" } }
Link { to: "/users", li { "List all users" } } li { Link { target: Route::BlogList {}, "Blog" } }
Link { to: "/blog", li { "Blog posts" } }
Link { to: "/users/bill", li { "List all users" } }
Link { to: "/blog/5", li { "Blog post 5" } }
} }
Route { to: "/", "Home" }
Route { to: "/users", "User list" }
Route { to: "/users/:name", User {} }
Route { to: "/blog", "Blog list" }
Route { to: "/blog/:post", BlogPost {} }
Route { to: "", "Err 404 Route Not Found" }
} }
}) Outlet {}
}
} }
fn BlogPost(cx: Scope) -> Element { #[inline_props]
let post = dioxus_router::use_route(cx).last_segment().unwrap(); fn Home(cx: Scope) -> Element {
render! {
cx.render(rsx! { h1 { "Welcome to the Dioxus Blog!" }
div { }
h1 { "Reading blog post: {post}" } }
p { "example blog post" }
} #[inline_props]
}) fn Blog(cx: Scope) -> Element {
} render! {
h1 { "Blog" }
#[derive(Deserialize)] Outlet {}
struct Query { }
bold: bool, }
}
#[inline_props]
fn User(cx: Scope) -> Element { fn BlogList(cx: Scope) -> Element {
let post = dioxus_router::use_route(cx).last_segment().unwrap(); render! {
h2 { "Choose a post" }
let query = dioxus_router::use_route(cx) ul {
.query::<Query>() li {
.unwrap_or(Query { bold: false }); Link {
target: Route::BlogPost { name: "Blog post 1".into() },
cx.render(rsx! { "Read the first blog post"
div { }
h1 { "Reading blog post: {post}" } }
p { "example blog post" } li {
Link {
if query.bold { target: Route::BlogPost { name: "Blog post 2".into() },
rsx!{ b { "bold" } } "Read the second blog post"
} else { }
rsx!{ i { "italic" } } }
}
}
}
#[inline_props]
fn BlogPost(cx: Scope, name: String) -> Element {
render! {
h2 { "Blog Post: {name}"}
}
}
#[inline_props]
fn PageNotFound(cx: Scope, route: Vec<String>) -> Element {
render! {
h1 { "Page not found" }
p { "We are terribly sorry, but the page you requested doesn't exist." }
pre {
color: "red",
"log:\nattemped to navigate to: {route:?}"
} }
} }
})
} }

77
examples/shared_state.rs Normal file
View file

@ -0,0 +1,77 @@
#![allow(non_snake_case)]
use std::collections::HashMap;
use dioxus::prelude::*;
fn main() {
dioxus_desktop::launch(App);
}
#[derive(Default)]
struct CoolData {
data: HashMap<usize, String>,
}
impl CoolData {
pub fn new(data: HashMap<usize, String>) -> Self {
Self { data }
}
pub fn view(&self, id: &usize) -> Option<&String> {
self.data.get(id)
}
pub fn set(&mut self, id: usize, data: String) {
self.data.insert(id, data);
}
}
#[rustfmt::skip]
pub fn App(cx: Scope) -> Element {
use_shared_state_provider(cx, || CoolData::new(HashMap::from([
(0, "Hello, World!".to_string()),
(1, "Dioxus is amazing!".to_string())
])));
render!(
DataEditor {
id: 0
}
DataEditor {
id: 1
}
DataView {
id: 0
}
DataView {
id: 1
}
)
}
#[inline_props]
fn DataEditor(cx: Scope, id: usize) -> Element {
let cool_data = use_shared_state::<CoolData>(cx).unwrap().read();
let my_data = &cool_data.view(id).unwrap();
render!(p {
"{my_data}"
})
}
#[inline_props]
fn DataView(cx: Scope, id: usize) -> Element {
let cool_data = use_shared_state::<CoolData>(cx).unwrap();
let oninput = |e: FormEvent| cool_data.write().set(*id, e.value.clone());
let cool_data = cool_data.read();
let my_data = &cool_data.view(id).unwrap();
render!(input {
oninput: oninput,
value: "{my_data}"
})
}

View file

@ -1,12 +1,11 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_router::*; use dioxus_router::prelude::*;
fn main() { fn main() {
simple_logger::SimpleLogger::new() simple_logger::SimpleLogger::new()
.with_level(log::LevelFilter::Debug) .with_level(log::LevelFilter::Debug)
.with_module_level("dioxus_router", log::LevelFilter::Trace)
.with_module_level("dioxus", log::LevelFilter::Trace) .with_module_level("dioxus", log::LevelFilter::Trace)
.init() .init()
.unwrap(); .unwrap();
@ -14,49 +13,69 @@ fn main() {
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
cx.render(rsx! { render! {
Router { Router {}
}
}
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
#[layout(NavBar)]
#[route("/")]
Home {},
#[nest("/new")]
#[route("/")]
BlogList {},
#[route("/:post")]
BlogPost {
post: String,
},
#[end_nest]
#[route("/oranges")]
Oranges {},
}
#[inline_props]
fn NavBar(cx: Scope) -> Element {
render! {
h1 { "Your app here" } h1 { "Your app here" }
ul { ul {
Link { to: "/", li { "home" } } li { Link { target: Route::Home {}, "home" } }
Link { to: "/blog", li { "blog" } } li { Link { target: Route::BlogList {}, "blog" } }
Link { to: "/blog/tim", li { "tims' blog" } } li { Link { target: Route::BlogPost { post: "tim".into() }, "tims' blog" } }
Link { to: "/blog/bill", li { "bills' blog" } } li { Link { target: Route::BlogPost { post: "bill".into() }, "bills' blog" } }
Link { to: "/blog/james", li { Link { target: Route::BlogPost { post: "james".into() }, "james amazing' blog" } }
li { "james amazing' blog" }
} }
Link { to: "/apples", li { "go to apples" } } Outlet {}
} }
Route { to: "/", Home {} }
Route { to: "/blog/", BlogList {} }
Route { to: "/blog/:id/", BlogPost {} }
Route { to: "/oranges", "Oranges are not apples!" }
Redirect { from: "/apples", to: "/oranges" }
}
})
} }
#[inline_props]
fn Home(cx: Scope) -> Element { fn Home(cx: Scope) -> Element {
log::debug!("rendering home {:?}", cx.scope_id()); log::debug!("rendering home {:?}", cx.scope_id());
cx.render(rsx! { h1 { "Home" } }) render! { h1 { "Home" } }
} }
#[inline_props]
fn BlogList(cx: Scope) -> Element { fn BlogList(cx: Scope) -> Element {
log::debug!("rendering blog list {:?}", cx.scope_id()); log::debug!("rendering blog list {:?}", cx.scope_id());
cx.render(rsx! { div { "Blog List" } }) render! { div { "Blog List" } }
} }
fn BlogPost(cx: Scope) -> Element { #[inline_props]
let Some(id) = use_route(cx).segment("id") else { fn BlogPost(cx: Scope, post: String) -> Element {
return cx.render(rsx! { div { "No blog post id" } }) log::debug!("rendering blog post {}", post);
};
log::debug!("rendering blog post {}", id); render! {
cx.render(rsx! {
div { div {
h3 { "blog post: {id:?}" } h3 { "blog post: {post}" }
Link { to: "/blog/", "back to blog list" } Link { target: Route::BlogList {}, "back to blog list" }
} }
}) }
}
#[inline_props]
fn Oranges(cx: Scope) -> Element {
render!("Oranges are not apples!")
} }

View file

@ -122,7 +122,7 @@ npx tailwindcss -i ./input.css -o ./public/tailwind.css --watch
- Run the following command in the root of the project to start the dioxus dev server: - Run the following command in the root of the project to start the dioxus dev server:
```bash ```bash
dioxus serve --hot-reload dx serve --hot-reload
``` ```
- Open the browser to http://localhost:8080 - Open the browser to http://localhost:8080

View file

@ -18,7 +18,7 @@ There are plenty Rust Elm-like frameworks in the world - we were not interested
The `RSX` DSL is _barely_ a DSL. Rustaceans will find the DSL very similar to simply assembling nested structs, but without the syntactical overhead of "Default" everywhere or having to jump through hoops with the builder pattern. Between RSX, HTML, the Raw Factory API, and the NodeBuilder syntax, there's plenty of options to choose from. The `RSX` DSL is _barely_ a DSL. Rustaceans will find the DSL very similar to simply assembling nested structs, but without the syntactical overhead of "Default" everywhere or having to jump through hoops with the builder pattern. Between RSX, HTML, the Raw Factory API, and the NodeBuilder syntax, there's plenty of options to choose from.
### What are the build times like? Why on earth would I choose Rust instead of JS/TS/Elm? ### What are the build times like? Why on earth would I choose Rust instead of JS/TS/Elm?
Dioxus builds as roughly as fast as a complex WebPack-TypeScript site. Compile times will be slower than an equivalent TypeScript site, but not unbearably slow. The Wasm compiler backend for Rust is very fast. Iterating on small components is basically instant and larger apps takes a few seconds. In practice, the compiler guarantees of Rust balance out the rebuild times. dx builds as roughly as fast as a complex WebPack-TypeScript site. Compile times will be slower than an equivalent TypeScript site, but not unbearably slow. The Wasm compiler backend for Rust is very fast. Iterating on small components is basically instant and larger apps takes a few seconds. In practice, the compiler guarantees of Rust balance out the rebuild times.
### What about Yew/Seed/Sycamore/Dominator/Dodrio/Percy? ### What about Yew/Seed/Sycamore/Dominator/Dodrio/Percy?
- Yew and Seed use an Elm-like pattern and don't support SSR or any alternate rendering platforms - Yew and Seed use an Elm-like pattern and don't support SSR or any alternate rendering platforms

View file

@ -4,7 +4,7 @@ version = "0.3.0"
edition = "2021" edition = "2021"
authors = ["Jonathan Kelley"] authors = ["Jonathan Kelley"]
description = "Autofomatter for Dioxus RSX" description = "Autofomatter for Dioxus RSX"
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/" repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com" homepage = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react"] keywords = ["dom", "ui", "gui", "react"]
@ -14,9 +14,9 @@ keywords = ["dom", "ui", "gui", "react"]
dioxus-rsx = { workspace = true } dioxus-rsx = { workspace = true }
proc-macro2 = { version = "1.0.6", features = ["span-locations"] } proc-macro2 = { version = "1.0.6", features = ["span-locations"] }
quote = "1.0" quote = "1.0"
syn = { version = "1.0.11", features = ["full", "extra-traits"] } syn = { version = "2.0", features = ["full", "extra-traits", "visit"] }
serde = { version = "1.0.136", features = ["derive"] } serde = { version = "1.0.136", features = ["derive"] }
prettyplease = { package = "prettier-please", version = "0.1.16", features = [ prettyplease = { package = "prettier-please", version = "0.2", features = [
"verbatim", "verbatim",
] } ] }

View file

@ -3,177 +3,23 @@
//! Returns all macros that match a pattern. You can use this information to autoformat them later //! Returns all macros that match a pattern. You can use this information to autoformat them later
use proc_macro2::LineColumn; use proc_macro2::LineColumn;
use syn::{Block, Expr, File, Item, Macro, Stmt}; use syn::{visit::Visit, File, Macro};
type CollectedMacro<'a> = &'a Macro; type CollectedMacro<'a> = &'a Macro;
pub fn collect_from_file<'a>(file: &'a File, macros: &mut Vec<CollectedMacro<'a>>) { pub fn collect_from_file<'a>(file: &'a File, macros: &mut Vec<CollectedMacro<'a>>) {
for item in file.items.iter() { MacroCollector::visit_file(&mut MacroCollector { macros }, file);
collect_from_item(item, macros);
}
} }
pub fn collect_from_item<'a>(item: &'a Item, macros: &mut Vec<CollectedMacro<'a>>) { struct MacroCollector<'a, 'b> {
match item { macros: &'a mut Vec<CollectedMacro<'b>>,
Item::Fn(f) => collect_from_block(&f.block, macros),
// Ignore macros if they're not rsx or render
Item::Macro(macro_) => {
if macro_.mac.path.segments[0].ident == "rsx"
|| macro_.mac.path.segments[0].ident == "render"
{
macros.push(&macro_.mac);
}
} }
// Currently disabled since we're not focused on autoformatting these impl<'a, 'b> Visit<'b> for MacroCollector<'a, 'b> {
Item::Impl(_imp) => {} fn visit_macro(&mut self, i: &'b Macro) {
Item::Trait(_) => {} self.macros.push(i);
// Global-ish things
Item::Static(f) => collect_from_expr(&f.expr, macros),
Item::Const(f) => collect_from_expr(&f.expr, macros),
Item::Mod(s) => {
if let Some((_, block)) = &s.content {
for item in block {
collect_from_item(item, macros);
} }
} }
}
// None of these we can really do anything with at the item level
Item::Macro2(_)
| Item::Enum(_)
| Item::ExternCrate(_)
| Item::ForeignMod(_)
| Item::TraitAlias(_)
| Item::Type(_)
| Item::Struct(_)
| Item::Union(_)
| Item::Use(_)
| Item::Verbatim(_) => {}
_ => {}
}
}
pub fn collect_from_block<'a>(block: &'a Block, macros: &mut Vec<CollectedMacro<'a>>) {
for stmt in &block.stmts {
match stmt {
Stmt::Item(item) => collect_from_item(item, macros),
Stmt::Local(local) => {
if let Some((_eq, init)) = &local.init {
collect_from_expr(init, macros);
}
}
Stmt::Expr(exp) | Stmt::Semi(exp, _) => collect_from_expr(exp, macros),
}
}
}
pub fn collect_from_expr<'a>(expr: &'a Expr, macros: &mut Vec<CollectedMacro<'a>>) {
// collect an expr from the exprs, descending into blocks
match expr {
Expr::Macro(macro_) => {
if macro_.mac.path.segments[0].ident == "rsx"
|| macro_.mac.path.segments[0].ident == "render"
{
macros.push(&macro_.mac);
}
}
Expr::MethodCall(e) => {
collect_from_expr(&e.receiver, macros);
for expr in e.args.iter() {
collect_from_expr(expr, macros);
}
}
Expr::Assign(exp) => {
collect_from_expr(&exp.left, macros);
collect_from_expr(&exp.right, macros);
}
Expr::Async(b) => collect_from_block(&b.block, macros),
Expr::Block(b) => collect_from_block(&b.block, macros),
Expr::Closure(c) => collect_from_expr(&c.body, macros),
Expr::Let(l) => collect_from_expr(&l.expr, macros),
Expr::Unsafe(u) => collect_from_block(&u.block, macros),
Expr::Loop(l) => collect_from_block(&l.body, macros),
Expr::Call(c) => {
collect_from_expr(&c.func, macros);
for expr in c.args.iter() {
collect_from_expr(expr, macros);
}
}
Expr::ForLoop(b) => {
collect_from_expr(&b.expr, macros);
collect_from_block(&b.body, macros);
}
Expr::If(f) => {
collect_from_expr(&f.cond, macros);
collect_from_block(&f.then_branch, macros);
if let Some((_, else_branch)) = &f.else_branch {
collect_from_expr(else_branch, macros);
}
}
Expr::Yield(y) => {
if let Some(expr) = &y.expr {
collect_from_expr(expr, macros);
}
}
Expr::Return(r) => {
if let Some(expr) = &r.expr {
collect_from_expr(expr, macros);
}
}
Expr::Match(l) => {
collect_from_expr(&l.expr, macros);
for arm in l.arms.iter() {
if let Some((_, expr)) = &arm.guard {
collect_from_expr(expr, macros);
}
collect_from_expr(&arm.body, macros);
}
}
Expr::While(w) => {
collect_from_expr(&w.cond, macros);
collect_from_block(&w.body, macros);
}
// don't both formatting these for now
Expr::Array(_)
| Expr::AssignOp(_)
| Expr::Await(_)
| Expr::Binary(_)
| Expr::Box(_)
| Expr::Break(_)
| Expr::Cast(_)
| Expr::Continue(_)
| Expr::Field(_)
| Expr::Group(_)
| Expr::Index(_)
| Expr::Lit(_)
| Expr::Paren(_)
| Expr::Path(_)
| Expr::Range(_)
| Expr::Reference(_)
| Expr::Repeat(_)
| Expr::Struct(_)
| Expr::Try(_)
| Expr::TryBlock(_)
| Expr::Tuple(_)
| Expr::Type(_)
| Expr::Unary(_)
| Expr::Verbatim(_) => {}
_ => {}
};
}
pub fn byte_offset(input: &str, location: LineColumn) -> usize { pub fn byte_offset(input: &str, location: LineColumn) -> usize {
let mut offset = 0; let mut offset = 0;

Some files were not shown because too many files have changed in this diff Show more