Merge branch 'master' into master

This commit is contained in:
Jon Kelley authored and GitHub committed 2022-04-23 23:08:58 -04:00
commit c4a19f71af
281 files changed
+15193 -3917

No files matched your search

+2 -2
View File
@@ -5,7 +5,6 @@ on:
paths:
- docs/**
- .github/workflows/docs.yml
pull_request:
branches:
- master
@@ -25,7 +24,8 @@ jobs:
run: cd docs &&
cd guide && mdbook build -d ../nightly/guide && cd .. &&
cd reference && mdbook build -d ../nightly/reference && cd .. &&
cd router && mdbook build -d ../nightly/router && cd ..
cd router && mdbook build -d ../nightly/router && cd .. &&
cd cli && mdbook build -d ../nightly/cli && cd ..
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4.2.3
+12
View File
@@ -2,6 +2,8 @@ name: macOS tests
on:
push:
branches:
- master
paths:
- packages/**
- examples/**
@@ -9,12 +11,22 @@ on:
- .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:
+35 -18
View File
@@ -2,6 +2,8 @@ name: Rust CI
on:
push:
branches:
- master
paths:
- packages/**
- examples/**
@@ -9,12 +11,22 @@ on:
- .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:
check:
if: github.event.pull_request.draft == false
name: Check
runs-on: ubuntu-latest
steps:
@@ -32,6 +44,7 @@ jobs:
command: check
test:
if: github.event.pull_request.draft == false
name: Test Suite
runs-on: ubuntu-latest
steps:
@@ -53,6 +66,7 @@ jobs:
args: tests
fmt:
if: github.event.pull_request.draft == false
name: Rustfmt
runs-on: ubuntu-latest
steps:
@@ -70,6 +84,7 @@ jobs:
args: --all -- --check
clippy:
if: github.event.pull_request.draft == false
name: Clippy
runs-on: ubuntu-latest
steps:
@@ -88,21 +103,23 @@ jobs:
command: clippy
args: -- -D warnings
coverage:
name: Coverage
runs-on: ubuntu-latest
container:
image: xd009642/tarpaulin:develop-nightly
options: --security-opt seccomp=unconfined
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Generate code coverage
run: |
apt-get update &&\
apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev -y &&\
cargo +nightly tarpaulin --verbose --tests --all-features --workspace --timeout 120 --out Xml
- name: Upload to codecov.io
uses: codecov/codecov-action@v2
with:
fail_ci_if_error: false
# Coverage is disabled until we can fix it
# coverage:
# name: Coverage
# runs-on: ubuntu-latest
# container:
# image: xd009642/tarpaulin:develop-nightly
# options: --security-opt seccomp=unconfined
# steps:
# - name: Checkout repository
# uses: actions/checkout@v2
# - name: Generate code coverage
# run: |
# apt-get update &&\
# apt-get install build-essential &&\
# apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev -y &&\
# cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out Xml
# - name: Upload to codecov.io
# uses: codecov/codecov-action@v2
# with:
# fail_ci_if_error: false
+12
View File
@@ -2,6 +2,8 @@ name: windows
on:
push:
branches:
- master
paths:
- packages/**
- examples/**
@@ -9,12 +11,22 @@ on:
- .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:
+4
View File
@@ -79,3 +79,7 @@ clonable
oninput
Webview
idanarye
Katex
mrxiaozhuox
Fruitie
Vercel
+50 -27
View File
@@ -1,29 +1,36 @@
[package]
name = "dioxus"
version = "0.1.8"
version = "0.2.3"
authors = ["Jonathan Kelley"]
edition = "2018"
edition = "2021"
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
license = "MIT OR Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
rust-version = "1.56.0"
[dependencies]
dioxus-core = { path = "./packages/core", version = "^0.1.9" }
dioxus-html = { path = "./packages/html", version = "^0.1.6", optional = true }
dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true }
dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true }
dioxus-core = { path = "./packages/core", version = "^0.2.0" }
dioxus-html = { path = "./packages/html", version = "^0.2.0", optional = true }
dioxus-core-macro = { path = "./packages/core-macro", version = "^0.2.0", optional = true }
dioxus-hooks = { path = "./packages/hooks", version = "^0.2.0", optional = true }
fermi = { path = "./packages/fermi", version = "^0.2.0", optional = true }
dioxus-web = { path = "./packages/web", version = "^0.0.5", optional = true }
dioxus-desktop = { path = "./packages/desktop", version = "^0.1.6", optional = true }
dioxus-ssr = { path = "./packages/ssr", version = "^0.1.3", optional = true }
dioxus-web = { path = "./packages/web", version = "^0.2.0", optional = true }
dioxus-desktop = { path = "./packages/desktop", version = "^0.2.2", optional = true }
dioxus-ssr = { path = "./packages/ssr", version = "^0.2.0", optional = true }
dioxus-router = { path = "./packages/router", version = "^0.1.1", optional = true }
dioxus-mobile = { path = "./packages/mobile", version = "^0.0.3", optional = true }
dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.0.0", optional = true }
# dioxus-liveview = { path = "./packages/liveview", optional = true }
dioxus-router = { path = "./packages/router", version = "^0.2.1", optional = true }
dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.2.0", optional = true }
dioxus-tui = { path = "./packages/tui", version = "^0.2.0", optional = true }
dioxus-liveview = { path = "./packages/liveview", optional = true }
# dioxus-mobile = { path = "./packages/mobile", version = "^0.2.0", optional = true }
# dioxus-rsx = { path = "./packages/rsx", optional = true }
# macro = ["dioxus-core-macro", "dioxus-rsx"]
[features]
default = ["macro", "hooks", "html"]
@@ -32,15 +39,12 @@ macro = ["dioxus-core-macro"]
hooks = ["dioxus-hooks"]
html = ["dioxus-html"]
ssr = ["dioxus-ssr"]
web = ["dioxus-web"]
web = ["dioxus-web", "dioxus-router/web"]
desktop = ["dioxus-desktop"]
ayatana = ["dioxus-desktop/ayatana"]
router = ["dioxus-router"]
# "dioxus-router/web"
# "dioxus-router/desktop"
# desktop = ["dioxus-desktop", "dioxus-router/desktop"]
# mobile = ["dioxus-mobile"]
# liveview = ["dioxus-liveview"]
tui = ["dioxus-tui"]
liveview = ["dioxus-liveview"]
[workspace]
@@ -54,18 +58,37 @@ members = [
"packages/desktop",
"packages/mobile",
"packages/interpreter",
"packages/fermi",
"packages/tui",
"packages/liveview",
]
[dev-dependencies]
futures-util = "0.3.17"
futures-util = "0.3.21"
log = "0.4.14"
num-format = "0.4.0"
separator = "0.4.1"
serde = { version = "1.0.131", features = ["derive"] }
serde = { version = "1.0.136", features = ["derive"] }
im-rc = "15.0.0"
anyhow = "1.0.51"
serde_json = "1.0.73"
anyhow = "1.0.53"
serde_json = "1.0.79"
rand = { version = "0.8.4", features = ["small_rng"] }
tokio = { version = "1.14.0", features = ["full"] }
reqwest = { version = "0.11.8", features = ["json"] }
dioxus = { path = ".", features = ["desktop", "ssr", "router"] }
tokio = { version = "1.16.1", features = ["full"] }
reqwest = { version = "0.11.9", features = ["json"] }
dioxus = { path = ".", features = ["desktop", "ssr", "router", "fermi", "tui"] }
fern = { version = "0.6.0", features = ["colored"] }
criterion = "0.3.5"
thiserror = "1.0.30"
env_logger = "0.9.0"
[[bench]]
name = "create"
harness = false
[[bench]]
name = "jsframework"
harness = false
[[bench]]
name = "tui_update"
harness = false
+11 -33
View File
@@ -1,8 +1,5 @@
<div align="center">
<h1>Dioxus</h1>
<p>
<strong>Frontend that scales.</strong>
</p>
</div>
<div align="center">
@@ -45,37 +42,30 @@
<span> | </span>
<a href="https://github.com/DioxusLabs/example-projects"> Examples </a>
<span> | </span>
<a href="https://dioxuslabs.com/guide"> Guide (0.1.8) </a>
<span> | </span>
<a href="https://dioxuslabs.com/nightly/guide"> Guide (Master) </a>
</h3>
</div>
<div align="center">
<h4>
<a href="https://github.com/DioxusLabs/dioxus/blob/master/README.md"> English </a>
<a href="https://dioxuslabs.com/guide"> Guide </a>
<span> | </span>
<a href="https://github.com/DioxusLabs/dioxus/blob/master/notes/README/ZH_CN.md"> 中文 </a>
</h3>
</div>
<br/>
Dioxus is a portable, performant, and ergonomic framework for building cross-platform user interfaces in Rust.
```rust
fn app(cx: Scope) -> Element {
let (count, set_count) = use_state(&cx, || 0);
let mut count = use_state(&cx, || 0);
cx.render(rsx!(
cx.render(rsx! {
h1 { "High-Five counter: {count}" }
button { onclick: move |_| set_count(count + 1), "Up high!" }
button { onclick: move |_| set_count(count - 1), "Down low!" }
))
button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" }
})
}
```
Dioxus can be used to deliver webapps, desktop apps, static sites, liveview apps, mobile apps (WIP), and more. At its core, Dioxus is entirely renderer agnostic and has great documentation for creating new renderers for any platform.
Dioxus can be used to deliver webapps, desktop apps, static sites, mobile apps, TUI apps, liveview apps, and more. Dioxus is entirely renderer agnostic and can be used as platform for any renderer.
If you know React, then you already know Dioxus.
@@ -87,15 +77,6 @@ If you know React, then you already know Dioxus.
- Multi-channel asynchronous scheduler for first-class async support.
- And more! Read the [full release post](https://dioxuslabs.com/blog/introducing-dioxus/).
### Examples
All examples in this repo are desktop apps. To run an example, simply clone this repo and use `cargo run --example XYZ`
```
cargo run --example EXAMPLE
```
## Get Started with...
<table style="width:100%" align="center">
@@ -119,13 +100,10 @@ cargo run --example EXAMPLE
See the [awesome-dioxus](https://github.com/DioxusLabs/awesome-dioxus) page for a curated list of content in the Dioxus Ecosystem.
## Why Dioxus and why Rust?
TypeScript is a fantastic addition to JavaScript, but it's still fundamentally JavaScript. TS code runs slightly slower, has tons of configuration options, and not every package is properly typed.
In contrast, Dioxus is written in Rust - which is almost like "TypeScript on steroids".
By using Rust, we gain:
- Static types for *every* library
@@ -160,9 +138,9 @@ You shouldn't use Dioxus if:
## Comparison with other Rust UI frameworks
Dioxus primarily emphasizes **developer experience** and **familiarity with React principles**.
- [Yew](https://github.com/yewstack/yew): prefers the elm pattern instead of React-hooks, no borrowed props, supports SSR (no hydration).
- [Yew](https://github.com/yewstack/yew): prefers the elm pattern instead, no borrowed props, supports SSR (no hydration), no direct desktop/mobile support.
- [Percy](https://github.com/chinedufn/percy): Supports SSR but with less emphasis on state management and event handling.
- [Sycamore](https://github.com/sycamore-rs/sycamore): VDOM-less using fine-grained reactivity, but lacking in ergonomics.
- [Sycamore](https://github.com/sycamore-rs/sycamore): VDOM-less using fine-grained reactivity, but no direct support for desktop/mobile.
- [Dominator](https://github.com/Pauan/rust-dominator): Signal-based zero-cost alternative, less emphasis on community and docs.
- [Azul](https://azul.rs): Fully native HTML/CSS renderer for desktop applications, no support for web/ssr
@@ -197,7 +175,7 @@ Want to jump in and help build the future of Rust frontend? There's plenty of pl
This project is licensed under the [MIT license].
[MIT license]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE
[MIT license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
### Contribution
File renamed without changes.
File renamed without changes.
+220
View File
@@ -0,0 +1,220 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use dioxus::prelude::*;
use dioxus_tui::{Config, TuiContext};
criterion_group!(mbenches, tui_update);
criterion_main!(mbenches);
/// This benchmarks the cache performance of the TUI for small edits by changing one box at a time.
fn tui_update(c: &mut Criterion) {
let mut group = c.benchmark_group("Update boxes");
// We can also use loops to define multiple benchmarks, even over multiple dimensions.
for size in 1..=8u32 {
let parameter_string = format!("{}", (3 * size).pow(2));
group.bench_with_input(
BenchmarkId::new("size", parameter_string),
&size,
|b, size| {
b.iter(|| match size {
1 => dioxus::tui::launch_cfg(app3, Config::default().with_headless()),
2 => dioxus::tui::launch_cfg(app6, Config::default().with_headless()),
3 => dioxus::tui::launch_cfg(app9, Config::default().with_headless()),
4 => dioxus::tui::launch_cfg(app12, Config::default().with_headless()),
5 => dioxus::tui::launch_cfg(app15, Config::default().with_headless()),
6 => dioxus::tui::launch_cfg(app18, Config::default().with_headless()),
7 => dioxus::tui::launch_cfg(app21, Config::default().with_headless()),
8 => dioxus::tui::launch_cfg(app24, Config::default().with_headless()),
_ => (),
})
},
);
}
}
#[derive(Props, PartialEq)]
struct BoxProps {
x: usize,
y: usize,
hue: f32,
alpha: f32,
}
#[allow(non_snake_case)]
fn Box(cx: Scope<BoxProps>) -> Element {
let count = use_state(&cx, || 0);
let x = cx.props.x * 2;
let y = cx.props.y * 2;
let hue = cx.props.hue;
let display_hue = cx.props.hue as u32 / 10;
let count = count.get();
let alpha = cx.props.alpha + (count % 100) as f32;
cx.render(rsx! {
div {
left: "{x}%",
top: "{y}%",
width: "100%",
height: "100%",
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
align_items: "center",
p{"{display_hue:03}"}
}
})
}
#[derive(Props, PartialEq)]
struct GridProps {
size: usize,
}
#[allow(non_snake_case)]
fn Grid(cx: Scope<GridProps>) -> Element {
let size = cx.props.size;
let count = use_state(&cx, || 0);
let counts = use_ref(&cx, || vec![0; size * size]);
let ctx: TuiContext = cx.consume_context().unwrap();
if *count.get() + 1 >= (size * size) {
ctx.quit();
} else {
counts.with_mut(|c| {
let i = *count.current();
c[i] += 1;
c[i] = c[i] % 360;
});
count.with_mut(|i| {
*i += 1;
*i = *i % (size * size);
});
}
cx.render(rsx! {
div{
width: "100%",
height: "100%",
flex_direction: "column",
(0..size).map(|x|
{
cx.render(rsx! {
div{
width: "100%",
height: "100%",
flex_direction: "row",
(0..size).map(|y|
{
let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32;
let key = format!("{}-{}", x, y);
cx.render(rsx! {
Box{
x: x,
y: y,
alpha: 100.0,
hue: alpha,
key: "{key}",
}
})
}
)
}
})
}
)
}
})
}
fn app3(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 3,
}
}
})
}
fn app6(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 6,
}
}
})
}
fn app9(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 9,
}
}
})
}
fn app12(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 12,
}
}
})
}
fn app15(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 15,
}
}
})
}
fn app18(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 18,
}
}
})
}
fn app21(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 21,
}
}
})
}
fn app24(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 24,
}
}
})
}
+2
View File
@@ -0,0 +1,2 @@
comment: false
fail_ci_if_error: false
+1
View File
@@ -0,0 +1 @@
book
+6
View File
@@ -0,0 +1,6 @@
[book]
authors = ["YuKun Liu"]
language = "en"
multilingual = false
src = "src"
title = "Dioxus Cli"
+11
View File
@@ -0,0 +1,11 @@
# Summary
- [Introduction](./introduction.md)
- [Installation](./installation.md)
- [Create a Project](./creating.md)
- [Configure Project](./configure.md)
- [Commands](./cmd/README.md)
- [Build](./cmd/build.md)
- [Serve](./cmd/serve.md)
- [Clean](./cmd/clean.md)
- [Translate](./cmd/translate.md)
+26
View File
@@ -0,0 +1,26 @@
# Commands
In this chapter we will introduce all `dioxus-cli` commands.
> you can also use `dioxus --help` to get cli help info.
```
dioxus
Build, bundle, & ship your Dioxus app
USAGE:
dioxus [OPTIONS] <SUBCOMMAND>
OPTIONS:
-h, --help Print help information
-v Enable verbose logging
SUBCOMMANDS:
build Build the Rust WASM app and all of its assets
clean Clean output artifacts
config Dioxus config file controls
create Init a new project for Dioxus
help Print this message or the help of the given subcommand(s)
serve Build, watch & serve the Rust WASM app and all of its assets
translate Translate some source file into Dioxus code
```
+47
View File
@@ -0,0 +1,47 @@
# Build
The `dioxus build` command can help you `pack & build` a dioxus project.
```
dioxus-build
Build the Rust WASM app and all of its assets
USAGE:
dioxus build [OPTIONS]
OPTIONS:
--example <EXAMPLE> [default: ""]
--platform <PLATFORM> [default: "default_platform"]
--release [default: false]
```
You can use this command to build project to `out_dir` :
```
dioxus build --release
```
## Target platform
Use option `platform` choose build target platform:
```
# for desktop project
dioxus build --platform desktop
```
`platform` only supports `desktop` & `web`.
```
# for web project
dioxus build --platform web
```
## Build Example
You can use `--example {name}` to build a example code.
```
# build `example/test` code
dioxus build --exmaple test
```
+18
View File
@@ -0,0 +1,18 @@
# Clean
`dioxus clean` will call `target clean` and remove `out_dir` directory.
```
dioxus-clean
Clean output artifacts
USAGE:
dioxus clean
```
you can use this command to clean all build cache and the `out_dir` content.
```
dioxus clean
```
+49
View File
@@ -0,0 +1,49 @@
# Serve
The `dioxus serve` can start a dev server (include hot-reload tool) to run the project.
```
dioxus-serve
Build, watch & serve the Rust WASM app and all of its assets
USAGE:
dioxus serve [OPTIONS]
OPTIONS:
--example <EXAMPLE> [default: ""]
--platform <PLATFORM> [default: "default_platform"]
--release [default: false]
```
You can use this command to build project and start a `dev server` :
```
dioxus serve
```
## Target platform
Use option `platform` choose build target platform:
```
# for desktop project
dioxus serve --platform desktop
```
`platform` only supports `desktop` & `web`.
`dev-server` only for `web` project.
```
# for web project
dioxus serve --platform web
```
## Serve Example
You can use `--example {name}` to start a example code.
```
# build `example/test` code
dioxus serve --exmaple test
```
+57
View File
@@ -0,0 +1,57 @@
# Translate
`dioxus translate` can translate some source file into Dioxus code.
```
dioxus-translate
Translate some source file into Dioxus code
USAGE:
dioxus translate [OPTIONS] [OUTPUT]
ARGS:
<OUTPUT> Output file, stdout if not present
OPTIONS:
-c, --component Activate debug mode
-f, --file <FILE> Input file
```
## Translate HTML to stdout
```
dioxus transtale --file ./index.html
```
## Output in a file
```
dioxus translate --component --file ./index.html component.rsx
```
set `component` flag will wrap `dioxus rsx` code in a component function.
## Example
```html
<div>
<h1> Hello World </h1>
<a href="https://dioxuslabs.com/">Link</a>
</div>
```
Translate HTML to Dioxus component code.
```rust
fn component(cx: Scope) -> Element {
cx.render(rsx! {
div {
h1 { "Hello World" },
a {
href: "https://dioxuslabs.com/",
"Link"
}
}
})
}
```
+147
View File
@@ -0,0 +1,147 @@
# Configure Project
This chapter will introduce `Dioxus.toml` and anatomy the config file.
## Structure
We use `toml` to define some info for `dioxus` project.
### Application
1. ***name*** - project name & title
2. ***default_platform*** - which platform target for this project.
```
# current support: web, desktop
# default: web
default_platform = "web"
```
change this to `desktop`, the `dioxus build & serve` will default build desktop app.
3. ***out_dir*** - which directory to put the output file; use `dioxus build & service`, the output will put at this directory, and the `assets` will be also copy to here.
```
out_dir = "dist"
```
4. ***asset_dir*** - which direcotry to put your `static, assets` file, cli will automatic copy all file to `out_dir`, so you can put some resource file in there, like `CSS, JS, Image` file.
```
asset_dir = "public"
```
### Web.App
Web platform application config:
1. ***title*** - this value will display on the web page title. like `<title></title>` tag.
```
# HTML title tag content
title = "dioxus app | ⛺"
```
### Web.Watcher
Web platform `dev-server` watcher config:
1. ***reload_html*** - a boolean value; when watcher trigger, regenerate `index.html` file.
```
# when watcher trigger, regenerate the `index.html`
reload_html = true
```
2. ***watch_path*** - which files & directories will be watcher monitoring.
```
watch_path = ["src", "public"]
```
### Web.Resource
Include some `CSS Javascript` resources into html file.
1. ***style*** - include some style(CSS) file into html.
```
style = [
# include from public_dir.
"./assets/style.css",
# or some asset from online cdn.
"https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css"
]
```
2. ***script*** - include some script(JS) file into html.
```
style = [
# include from public_dir.
"./assets/index.js",
# or some asset from online cdn.
"https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js"
]
```
### Web.Resource.Dev
Only include resources at `Dev` mode.
1. ***style*** - include some style(CSS) file into html.
```
style = [
# include from public_dir.
"./assets/style.css",
# or some asset from online cdn.
"https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css"
]
```
2. ***script*** - include some script(JS) file into html.
```
style = [
# include from public_dir.
"./assets/index.js",
# or some asset from online cdn.
"https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.js"
]
```
## Config example
```toml
[application]
# App (Project) Name
name = "{{project-name}}"
# Dioxus App Default Platform
# desktop, web, mobile, ssr
default_platform = "web"
# `build` & `serve` dist path
out_dir = "dist"
# resource (public) file folder
asset_dir = "public"
[web.app]
# HTML title tag content
title = "dioxus | ⛺"
[web.watcher]
# when watcher trigger, regenerate the `index.html`
reload_html = true
# which files or dirs will be watcher monitoring
watch_path = ["src", "public"]
# include `assets` in web platform
[web.resource]
# CSS style file
style = []
# Javascript code file
script = []
[web.resource.dev]
# serve: [dev-server] only
# CSS style file
style = []
# Javascript code file
script = []
```
+38
View File
@@ -0,0 +1,38 @@
# Create a Project
Once you have the Dioxus CLI tool installed, you can use it to create dioxus project.
## Initializing a default project
The `dioxus create` command will create a new directory containing a project template.
```
dioxus create hello-dioxus
```
It will clone a default template from github template: [DioxusLabs/dioxus-template](https://github.com/DioxusLabs/dioxus-template)
> This default template is use for `web` platform application.
then you can change the current directory in to the project:
```
cd hello-dioxus
```
> Make sure `wasm32 target` is installed before running the Web project.
now we can create a `dev server` to display the project:
```
dioxus serve
```
by default, the dioxus dev server will running at: [`http://127.0.0.1:8080/`](http://127.0.0.1:8080/)
## Initalizing from other repository
you can assign which repository you want to create:
```
dioxus init hello-dioxus --template=gh:dioxuslabs/dioxus-template
```
+24
View File
@@ -0,0 +1,24 @@
# Installation
There are multiple ways to install the Dioxus CLI tool. Choose any one of the methods below that best suit your needs.
## Install from latest master version
We suggest you use `github master` version to install it now.
We have not yet released the latest version to `crates.io`
```
cargo install --git https://github.com/Dioxuslabs/cli
```
This will automatically download `Dioxus-CLI` source from github master branch,
and install it in Cargo's global binary directory (`~/.cargo/bin/` by default).
## Install from `crates.io` version
```
cargo install dioxus-cli
```
Make sure to add the Cargo bin directory to your `PATH`.
+21
View File
@@ -0,0 +1,21 @@
# Introduction
📦✨ **Dioxus-Cli** is a tool to help get dioxus projects off the ground.
![dioxus-logo](https://dioxuslabs.com/guide/images/dioxuslogo_full.png)
It includes `dev server`, `hot reload` and some `quick command` to help you use dioxus.
## Features
- [x] `html` to `rsx` conversion tool
- [x] hot reload for `web` platform
- [x] create dioxus project from `git` repo
- [x] build & pack dioxus project
- [ ] autoformat dioxus `rsx` code
## Contributors
Contributors to this guide:
- [mrxiaozhuox](https://github.com/mrxiaozhuox)
+1
View File
@@ -0,0 +1 @@
book
+6
View File
@@ -0,0 +1,6 @@
[book]
authors = ["Jonathan Kelley"]
language = "en"
multilingual = false
src = "src"
title = "Fermi Guide"
+3
View File
@@ -0,0 +1,3 @@
# Summary
- [Chapter 1](./chapter_1.md)
+1
View File
@@ -0,0 +1 @@
# Chapter 1
+10 -10
View File
@@ -2,23 +2,23 @@
![dioxuslogo](./images/dioxuslogo_full.png)
**Dioxus** is a framework and ecosystem for building fast, scalable, and robust user interfaces with the Rust programming language. This guide will help you get started with Dioxus running on the Web, Desktop, Mobile, and more.
**Dioxus** is a library for building fast, scalable, and robust user interfaces with the Rust programming language. This guide will help you get started with Dioxus running on the Web, Desktop, Mobile, and more.
```rust
fn app(cx: Scope) -> Element {
let (count, set_count) = use_state(&cx, || 0);
let mut count = use_state(&cx, || 0);
cx.render(rsx!(
h1 { "High-Five counter: {count}" }
button { onclick: move |_| set_count(count + 1), "Up high!" }
button { onclick: move |_| set_count(count - 1), "Down low!" }
button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" }
))
};
```
In general, Dioxus and React share many functional similarities. If this guide is lacking in any general concept or an error message is confusing, React's documentation might be more helpful. We are dedicated to providing a *familiar* toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc). If you know React, then you already know Dioxus. If you don't know either, this guide will still help you!
> This is an introduction book! For advanced topics, check out the [Reference](https://dioxuslabs.com/reference) instead.
> This is an introduction book! For advanced topics, check out the [Reference](/reference) instead.
## Multiplatform
@@ -37,7 +37,7 @@ The Web is the best-supported target platform for Dioxus. To run on the Web, you
Because the web is a fairly mature platform, we expect there to be very little API churn for web-based features.
[Jump to the getting started guide for the web.]()
[Jump to the getting started guide for the web.](/reference/platforms/web)
Examples:
- [TodoMVC](https://github.com/DioxusLabs/example-projects/tree/master/todomvc)
@@ -55,7 +55,7 @@ For rendering statically to an `.html` file or from a WebServer, then you'll wan
let contents = dioxus::ssr::render_vdom(&dom);
```
[Jump to the getting started guide for SSR.]()
[Jump to the getting started guide for SSR.](/reference/platforms/ssr)
Examples:
- [Example DocSite](https://github.com/dioxusLabs/docsite)
@@ -68,13 +68,13 @@ The desktop is a powerful target for Dioxus, but is currently limited in capabil
Desktop APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart.
[Jump to the getting started guide for Desktop.]()
[Jump to the getting started guide for Desktop.](/reference/platforms/desktop)
Examples:
- [File explorer](https://github.com/dioxusLabs/file-explorer/)
- [WiFi scanner](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner)
[![File ExplorerExample](https://github.com/DioxusLabs/file-explorer-example/raw/master/image.png)](https://github.com/dioxusLabs/file-explorer/)
[![File ExplorerExample](https://raw.githubusercontent.com/DioxusLabs/example-projects/master/file-explorer/image.png)](https://github.com/DioxusLabs/example-projects/tree/master/file-explorer)
### Mobile Support
---
@@ -82,7 +82,7 @@ Mobile is currently the least-supported renderer target for Dioxus. Mobile apps
Mobile support is currently best suited for CRUD-style apps, ideally for internal teams who need to develop quickly but don't care much about animations or native widgets.
[Jump to the getting started guide for Mobile.]()
[Jump to the getting started guide for Mobile.](/reference/platforms/mobile)
Examples:
- [Todo App](https://github.com/DioxusLabs/example-projects/blob/master/ios_demo)
+22 -19
View File
@@ -62,10 +62,12 @@ Generally, here's the status of each platform:
## Roadmap
These Features are planned for the future of Dioxus:
---
Core:
### Core
- [x] Release of Dioxus Core
- [x] Upgrade documentation to include more theory and be more comprehensive
- [ ] Support for HTML-side templates for lightning-fast dom manipulation
@@ -73,18 +75,18 @@ Core:
- [ ] Support for ThreadSafe (Send + Sync)
- [ ] Support for Portals
SSR
### SSR
- [x] SSR Support + Hydration
- [ ] Integrated suspense support for SSR
Desktop
### Desktop
- [ ] Declarative window management
- [ ] Templates for building/bundling
- [ ] Fully native renderer
- [ ] Access to Canvas/WebGL context natively
Mobile
- [ ] Mobile standard library
### Mobile
- [ ] Mobile standard library
- [ ] GPS
- [ ] Camera
- [ ] filesystem
@@ -93,22 +95,23 @@ Mobile
- [ ] Bluetooth
- [ ] Notifications
- [ ] Clipboard
- [ ]
- [ ] Animations
- [ ] Native Renderer
Bundling (CLI)
- [x] translation from HTML into RSX
- [ ] dev server
- [ ] live reload
- [ ] translation from JSX into RSX
- [ ] hot module replacement
- [ ] code splitting
- [ ] asset macros
- [ ] css pipeline
- [ ] image pipeline
### Bundling (CLI)
- [x] Translation from HTML into RSX
- [x] Dev server
- [x] Live reload
- [x] Translation from JSX into RSX
- [ ] Hot module replacement
- [ ] Code splitting
- [ ] Asset macros
- [ ] Css pipeline
- [ ] Image pipeline
Essential hooks
- [ ] Router
- [ ] Global state management
### Essential hooks
- [x] Router
- [x] Global state management
- [ ] Resize observer
+20 -8
View File
@@ -15,26 +15,38 @@
- [Children and Attributes](components/component_children.md)
- [How Data Flows](components/composing.md)
- [Adding Interactivity](interactivity/index.md)
- [Hooks and Internal State](interactivity/hooks.md)
- [UseState and UseRef](interactivity/importanthooks.md)
- [Event Listeners](interactivity/event_handlers.md)
- [User Input and Controlled Components](interactivity/user_input.md)
- [Lifecycle, updates, and effects](interactivity/lifecycles.md)
- [Hooks](interactivity/hooks.md)
- [UseState](interactivity/usestate.md)
- [UseRef](interactivity/useref.md)
- [User Input](interactivity/user_input.md)
<!-- - [Effects](interactivity/lifecycles.md) -->
- [Managing State](state/index.md)
- [Local State](state/localstate.md)
- [Lifting State](state/liftingstate.md)
- [Global State](state/sharedstate.md)
- [Lifting State](state/liftingstate.md)
- [Error handling](state/errorhandling.md)
- [Helper Crates](helpers/index.md)
- [Fermi](state/fermi.md)
- [Router](state/router.md)
- [Working with Async](async/index.md)
- [Tasks](async/asynctasks.md)
- [Fetching](async/fetching.md)
- [UseFuture](async/use_future.md)
- [UseCoroutine](async/coroutines.md)
<!-- - [Fetching](async/fetching.md) -->
<!-- - [Updating State](async/loading_state.md)
- [WebSockets](async/sockets.md) -->
<!-- - [Tasks](async/asynctasks.md) -->
<!--
- [Putting it all together: Dog Search Engine](tutorial/index.md)
- [New app](tutorial/new_app.md)
- [Structuring our app](tutorial/structure.md)
- [Defining State](tutorial/state.md)
- [Defining Components](tutorial/components.md)
- [Styling](tutorial/styling.md)
- [Bundling](tutorial/publishing.md)
- [Bundling](tutorial/publishing.md) -->
- [Next Steps and Advanced Topics](final.md)
+183
View File
@@ -0,0 +1,183 @@
# Coroutines
Another good tool to keep in your async toolbox are coroutines. Coroutines are futures that can be manually stopped, started, paused, and resumed.
Like regular futures, code in a Dioxus coroutine will run until the next `await` point before yielding. This low-level control over asynchronous tasks is quite powerful, allowing for infinitely looping tasks like WebSocket polling, background timers, and other periodic actions.
## `use_coroutine`
The basic setup for coroutines is the `use_coroutine` hook. Most coroutines we write will be polling loops using async/await.
```rust
fn app(cx: Scope) -> Element {
let ws: &UseCoroutine<()> = use_coroutine(&cx, |rx| async move {
// Connect to some sort of service
let mut conn = connect_to_ws_server().await;
// Wait for data on the service
while let Some(msg) = conn.next().await {
// handle messages
}
});
}
```
For many services, a simple async loop will handle the majority of use cases.
However, if we want to temporarily disable the coroutine, we can "pause" it using the `pause` method, and "resume" it using the `resume` method:
```rust
let sync: &UseCoroutine<()> = use_coroutine(&cx, |rx| async move {
// code for syncing
});
if sync.is_running() {
cx.render(rsx!{
button {
onclick: move |_| sync.pause(),
"Disable syncing"
}
})
} else {
cx.render(rsx!{
button {
onclick: move |_| sync.resume(),
"Enable syncing"
}
})
}
```
This pattern is where coroutines are extremely useful - instead of writing all the complicated logic for pausing our async tasks like we would with JavaScript promises, the Rust model allows us to just not poll our future.
## Sending Values
You might've noticed the `use_coroutine` closure takes an argument called `rx`. What is that? Well, a common pattern in complex apps is to handle a bunch of async code at once. With libraries like Redux Toolkit, managing multiple promises at once can be challenging and a common source of bugs.
With Coroutines, we have the opportunity to centralize our async logic. The `rx` parameter is an Unbounded Channel for code external to the coroutine to send data *into* the coroutine. Instead of looping on an external service, we can loop on the channel itself, processing messages from within our app without needing to spawn a new future. To send data into the coroutine, we would call "send" on the handle.
```rust
enum ProfileUpdate {
SetUsername(String),
SetAge(i32)
}
let profile = use_coroutine(&cx, |mut rx: UnboundedReciver<ProfileUpdate>| async move {
let mut server = connect_to_server().await;
while let Ok(msg) = rx.next().await {
match msg {
ProfileUpdate::SetUsername(name) => server.update_username(name).await,
ProfileUpdate::SetAge(age) => server.update_age(age).await,
}
}
});
cx.render(rsx!{
button {
onclick: move |_| profile.send(ProfileUpdate::SetUsername("Bob".to_string())),
"Update username"
}
})
```
For sufficiently complex apps, we could build a bunch of different useful "services" that loop on channels to update the app.
```rust
let profile = use_coroutine(&cx, profile_service);
let editor = use_coroutine(&cx, editor_service);
let sync = use_coroutine(&cx, sync_service);
async fn profile_service(rx: UnboundedReceiver<ProfileCommand>) {
// do stuff
}
async fn sync_service(rx: UnboundedReceiver<SyncCommand>) {
// do stuff
}
async fn editor_service(rx: UnboundedReceiver<EditorCommand>) {
// do stuff
}
```
We can combine coroutines with Fermi to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state *within* a task and then simply update the "view" values stored in Atoms. It cannot be understated how powerful this technique is: we get all the perks of native Rust tasks with the optimizations and ergonomics of global state. This means your *actual* state does not need to be tied up in a system like Fermi or Redux - the only Atoms that need to exist are those that are used to drive the display/UI.
```rust
static USERNAME: Atom<String> = |_| "default".to_string();
fn app(cx: Scope) -> Element {
let atoms = use_atom_root(&cx);
use_coroutine(&cx, |rx| sync_service(rx, atoms.clone()));
cx.render(rsx!{
Banner {}
})
}
fn Banner(cx: Scope) -> Element {
let username = use_read(&cx, USERNAME);
cx.render(rsx!{
h1 { "Welcome back, {username}" }
})
}
```
Now, in our sync service, we can structure our state however we want. We only need to update the view values when ready.
```rust
enum SyncAction {
SetUsername(String),
}
async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
let username = atoms.write(USERNAME);
let errors = atoms.write(ERRORS);
while let Ok(msg) = rx.next().await {
match msg {
SyncAction::SetUsername(name) => {
if set_name_on_server(&name).await.is_ok() {
username.set(name);
} else {
errors.make_mut().push("SetUsernameFailed");
}
}
}
}
}
```
## Yielding Values
To yield values from a coroutine, simply bring in a `UseState` handle and set the value whenever your coroutine completes its work.
```rust
let sync_status = use_state(&cx, || Status::Launching);
let sync_task = use_coroutine(&cx, |rx: UnboundedReceiver<SyncAction>| {
to_owned![sync_status];
async move {
loop {
delay_ms(1000).await;
sync_status.set(Status::Working);
}
}
})
```
## Automatic injection into the Context API
Coroutine handles are automatically injected through the context API. `use_coroutine_handle` with the message type as a generic can be used to fetch a handle.
```rust
fn Child(cx: Scope) -> Element {
let sync_task = use_coroutine_handle::<SyncAction>(&cx);
sync_task.send(SyncAction::SetUsername);
}
```
-2
View File
@@ -1,4 +1,2 @@
# Fetching
This section is currently under construction! 🏗
+89 -7
View File
@@ -4,19 +4,101 @@ Not all apps you'll build can be self-contained with synchronous code. You'll of
So far, we've only talked about building apps with synchronous code, so this chapter will focus integrating asynchronous code into your app.
## The Runtime
By default, Dioxus-Desktop ships with the `Tokio` runtime and automatically sets everything up for you.
By default, Dioxus-Desktop ships with the `Tokio` runtime and automatically sets everything up for you. This is currently not configurable, though it would be easy to write an integration for Dioxus desktop that uses a different asynchronous runtime.
## Send/Sync
Writing apps that deal with Send/Sync can be frustrating at times. Under the hood, Dioxus is not currently thread-safe, so any async code you write does *not* need to be `Send/Sync`. That means Cell/Rc/RefCell are all fair game.
Writing apps that deal with Send/Sync can be frustrating at times. Under the hood, Dioxus is not currently thread-safe, so any async code you write does *not* need to be `Send/Sync`. That means that you can use non-thread-safe structures like `Cell`, `Rc`, and `RefCell`.
All async code in your app is polled on a `LocalSet`, so you can also use `tokio::spawn_local`.
## Spawning a future
Currently, all futures in Dioxus must be `'static`. To spawn a future, simply call `cx.spawn()`.
```rust
rsx!{
button {
onclick: move |_| cx.spawn(async move {
let result = fetch_thing().await;
})
}
}
```
Calling `spawn` will give you a `JoinHandle` which lets you cancel or pause the future.
## Setting state from within a future
All async code in your app is polled on a `LocalSet`, so any async code we w
If you start to write some async code, you'll quickly be greeted with the infamous error about borrowed items in static closures.
```
error[E0759]: `cx` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
--> examples/tasks.rs:13:27
|
12 | fn app(cx: Scope) -> Element {
| ----- this data with an anonymous lifetime `'_`...
13 | let count = use_state(&cx, || 0);
| ^^^ ...is used here...
14 |
15 | use_future(&cx, (), move |_| {
| ---------- ...and is required to live as long as `'static` here
|
note: `'static` lifetime requirement introduced by this bound
--> /Users/jonkelley/Development/dioxus/packages/hooks/src/usefuture.rs:25:29
|
25 | F: Future<Output = T> + 'static,
| ^^^^^^^
For more information about this error, try `rustc --explain E0759`.
error: could not compile `dioxus` due to previous error
```
Rustc tends to provide great feedback in its errors, but this particular error is actually quite unhelpful. For reference, here's our code:
```rust
fn app(cx: Scope) -> Element {
let count = use_state(&cx, || 0);
cx.spawn(async move {
tokio::time::sleep(Duration::from_millis(1000)).await;
count += 1;
});
cx.render(rsx! {
button {
onclick: move |_| count.set(0),
"Reset the count"
}
})
}
```
Simple, right? We spawn a future that updates the value after one second has passed. Well, yes, and no. Our `count` value is only valid for the lifetime of this component, but our future could still be running even after the component re-renders. By default, Dioxus places a requirement on all futures to be `'static`, meaning they can't just borrow state from our hooks outright.
To get around this, we need to get a `'static` handle to our state. All Dioxus hooks implement `Clone`, so you simply need to call clone before spawning your future. Any time you get the particular error above, make sure you call `Clone` or `ToOwned`.
```rust
cx.spawn({
let mut count = count.clone();
async move {
tokio::time::sleep(Duration::from_millis(1000)).await;
count += 1;
}
});
```
To make this a little bit easier, Dioxus exports the `to_owned!` macro which will create a binding as shown above, which can be quite helpful when dealing with many values.
```rust
cx.spawn({
to_owned![count, age, name, description, etc];
async move {
tokio::time::sleep(Duration::from_millis(1000)).await;
count += 1;
}
});
```
> This section is currently under construction! 🏗
+8
View File
@@ -0,0 +1,8 @@
# Updating State
After your future has been spawned, you might want to update the state of our UI. Possible implementations might include:
- A loading state
- A transition state
- Showing failures
- Listening to sockets and updating the app
+1
View File
@@ -0,0 +1 @@
# WebSockets
+85
View File
@@ -0,0 +1,85 @@
# UseFuture
When dealing with asynchronous code, you might need to wait for some action to complete before rendering your component. If you had to build this abstraction yourself, you'd probably end up with some `use_state` spaghetti code.
One of the core hooks that Dioxus provides is `use_future` - a simple hook that lets you tap into a running task.
## Use case
The simplest use case of `use_future` is to prevent rendering until some asynchronous code has been completed. Dioxus doesn't currently have a library as sophisticated as React Query for prefetching tasks, but we can get some of the way there with `use_future`. In one of the Dioxus examples, we use `use_future` to download some search data before rendering the rest of the app:
```rust
fn app(cx: Scope) -> Element {
// set "breeds" to the current value of the future
let breeds = use_future(&cx, (), |_| async move {
reqwest::get("https://dog.ceo/api/breeds/list/all")
.await
.unwrap()
.json::<ListBreeds>()
.await
});
let status = match breeds.value() {
Some(Ok(val)) => "ready!",
Some(Err(err)) => "errored!",
None => "loading!",
}
}
```
On first run, the code inside `use_future` will be submitted to the Dioxus scheduler once the component has rendered. Since there's no data ready when the component loads the first time, its "value" will be `None`.
However, once the future is finished, the component will be re-rendered and a new screen will be displayed - Ok or Err, depending on the outcome of our fetch.
## Restarting the Future
The example we showed above will only ever run once. What happens if some value changed on the server and we need to update our future's value?
Well, the UseFuture handle provides a handy "restart" method. We can wire this up to a button or some other comparison code to get a regenerating future.
```rust
fn app(cx: Scope) -> Element {
// set "breeds" to the current value of the future
let dog = use_future(&cx, (), |_| async move {
reqwest::get("https://dog.ceo/api/breeds/image/random")
.await
.unwrap()
.json::<RandomDog>()
.await
});
cx.render(match breeds.value() {
Some(Ok(val)) => rsx!(div {
img { src: "{val.message}"}
button {
onclick: move |_| dog.restart(),
"Click to fetch a new dog"
}
}),
Some(Err(err)) => rsx!("Failed to load dog"),
None => rsx!("Loading dog image!"),
})
}
```
## With Dependencies
We showed that UseFuture can be regenerated manually, but how can we automatically get it to update whenever some input value changes? This is where the "dependencies" tuple comes into play. We just need to add a value into our tuple argument and it'll be automatically cloned into our future when it starts.
```rust
#[inline_props]
fn RandomDog(cx: Scope, breed: String) -> Element {
let dog = use_future(&cx, (breed,), |(breed)| async move {
reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
.await
.unwrap()
.json::<RandomDog>()
.await
});
// some code as before
}
```
@@ -1,6 +1,6 @@
# Passing children and attributes
Often times, you'll want to wrap some important functionality *around* your state, not directly nested *inside* another component. In these cases, you'll want to pass elements and attributes into a component and let the component place them appropriately.
Oftentimes, you'll want to wrap some important functionality *around* your state, not directly nested *inside* another component. In these cases, you'll want to pass elements and attributes into a component and let the component place them appropriately.
In this chapter, you'll learn about:
- Passing elements into components
@@ -30,7 +30,7 @@ struct ClickableProps<'a> {
title: &'a str
}
fn Clickable(cx: Scope<ClickableProps>) -> Element {
fn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
cx.render(rsx!(
a {
href: "{cx.props.href}"
@@ -64,7 +64,7 @@ struct ClickableProps<'a> {
body: Element<'a>
}
fn Clickable(cx: Scope<ClickableProps>) -> Element {
fn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
cx.render(rsx!(
a {
href: "{cx.props.href}",
@@ -98,7 +98,7 @@ struct ClickableProps<'a> {
children: Element<'a>
}
fn Clickable(cx: Scope<ClickableProps>) -> Element {
fn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
cx.render(rsx!(
a {
href: "{cx.props.href}",
@@ -125,7 +125,7 @@ While technically allowed, it's an antipattern to pass children more than once i
However, because the `Element` is transparently a `VNode`, we can actually match on it to extract the nodes themselves, in case we are expecting a specific format:
```rust
fn clickable(cx: Scope<ClickableProps>) -> Element {
fn clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
match cx.props.children {
Some(VNode::Text(text)) => {
// ...
@@ -137,6 +137,8 @@ fn clickable(cx: Scope<ClickableProps>) -> Element {
}
```
Passing nodes through props means that they are immutable. If you find yourself needing to mutate nodes passed through props, consider creating a new node in its place that takes on its attributes, children, and listeners.
<!-- ## Passing attributes
In the cases where you need to pass arbitrary element properties into a component - say to add more functionality to the `<a>` tag, Dioxus will accept any quoted fields. This is similar to adding arbitrary fields to regular elements using quotes.
@@ -160,7 +162,7 @@ struct ClickableProps<'a> {
attributes: Attributes<'a>
}
fn clickable(cx: Scope<ClickableProps>) -> Element {
fn clickable(cx: Scope<ClickableProps<'a>>) -> Element {
cx.render(rsx!(
a {
..cx.props.attributes,
@@ -184,7 +186,7 @@ struct ClickableProps<'a> {
onclick: EventHandler<'a, MouseEvent>
}
fn clickable(cx: Scope<ClickableProps>) -> Element {
fn clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
cx.render(rsx!(
a {
onclick: move |evt| cx.props.onclick.call(evt)
@@ -214,4 +216,3 @@ In this chapter, we learned:
- How the `attributes` field works on component properties
- How to convert `listeners` into `EventHandlers` for components
- How to extend any node with custom attributes and children
+13 -13
View File
@@ -1,6 +1,6 @@
# Thinking in React
# Thinking in Reactively
We've finally reached the point in our tutorial where we can talk about the theory of Reactive interfaces. We've talked about defining a declarative view, but not about the aspects that make our code *reactive*.
We've finally reached the point in our tutorial where we can talk about the theory of Reactivity. We've talked about defining a declarative view, but not about the aspects that make our code *reactive*.
Understanding the theory of reactive programming is essential to making sense of Dioxus and writing effective, performant UIs.
@@ -15,7 +15,7 @@ This section is a bit long, but worth the read. We recommend coffee, tea, and/or
## Reactive Programming
Dioxus is one the very few Rust libraries that provide a "Reactive Programming Model". The term "Reactive programming" is a classification of programming paradigm - much like functional or imperative programming. This is a very important distinction since it affects how we *think* about our code.
Dioxus is one of a handful of Rust libraries that provide a "Reactive Programming Model". The term "Reactive programming" is a classification of programming paradigm - much like functional or imperative programming. This is a very important distinction since it affects how we *think* about our code.
Reactive programming is a programming model concerned with deriving computations from asynchronous data flow. Most reactive programs are comprised of datasources, intermediate computations, and a final result.
@@ -51,7 +51,7 @@ fn compute_graph(constant: i32, seconds: i32) -> bool {
## How is Dioxus Reactive?
The Dioxus VirtualDom provides us a framework for reactive programming. When we build apps with dioxus, we need to provide our own datasources. This can be either initial props or some values fetched from the network. We then pass this data through our app into components through properties.
The Dioxus VirtualDom provides us a framework for reactive programming. When we build apps with dioxus, we need to provide our own datasources. This can be either initial props or some values fetched from the network. We then pass this data through our app into components through properties.
If we represented the reactive graph presented above in Dioxus, it would look very similar:
@@ -61,8 +61,8 @@ fn RenderGraph(cx: Scope) -> Element {
let seconds = use_datasource(SECONDS);
let constant = use_state(&cx, || 1);
cx.render(rsx!(
RenderG { seconds: seconds }
cx.render(rsx!(
RenderG { seconds: seconds }
RenderT { seconds: seconds, constant: constant }
))
}
@@ -108,8 +108,8 @@ For local state in hooks, Dioxus gives us the `use_hook` method which returns an
fn app(cx: Scope) -> Element {
let mut count = cx.use_hook(|_| 0);
cx.render(rsx!{
button {
onclick: move |_| *count += 1,
button {
onclick: move |_| *count += 1,
"Count: {count}"
}
})
@@ -119,11 +119,11 @@ fn app(cx: Scope) -> Element {
However, when this value is written to, the component does not know to be reevaluated. We must explicitly tell Dioxus that this component is "dirty" and needs to be re-rendered. This is done through the `cx.needs_update` method:
```rust
button {
button {
onclick: move |_| {
*count += 1;
cx.needs_update();
},
},
"Count: {count}"
}
```
@@ -144,7 +144,7 @@ With the `provide_context` and `consume_context` methods on `Scope`, we can shar
To make app-global state easier to reason about, Dioxus makes all values provided through `provide_context` immutable. This means any library built on top of `provide_context` needs to use interior mutability to modify shared global state.
In these cases, App-Global state needs to manually track which components need to be re-generated.
In these cases, App-Global state needs to manually track which components need to be re-generated.
To regenerate *any* component in your app, you can get a handle to the Dioxus' internal scheduler through `schedule_update_any`:
@@ -165,7 +165,7 @@ The tree of UI that dioxus creates will roughly look like the tree of components
![Tree of UI](../images/component_tree.png)
But what happens when we call `needs_update` after modifying some important state? Well, if Dioxus called our component's function again, then we would produce new, different nodes. In fact, this is exactly what Dioxus does!
But what happens when we call `needs_update` after modifying some important state? Well, if Dioxus called our component's function again, then we would produce new, different nodes. In fact, this is exactly what Dioxus does!
At this point, we have some old nodes and some new nodes. Again, we call this "rendering" because Dioxus had to create new nodes because of our explicit actions. Any time new nodes get created, our VirtualDom is being "rendered."
@@ -216,7 +216,7 @@ However, for components that borrow data, it doesn't make sense to implement Par
You can technically override this behavior by implementing the `Props` trait manually, though it's unsafe and easy to mess up:
```rust
impl Properties for CustomProps {
unsafe impl Properties for CustomProps {
fn memoize(&self, other &Self) -> bool {
self != other
}
+43 -11
View File
@@ -1,12 +1,14 @@
# Component Properties
Dioxus components are functions that accept Props as input and output an Element. In fact, the `App` function you saw in the previous chapter was a component with no Props! Most components, however, will need to take some Props to render something useful – so, in this section, we'll learn about props:
- Deriving the Props trait
- Memoization through PartialEq
- Optional fields on props
- The inline_props macro
- The inline_props macro
## Props
The input of your Component must be passed in a single struct, which must implement the `Props` trait. We can derive this trait automatically with `#[derive(Props)]`.
> Dioxus `Props` is very similar to [@idanarye](https://github.com/idanarye)'s [TypedBuilder crate](https://github.com/idanarye/rust-typed-builder) and supports many of the same parameters.
@@ -56,10 +58,10 @@ And we can see that the Component indeed gets rendered:
![Screenshot of running app. Text: "+ \ 42 \ -"](component_example_votes.png)
> The simplest Owned Props you can have is `()` - or no value at all. This is what the `App` Component takes as props. `Scope` accepts a generic for the Props which defaults to `()`.
>
>
> ```rust
>// this scope
>Scope<()>
>Scope<()>
>
>// is the same as this scope
>Scope
@@ -131,23 +133,29 @@ Borrowed Props cannot be safely memoized. However, this is not a problem – Dio
## Optional Props
You can easily create optional fields by attaching the `optional` modifier to a field:
You can easily create optional fields by using the `Option<…>` type for a field:
```rust
#[derive(Props, PartialEq)]
struct MyProps {
name: String,
#[props(optional)]
description: Option<String>
}
fn Demo(cx: MyProps) -> Element {
todo!()
let text = match cx.props.description {
Some(d) => d, // if a value is provided
None => "No description" // if the prop is omitted
};
cx.render(rsx! {
"{name}": "{text}"
})
}
```
Then, we can completely omit the description field when calling the component:
In this example `name` is a required prop and `description` is optional.
This means we can completely omit the description field when calling the component:
```rust
rsx!{
@@ -157,15 +165,39 @@ rsx!{
}
}
```
Additionally if we provide a value we don't have to wrap it with `Some(…)`. This is done automatically for us:
The `optional` modifier is a combination of two separate modifiers: `default` and `strip_option`. The full list of modifiers includes:
```rust
rsx!{
Demo {
name: "Thing".to_string(),
description: "This is explains it".to_string(),
}
}
```
If you want to make a prop required even though it is of type `Option` you can provide the `!optional` modifier:
```rust
#[derive(Props, PartialEq)]
struct MyProps {
name: String,
#[props(!optional)]
description: Option<String>
}
```
This can be especially useful if you have a type alias named `Option` in the current scope.
For more information on how tags work, check out the [TypedBuilder](https://github.com/idanarye/rust-typed-builder) crate. However, all attributes for props in Dioxus are flattened (no need for `setter` syntax) and the `optional` field is new. The `optional` modifier is a combination of two separate modifiers: `default` and `strip_option` and it is automatically detected on `Option<…>` types.
The full list of Dioxus' modifiers includes:
- `default` - automatically add the field using its `Default` implementation
- `strip_option` - automatically wrap values at the call site in `Some`
- `optional` - alias for `default` and `strip_option`
- `into` - automatically call `into` on the value at the callsite
For more information on how tags work, check out the [TypedBuilder](https://github.com/idanarye/rust-typed-builder) crate. However, all attributes for props in Dioxus are flattened (no need for `setter` syntax) and the `optional` field is new.
## The `inline_props` macro
@@ -1,6 +1,6 @@
# Conditional Rendering
Your components will often need to display different things depending on different conditions. With Dioxus, we can use Rust's normal control flow to conditional hide, show, and modify the structure of our markup.
Your components will often need to display different things depending on different conditions. With Dioxus, we can use Rust's normal control flow to conditionally hide, show, and modify the structure of our markup.
In this chapter, you'll learn:
- How to return different Elements depending on a condition
@@ -7,7 +7,6 @@ In this section, we'll cover special attributes built into Dioxus:
- `dangerous_inner_html`
- Boolean attributes
- `prevent_default`
<!-- - `..Attributes` -->
- event handlers as string attributes
- `value`, `checked`, and `selected`
@@ -31,7 +30,7 @@ fn BlogPost(cx: Scope) -> Element {
```
> Note! This attribute is called "dangerous_inner_html" because it is **dangerous** to pass it data you don't trust. If you're not careful, you can easily expose cross-site-scripting (XSS) attacks to your users.
>
>
> If you're handling untrusted input, make sure to sanitize your HTML before passing it into `dangerous_inner_html` – or just pass it to a Text Element to escape any HTML tags.
@@ -49,9 +48,9 @@ rsx!{
}
}
```
wouldn't actually render the `hidden` attribute:
wouldn't actually render the `hidden` attribute:
```html
<div>hello</div>
<div>hello</div>
```
Not all attributes work like this however. *Only the following attributes* have this behavior:
@@ -87,9 +86,9 @@ For any other attributes, a value of `"false"` will be sent directly to the DOM.
## Stopping form input and navigation with `prevent_default`
Currently, calling `prevent_default` on events in EventHandlers is not possible from Desktop/Mobile. Until this is supported, it's possible to prevent default using the `prevent_default` attribute.
Currently, calling `prevent_default` on events in EventHandlers is not possible from Desktop/Mobile. Until this is supported, it's possible to prevent default using the `prevent_default` attribute.
> Note: you cannot conditionally prevent default with this approach. This is a limitation until synchronous event handling is available across the Webview boundary
> Note: you cannot conditionally prevent default with this approach. This is a limitation until synchronous event handling is available across the Webview boundary
To use `prevent_default`, simply attach the `prevent_default` attribute to a given element and set it to the name of the event handler you want to prevent default on. We can attach this attribute multiple times for multiple attributes.
@@ -104,7 +103,7 @@ rsx!{
}
}
```
<!--
<!--
## Passing attributes into children: `..Attributes`
> Note: this is an experimental, unstable feature not available in released versions of Dioxus. Feel free to skip this section.
@@ -188,5 +187,5 @@ In this chapter, we learned:
<!-- todo
There's more to elements! For further reading, check out:
- [Custom Elements]()
- [Custom Elements]()
-->
+4 -2
View File
@@ -8,6 +8,7 @@ In this chapter, we'll cover:
- Element properties
## Declaring our first Element
Because Dioxus is mostly used with HTML/CSS renderers, the default Element "collection" is HTML. Provided the `html` feature is not disabled, we can declare Elements using the `rsx!` macro:
```rust
@@ -29,6 +30,7 @@ Produces:
```
We can construct any valid HTML tag with the `tag {}` pattern and expect the resulting HTML structure to resemble our declaration.
## Composing Elements
Of course, we need more complex structures to make our apps actually useful! Just like HTML, the `rsx!` macro lets us nest Elements inside of each other.
@@ -81,7 +83,7 @@ let name = "Bob";
rsx! ( "hello {name}" )
```
Unfortunately, you cannot drop in arbitrary expressions directly into the string literal. In the cases where we need to compute a complex value, we'll want to use `format_args!` directly. Due to specifics of the `rsx!` macro (which we'll cover later), our call to `format_args` must be contained within square braces.
Unfortunately, you cannot yet drop in arbitrary expressions directly into the string literal with Rust. In the cases where we need to compute a complex value, we'll want to use `format_args!` directly. Due to specifics of the `rsx!` macro (which we'll cover later), our call to `format_args` must be contained within square braces.
```rust
rsx!( [format_args!("Hello {}", if enabled { "Jack" } else { "Bob" } )] )
@@ -150,7 +152,7 @@ All element attributes must occur *before* child elements. The `rsx!` macro will
Listeners are a special type of Attribute that only accept functions. Listeners let us attach functionality to our Elements by running a provided closure whenever the specified Listener is triggered.
We'll cover listeners in more depth in the Listeners chapter, but for now, just know that every listener must start with the `on` keyword and accepts closures.
We'll cover listeners in more depth in the Adding Interactivity chapter, but for now, just know that every listener starts with "on" and accepts closures.
```rust
rsx!(
+14 -9
View File
@@ -63,7 +63,7 @@ $ cat Cargo.toml
[package]
name = "hello-dioxus"
version = "0.1.0"
edition = "2018"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -81,6 +81,13 @@ $ cargo add dioxus --features desktop
It's very important to add `dioxus` with the `desktop` feature for this example. The `dioxus` crate is a batteries-include crate that combines a bunch of utility crates together, ensuring compatibility of the most important parts of the ecosystem. Under the hood, the `dioxus` crate configures various renderers, hooks, debug tooling, and more. The `desktop` feature ensures the we only depend on the smallest set of required crates to compile and render.
If you system does not provide the `libappindicator3` library, like Debian/bullseye, you can enable the replacement `ayatana` with an additional flag:
```shell
$ # On Debian/bullseye use:
$ cargo add dioxus --features desktop --features ayatana
```
If you plan to develop extensions for the `Dioxus` ecosystem, please use the `dioxus` crate with the `core` feature to limit the amount of dependencies your project brings in.
### Our first app
@@ -92,10 +99,10 @@ use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(App);
dioxus::desktop::launch(app);
}
fn App(cx: Scope) -> Element {
fn app(cx: Scope) -> Element {
cx.render(rsx! (
div { "Hello, world!" }
))
@@ -108,24 +115,24 @@ At this point, you could call `cargo run` and be greeted with a simple `Hello, W
### Dissecting our example
The `use` statement at the top of our app imports everything from the the `prelude` module. `use`-ing the prelude imports the right traits, types, and macros needed for working with Dioxus.
The `use` statement at the top of our app imports everything from the `prelude` module. `use`-ing the prelude imports the right traits, types, and macros needed for working with Dioxus.
```rust
use dioxus::prelude::*;
```
This initialization code launches a Tokio runtime on a helper thread where your code will run. Then, the WebView renderer will be launched on the main-thread. Due to platform requirements, the main thread is blocked by your app's event loop.
This initialization code launches a Tokio runtime on a helper thread where your code will run. Then, the WebView renderer will be launched on the main thread. Due to platform requirements, the main thread is blocked by your app's event loop.
```rust
fn main() {
dioxus::desktop::launch(App);
dioxus::desktop::launch(app);
}
```
Finally, our app. Every component in Dioxus is a function that takes in `Context` and `Props` and returns an `Element`.
```rust
fn App(cx: Scope) -> Element {
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div { "Hello, world!" }
})
@@ -143,5 +150,3 @@ For now, just know that `Scope` lets you store state with hooks and render eleme
## Moving on
Congrats! You've built your first desktop application with Dioxus. Next, we're going to learn about the basics of building interactive user interfaces.
+47
View File
@@ -0,0 +1,47 @@
# Helper Crates
Because the Dioxus ecosystem is fairly young, there aren't a ton of third-party libraries designed to "get things done." That's where the Dioxus helper crates come in. These are officially supported crates built on top of existing libraries to solve some of the common barriers to building apps.
## Router
Quickly add a cross-platform router to structure your app.
[Link](https://github.com/DioxusLabs/dioxus/tree/master/packages/router)
## Fermi
Global state as easy as `use_state`.
[Link](https://github.com/DioxusLabs/dioxus/tree/master/packages/fermi)
## Query
This crate has yet to be developed! However, we do plan on providing a crate for good fetching and query support.
## 3rd-party - Toast
Toast notifications, curtesy of [@mrxiaozhuox](https://github.com/mrxiaozhuox).
[Link](https://github.com/mrxiaozhuox/dioxus-toast)
## 3rd-party - HeroIcon
Collection of helpful hero icons, curtesy of [@autarch](https://github.com/autarch).
[Link](https://github.com/houseabsolute/dioxus-heroicons)
## 3rd-party - Katex
Draw beautiful equations, curtesy of [@oovm](https://github.com/oovm)
[Link](https://github.com/oovm/katex-wasm/tree/dev/projects/dioxus-katex)
## 3rd-party - PrimsJS
Highlight your code blocks with ease, curtesy of [@oovm](https://github.com/oovm)
[Link](https://github.com/oovm/katex-wasm/tree/dev/projects/dioxus-katex)
+116 -1
View File
@@ -1,3 +1,118 @@
# Event handlers
> This section is currently under construction! 🏗
To make our boring UIs less static and more interesting, we want to add the ability to interact with user input. To do this, we need to add some event handlers.
## The most basic events: clicks
If you've poked around in the Dioxus examples at all, you've definitely noticed the support for buttons and clicks. To add some basic action when a button is clicked, we need to define a button and then attach an "onclick" handler to it.
```rust
fn app(cx: Scope) -> Element {
cx.render(rsx!{
button {
onclick: move |evt| println!("I've been clicked!"),
"click me!"
}
})
}
```
If you're using the builder pattern, it's pretty simple too. `onclick` is a method for any builder with HTML elements.
```rust
fn app(cx: Scope) -> Element {
button(&cx)
.onclick(move |evt| println!("I've been clicked!"))
.text("click me!")
.build()
}
```
The event handler is different in Dioxus than other libraries. Event handlers in Dioxus may borrow any data that has a lifetime that matches the component's scope. This means we can save a value with `use_hook` and then use it in our handler.
```rust
fn app(cx: Scope) -> Element {
let val = cx.use_hook(|_| 10);
button(&cx)
.onclick(move |evt| println!("Current number {val}"))
.text("click me!")
.build()
}
```
## The `Event` object
When the listener is fired, Dioxus will pass in any related data through the `event` parameter. This holds helpful state relevant to the event. For instance, on forms, Dioxus will fill in the "values" field.
```rust
// the FormEvent is roughly
struct FormEvent {
value: String,
values: HashMap<String, String>
}
fn app(cx: Scope) -> Element {
cx.render(rsx!{
form {
onsubmit: move |evt| {
println!("Values of form are {evt.values:#?}");
}
input { id: "password", name: "password" }
input { id: "username", name: "username" }
}
})
}
```
## Stopping propagation
With a complex enough UI, you might realize that listeners can actually be nested.
```rust
div {
onclick: move |evt| {},
"outer",
div {
onclick: move |evt| {},
"inner"
}
}
```
In this particular layout, a click on the inner div is transitively also a click on the outer div. If we didn't want the outer div to be triggered every time we trigger the inner div, then we'd want to call `cancel_bubble()`.
This will prevent any listeners above the current listener from being triggered.
```rust
div {
onclick: move |evt| {},
"outer",
div {
onclick: move |evt| {
// now, outer won't be triggered
evt.cancel_bubble();
},
"inner"
}
}
```
## Prevent Default
With HTML-based renderers, the browser will automatically perform some action. For text inputs, this would be entering the provided key. For forms, this might involve navigating the page.
In some instances, you don't want this default behavior. In these cases, instead of handling the event directly, you'd want to prevent any default handlers.
Normally, in React or JavaScript, you'd call "preventDefault" on the event in the callback. Dioxus does *not* currently support this behavior. Instead, you need to add an attribute to the element generating the event.
```rust
form {
prevent_default: "onclick",
onclick: move |_|{
// handle the event without navigating the page.
}
}
```
+1 -2
View File
@@ -139,8 +139,7 @@ fn Child(cx: Scope, name: String) -> Element {
}
// ✅ Or, use a hashmap with use_ref
```rust
let ages = use_ref(&cx, |_| HashMap::new());
let ages = use_ref(&cx, || HashMap::new());
names.iter().map(|name| {
let age = ages.get(name).unwrap();
@@ -8,120 +8,3 @@ Most components you will write in Dioxus will need to store state somehow. For l
Both of these hooks are extremely powerful and flexible, so we've dedicated this section to understanding them properly.
> These two hooks are not the only way to store state. You can always build your own hooks!
## `use_state`
The `use_state` hook is very similar to its React counterpart. When we use it, we get two values:
- The value itself as an `&T`
- A handle to set the value `&UseState<T>`
```rust
let (count, set_count) = use_state(&cx, || 0);
// then to set the count
set_count(count + 1)
```
However, the `set_count` object is more powerful than it looks. You can use it as a closure, but you can also call methods on it to do more powerful operations.
For instance, we can get a handle to the current value at any time:
```rust
let current: Rc<T> = set_count.current();
```
Or, we can get the inner handle to set the value
```rust
let setter: Rc<dyn Fn(T)> = set_count.setter();
```
Or, we can set a new value using the current value as a reference:
```rust
set_count.modify(|i| i + 1);
```
If the value is cheaply cloneable, then we can call `with_mut` to get a mutable reference to the value:
```rust
set_count.with_mut(|i| *i += 1);
```
Alternatively, we can call `make_mut` which gives us a `RefMut` to the underlying value:
```rust
*set_count.make_mut() += 1;
```
Plus, the `set_count` handle is cloneable into async contexts, so we can use it in futures.
```rust
// count up infinitely
cx.spawn({
to_owned![set_count];
async move {
loop {
wait_ms(100).await;
set_count.modify(|i| i + 1);
}
}
})
```
## `use_ref`
You might've noticed a fundamental limitation to `use_state`: to modify the value in-place, it must be cheaply cloneable. But what if your type is not cheap to clone?
In these cases, you should reach for `use_ref` which is essentially just a glorified `Rc<RefCell<T>>` (Rust [smart pointers](https://doc.rust-lang.org/book/ch15-04-rc.html)).
This provides us some runtime locks around our data, trading reliability for performance. For most cases though, you will find it hard to make `use_ref` crash.
> Note: this is *not* exactly like its React counterpart since calling `write` will cause a re-render. For more React parity, use the `write_silent` method.
To use the hook:
```rust
let names = use_ref(&cx, || vec!["susie", "calvin"]);
```
To read the hook values, use the `read()` method:
```rust
cx.render(rsx!{
ul {
names.read().iter().map(|name| {
rsx!{ "{name}" }
})
}
})
```
And then to write into the hook value, use the `write` method:
```rust
names.write().push("Tiger");
```
If you don't want to re-render the component when names is updated, then we can use the `write_silent` method:
```rust
names.write_silent().push("Transmogrifier");
```
Again, like `UseState`, the `UseRef` handle is clonable into async contexts:
```rust
// infinitely push calvin into the list
cx.spawn({
to_owned![names];
async move {
loop {
wait_ms(100).await;
names.write().push("Calvin");
}
}
})
```
+8 -8
View File
@@ -108,7 +108,7 @@ There are a few different approaches to choosing when to update your state. You
When responding to user-triggered events, we'll want to "listen" for an event on some element in our component.
For example, let's say we provide a button to generate a new post. Whenever the user clicks the button, they get a new post. To achieve this functionality, we'll want to attach a function to the `on_click` method of `button`. Whenever the button is clicked, our function will run, and we'll get new Post data to work with.
For example, let's say we provide a button to generate a new post. Whenever the user clicks the button, they get a new post. To achieve this functionality, we'll want to attach a function to the `onclick` method of `button`. Whenever the button is clicked, our function will run, and we'll get new Post data to work with.
```rust
fn App(cx: Scope)-> Element {
@@ -116,7 +116,7 @@ fn App(cx: Scope)-> Element {
cx.render(rsx!{
button {
on_click: move |_| set_post(PostData::random())
onclick: move |_| set_post(PostData::random())
"Generate a random post"
}
Post { props: &post }
@@ -128,10 +128,10 @@ We'll dive much deeper into event listeners later.
### Updating state asynchronously
We can also update our state outside of event listeners with `futures` and `coroutines`.
We can also update our state outside of event listeners with `futures` and `coroutines`.
- `Futures` are Rust's version of promises that can execute asynchronous work by an efficient polling system. We can submit new futures to Dioxus either through `push_future` which returns a `TaskId` or with `spawn`.
- `Coroutines` are asynchronous blocks of our component that have the ability to cleanly interact with values, hooks, and other data in the component.
- `Coroutines` are asynchronous blocks of our component that have the ability to cleanly interact with values, hooks, and other data in the component.
Since coroutines and Futures stick around between renders, the data in them must be valid for the `'static` lifetime. We must explicitly declare which values our task will rely on to avoid the `stale props` problem common in React.
@@ -157,7 +157,7 @@ fn App(cx: Scope)-> Element {
}
```
Using asynchronous code can be difficult! This is just scratching the surface of what's possible. We have an entire chapter on using async properly in your Dioxus Apps. We have an entire section dedicated to using `async` properly later in this book.
Using asynchronous code can be difficult! This is just scratching the surface of what's possible. We have an entire chapter on using async properly in your Dioxus Apps.
### How do I tell Dioxus that my state changed?
@@ -173,9 +173,9 @@ With these building blocks, we can craft new hooks similar to `use_state` that l
In general, Dioxus should be plenty fast for most use cases. However, there are some rules you should consider following to ensure your apps are quick.
- 1) **Don't call set_state _while rendering_**. This will cause Dioxus to unnecessarily re-check the component for updates or enter an infinite loop.
- 2) **Break your state apart into smaller sections.** Hooks are explicitly designed to "unshackle" your state from the typical model-view-controller paradigm, making it easy to reuse useful bits of code with a single function.
- 3) **Move local state down**. Dioxus will need to re-check child components of your app if the root component is constantly being updated. You'll get best results if rapidly-changing state does not cause major re-renders.
1) **Don't call set_state _while rendering_**. This will cause Dioxus to unnecessarily re-check the component for updates or enter an infinite loop.
2) **Break your state apart into smaller sections.** Hooks are explicitly designed to "unshackle" your state from the typical model-view-controller paradigm, making it easy to reuse useful bits of code with a single function.
3) **Move local state down**. Dioxus will need to re-check child components of your app if the root component is constantly being updated. You'll get best results if rapidly-changing state does not cause major re-renders.
<!-- todo: link when the section exists
Don't worry - Dioxus is fast. But, if your app needs *extreme performance*, then take a look at the `Performance Tuning` in the `Advanced Guides` book.
+10 -2
View File
@@ -1,3 +1,11 @@
# Lifecycle, updates, and effects
# Effects
In theory, your UI code should be side-effect free. Whenever a component renders, all of its state should be prepared ahead of time. In reality, we often need to perform some sort of side effect. Possible effects include:
- Logging some data
- Pre-fetching some data
- Attaching code to native elements
- Cleaning up
This section is organized under interactivity because effects can be important to add things like transitions, videos, and other important media.
> This section is currently under construction! 🏗
+78 -2
View File
@@ -1,6 +1,82 @@
# User Input and Controlled Components
Handling user input is one of the most common things your app will do, but it can be tricky
Handling user input is one of the most common things your app will do, but it *can* be tricky.
The reactive paradigm and one-way-data-flow models were all derived to solve problems that can crop up around user input handling. This section will teach you about two effective patterns for handling user input: controlled and uncontrolled inputs.
## Controlled Inputs
The most common approach to handling input from elements is through "controlled" inputs. With this pattern, we drive the value of the input from our state, while simultaneously updating our state from new values.
This is the most common form of input handling and is widely used because it prevents the UI from being out of sync with your local state.
```rust
let name = use_state(&cx, || "bob".to_string());
cx.render(rsx!{
input {
value: "{name}",
oninput: move |evt| name.set(evt.value.clone()),
}
})
```
Why not just "bind" like in other frameworks?
In a handful of cases, you don't want the inputted value to match what's rendered to the screen. Let's say we want to implement an input that converts the input to uppercase when the input matches a certain value. With binding, we're forced to share the same input and state value. By explicitly handling the oninput case, we're given the opportunity to set a *new* value.
> This section is currently under construction! 🏗
```rust
let name = use_state(&cx, || "bob".to_string());
cx.render(rsx!{
input {
value: "{name}",
oninput: move |evt| {
if evt.value == "tim" {
name.set("TIM");
}
},
}
})
```
## Binding
>! Note - binding is currently not implemented in Dioxus. This section represents a future in-progress feature.
Because the above pattern is so common, we have an additional attribute called "bind" which is essentially a shorthand for our code above.
Bind just connects an oninput to a `UseState` and is implemented through the signal system.
```rust
let name = use_state(&cx, || "bob".to_string());
cx.render(rsx!{
input { bind: name }
})
```
## Uncontrolled Inputs
When working with large sets of inputs, you might be quickly tired of creating `use_state` for each value. Additionally, the pattern of one `use_state` per interaction might deteriorate when you need to have a flexible number of inputs. In these cases, we use "uncontrolled" inputs. Here, we don't drive the value of the input from the `use_state`, choosing to leave it in an "uncontrolled" state.
This approach can be more performant and more flexible, but more prone to UI inconsistencies than its controlled counterpart.
To use the "uncontrolled" pattern, we simply omit setting the value of the input. Instead, we can react to the change directly on the input itself, or from a form element higher up in the tree.
For this example, we don't attach any `use_state` handles into the labels. Instead, we simply attach an `oninput` handler to the form element. This will run each time any of the child inputs change, allowing us to perform tasks like form validation.
```rust
form {
oninput: move |evt| {
if !validate_input(evt.values) {
// display what errors validation resulted in
}
},
input { name: "name", }
input { name: "age", }
input { name: "date", }
}
```
+155
View File
@@ -0,0 +1,155 @@
# UseRef
You might've noticed a fundamental limitation to `use_state`: to modify the value in-place, it must be cheaply cloneable. But what if your type is not cheap to clone?
In these cases, you should reach for `use_ref` which is essentially just a glorified `Rc<RefCell<T>>` (Rust [smart pointers](https://doc.rust-lang.org/book/ch15-04-rc.html)).
This provides us some runtime locks around our data, trading reliability for performance. For most cases though, you will find it hard to make `use_ref` crash.
> Note: this is *not* exactly like its React counterpart since calling `write` will cause a re-render. For more React parity, use the `write_silent` method.
To use the hook:
```rust
let names = use_ref(&cx, || vec!["susie", "calvin"]);
```
To read the hook values, use the `read()` method:
```rust
cx.render(rsx!{
ul {
names.read().iter().map(|name| {
rsx!{ "{name}" }
})
}
})
```
And then to write into the hook value, use the `write` method:
```rust
names.write().push("Tiger");
```
If you don't want to re-render the component when names is updated, then we can use the `write_silent` method:
```rust
names.write_silent().push("Transmogrifier");
```
Again, like `UseState`, the `UseRef` handle is clonable into async contexts:
```rust
// infinitely push calvin into the list
cx.spawn({
to_owned![names];
async move {
loop {
wait_ms(100).await;
names.write().push("Calvin");
}
}
})
```
## Using UseRef for complex state
The best use case of `use_ref` is to manage "complex" data local to your component. This should be things like large structs, collections, queues, state machines, and other data where cloning would be expensive.
```rust
let val = use_ref(&cx, || vec![1, 3, 3, 7]);
let val = use_ref(&cx, || (0..10000).collect::<Vec<_>x>());
let val = use_ref(&cx, || Configuration {
a: "asdasd",
// .. more complex fields
});
```
UseRef can be sent down into child components too.
UseRef memoizes with itself, performing a cheap pointer comparison. If the UseRef handle is the same, then the component can be memoized.
```rust
fn app(cx: Scope) -> Element {
let val = use_ref(&cx, || 0);
cx.render(rsx!{
Child { val: val.clone() }
})
}
#[inline_props]
fn Child(cx: Scope, val: UseRef<i32>) -> Element {
// ...
}
```
## Using UseRef with "models"
One option for state management that UseRef enables is the development of a "model" for your components. This particular pattern enables you to model your state with regular structs.
For instance, our calculator example uses a struct to model the state.
```rust
struct Calculator {
display_value: String,
operator: Option<Operator>,
waiting_for_operand: bool,
cur_val: f64,
}
```
Our component is really simple - we just call `use_ref` to get an initial calculator state.
```rust
fn app(cx: Scope) -> Element {
let state = use_ref(&cx, Calculator::new);
cx.render(rsx!{
// the rendering code
})
}
```
In our UI, we can then use `read` and a helper method to get data out of the model.
```rust
// Our accessor method
impl Calculator {
fn formatted_display(&self) -> String {
self.display_value
.parse::<f64>()
.unwrap()
.separated_string()
}
}
// And then in the UI
cx.render(rsx!{
div { [state.read().formatted_display()] }
})
```
To modify the state, we can setup a helper method and then attach it to a callback.
```rust
// our helper
impl Calculator {
fn clear_display(&mut self) {
self.display_value = "0".to_string()
}
}
// our callback
cx.render(rsx!{
button {
onclick: move |_| state.write().clear_display(),
}
})
```
+114
View File
@@ -0,0 +1,114 @@
# Fundamental hook: `use_state`
The first fundamental hook for state management is `use_state`. This particular hook is designed to work well with the entire Dioxus ecosystem including futures, children, and memoization.
## Basic usage
The simplest use case of `use_state` is a simple counter. The handle returned by `use_state` implements `Add` and `AddAssign`. Writing through `AddAssign` will automatically mark the component as dirty, forcing an update.
```rust
fn app(cx: Scope) -> Element {
let count = use_state(&cx, || 0);
rsx!(cx, button { onclick: move |_| count += 1, })
}
```
## Reference
### Common ops
The `use_state` hook is very similar to its React counterpart. When we use it, we get a smart pointer - essentially an `Rc<T>` that tracks when we update it.
```rust
let mut count = use_state(&cx, || 0);
// then to set the count
count += 1;
```
### Current value
Our `count` value is more than just an integer. Because it's a smart pointer, it also implements other useful methods helpful in various contexts.
For instance, we can get a handle to the current value at any time:
```rust
let current: Rc<T> = count.current();
```
Or, we can get the inner handle to set the value:
```rust
let setter: Rc<dyn Fn(T)> = count.setter();
```
### Modify
Or, we can set a new value using the current value as a reference:
```rust
count.modify(|i| i + 1);
```
### `with_mut` and `make_mut`
If the value is cheaply cloneable, then we can call `with_mut` to get a mutable reference to the value:
```rust
count.with_mut(|i| *i += 1);
```
Alternatively, we can call `make_mut` which gives us a `RefMut` to the underlying value:
```rust
*count.make_mut() += 1;
```
### Use in Async
Plus, the `set_count` handle is cloneable into async contexts, so we can use it in futures.
```rust
// count up infinitely
cx.spawn({
let count = count.clone();
async move {
loop {
wait_ms(100).await;
count += 1;
}
}
})
```
## Using UseState for simple data
The best use case of `use_state` is to manage "simple" data local to your component. This should be things like numbers, strings, small maps, and cheaply-clonable types.
```rust
let val = use_state(&cx, || 0);
let val = use_state(&cx, || "Hello!");
let val = use_state(&cx, || vec![1, 3, 3, 7]);
```
UseState can be sent down into child components too.
You can either pass by reference to always force the child to update when the value changes, or you can clone the handle to take advantage of automatic memoization. In these cases, calling "get" will return stale data - always prefer "current" when "cloning" the UseState. This automatic memoization of UseState solves a performance problem common in React.
```rust
fn app(cx: Scope) -> Element {
let val = use_state(&cx, || 0);
cx.render(rsx!{
Child { val: val.clone() }
})
}
fn Child(cx: Scope, val: UseState<i32>) -> Element {
// ...
}
```
+7 -1
View File
@@ -8,7 +8,7 @@ We'll learn about:
- Suggested cargo extensions
For platform-specific guides, check out the [Platform Specific Guides](../platforms/00-index.md).
For platform-specific guides, check out the [Platform Specific Guides](/reference/platforms/index.md).
# Setting up Dioxus
@@ -52,6 +52,12 @@ Webview Linux apps require WebkitGtk. When distributing, this can be part of you
sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev
```
When using Debian/bullseye `libappindicator3-dev` is no longer available but replaced by `libayatana-appindicator3-dev`.
```
# on Debian/bullseye use:
sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatana-appindicator3-dev
```
If you run into issues, make sure you have all the basics installed, as outlined in the [Tauri docs](https://tauri.studio/en/docs/get-started/setup-linux).
+1
View File
@@ -0,0 +1 @@
# Channels
+148 -2
View File
@@ -1,12 +1,158 @@
# Error handling
A selling point of Rust for web development is the reliability of always knowing where errors can occur and being forced to handle them
However, we haven't talked about error handling at all in this guide! In this chapter, we'll cover some strategies in handling errors to ensure your app never crashes.
## The simplest - returning None
Astute observers might have noticed that `Element` is actually a type alias for `Option<VNode>`. You don't need to know what a `VNode` is, but it's important to recognize that we could actually return nothing at all:
```rust
fn App((cx, props): Component) -> Element {
fn App(cx: Scope) -> Element {
None
}
```
> This section is currently under construction! 🏗
This lets us add in some syntactic sugar for operations we think *shouldn't* fail, but we're still not confident enough to "unwrap" on.
> The nature of `Option<VNode>` might change in the future as the `try` trait gets upgraded.
```rust
fn App(cx: Scope) -> Element {
// immediately return "None"
let name = cx.use_hook(|_| Some("hi"))?;
}
```
## Early return on result
Because Rust can't accept both Options and Results with the existing try infrastructure, you'll need to manually handle Results. This can be done by converting them into Options or by explicitly handling them.
```rust
fn App(cx: Scope) -> Element {
// Convert Result to Option
let name = cx.use_hook(|_| "1.234").parse().ok()?;
// Early return
let count = cx.use_hook(|_| "1.234");
let val = match count.parse() {
Ok(val) => val
Err(err) => return cx.render(rsx!{ "Parsing failed" })
};
}
```
Notice that while hooks in Dioxus do not like being called in conditionals or loops, they *are* okay with early returns. Returning an error state early is a completely valid way of handling errors.
## Match results
The next "best" way of handling errors in Dioxus is to match on the error locally. This is the most robust way of handling errors, though it doesn't scale to architectures beyond a single component.
To do this, we simply have an error state built into our component:
```rust
let err = use_state(&cx, || None);
```
Whenever we perform an action that generates an error, we'll set that error state. We can then match on the error in a number of ways (early return, return Element, etc).
```rust
fn Commandline(cx: Scope) -> Element {
let error = use_state(&cx, || None);
cx.render(match *error {
Some(error) => rsx!(
h1 { "An error occured" }
)
None => rsx!(
input {
oninput: move |_| error.set(Some("bad thing happened!")),
}
)
})
}
```
## Passing error states through components
If you're dealing with a handful of components with minimal nesting, you can just pass the error handle into child components.
```rust
fn Commandline(cx: Scope) -> Element {
let error = use_state(&cx, || None);
if let Some(error) = **error {
return cx.render(rsx!{ "An error occured" });
}
cx.render(rsx!{
Child { error: error.clone() }
Child { error: error.clone() }
Child { error: error.clone() }
Child { error: error.clone() }
})
}
```
Much like before, our child components can manually set the error during their own actions. The advantage to this pattern is that we can easily isolate error states to a few components at a time, making our app more predictable and robust.
## Going global
A strategy for handling cascaded errors in larger apps is through signaling an error using global state. This particular pattern involves creating an "error" context, and then setting it wherever relevant. This particular method is not as "sophisticated" as React's error boundary, but it is more fitting for Rust.
To get started, consider using a built-in hook like `use_context` and `use_context_provider` or Fermi. Of course, it's pretty easy to roll your own hook too.
At the "top" of our architecture, we're going to want to explicitly declare a value that could be an error.
```rust
enum InputError {
None,
TooLong,
TooShort,
}
static INPUT_ERROR: Atom<InputError> = |_| InputError::None;
```
Then, in our top level component, we want to explicitly handle the possible error state for this part of the tree.
```rust
fn TopLevel(cx: Scope) -> Element {
let error = use_read(&cx, INPUT_ERROR);
match error {
TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
TooShort => return cx.render(rsx!{ "FAILED: Too Short!" }),
_ => {}
}
}
```
Now, whenever a downstream component has an error in its actions, it can simply just set its own error state:
```rust
fn Commandline(cx: Scope) -> Element {
let set_error = use_set(&cx, INPUT_ERROR);
cx.render(rsx!{
input {
oninput: move |evt| {
if evt.value.len() > 20 {
set_error(InputError::TooLong);
}
}
}
})
}
```
This approach to error handling is best in apps that have "well defined" error states. Consider using a crate like `thiserror` or `anyhow` to simplify the generation of the error types.
This pattern is widely popular in many contexts and is particularly helpful whenever your code generates a non-recoverable error. You can gracefully capture these "global" error states without panicking or mucking up state.
+61
View File
@@ -0,0 +1,61 @@
# Fanning Out
One of the most reliable state management patterns in large Dioxus apps is `fan-out`. The fan-out pattern is the ideal way to structure your app to maximize code reuse, testability, and deterministic rendering.
## The structure
With `fan-out`, our individual components at "leaf" position of our VirtualDom are "pure", making them easily reusable, testable, and deterministic. For instance, the "title" bar of our app might be a fairly complicated component.
```rust
#[derive(Props, PartialEq)]
struct TitlebarProps {
title: String,
subtitle: String,
}
fn Titlebar(cx: Scope<TitlebarProps>) -> Element {
cx.render(rsx!{
div {
class: "titlebar"
h1 { "{cx.props.title}" }
h1 { "{cx.props.subtitle}" }
}
})
}
```
If we used global state like use_context or fermi, we might be tempted to inject our `use_read` directly into the component.
```rust
fn Titlebar(cx: Scope<TitlebarProps>) -> Element {
let title = use_read(&cx, TITLE);
let subtitle = use_read(&cx, SUBTITLE);
cx.render(rsx!{/* ui */})
}
```
For many apps - this is a fine pattern, especially if the component is a one-off. However, if we want to reuse the component outside of this app, then we'll start to run into issues where our contexts are unavailable.
## Fanning Out
To enable our titlebar component to be used across apps, we want to lift our atoms upwards and out of the Titlebar component. We would organize a bunch of other components in this section of the app to share some of the same state.
```rust
fn DocsiteTitlesection(cx: Scope) {
let title = use_read(&cx, TITLE);
let subtitle = use_read(&cx, SUBTITLE);
let username = use_read(&cx, USERNAME);
let points = use_read(&cx, POINTS);
cx.render(rsx!{
TitleBar { title: title, subtitle: subtitle }
UserBar { username: username, points: points }
})
}
```
This particular wrapper component unfortunately cannot be reused across apps. However, because it simply wraps other real elements, it doesn't have to. We are free to reuse our TitleBar and UserBar components across apps with ease. We also know that this particular component is plenty performant because the wrapper doesn't have any props and is always memoized. The only times this component re-renders is when any of the atoms change.
This is the beauty of Dioxus - we always know where our components are likely to be re-rendered. Our wrapper components can easily prevent any large re-renders by simply memoizing their components. This system might not be as elegant or precise as signal systems found in libraries like Sycamore or SolidJS, but it is quite ergonomic.
+110
View File
@@ -0,0 +1,110 @@
# Fermi
After having covered local and global state, you're definitely ready to start building some complex Dioxus apps. Before you get too far, check out the Fermi crate. Fermi makes it dead-easy to manage global state and scales to the largest of apps.
## What is Fermi for?
If you're building an app that has scaled past a few small components, then it'll be worth sketching out and organizing your app's state. Fermi makes it easy to transition from a simple app that relies on `use_state` to an app with dozens of components.
> To put it simply - Fermi is the ideal crate for managing global state in your Dioxus app.
## How do I use it?
To add fermi to your project, simply add the "fermi" feature to your Dioxus dependency.
```toml
[dependencies]
dioxus = { version = "0.2", features = ["desktop", "fermi"] }
```
Fermi is built on the concept of "atoms" of global state. Instead of bundling all our state together in a single mega struct, we actually chose to implement it piece-by-piece with functions.
Each piece of global state in your app is represented by an "atom."
```rust
static TITLE: Atom<String> = |_| "Defualt Title".to_string();
```
This atom can be then used the with the `use_atom` hook as a drop-in replacement for `use_state`.
```rust
static TITLE: Atom<String> = |_| "Defualt Title".to_string();
fn Title(cx: Scope) -> Element {
let title = use_atom(&cx, TITLE);
cx.render(rsx!{
button { onclick: move |_| title.set("new title".to_string()) }
})
}
```
However, Fermi really becomes useful when we want to share the value between two different components.
```rust
static TITLE: Atom<String> = |_| "Defualt Title".to_string();
fn TitleBar(cx: Scope) -> Element {
let title = use_atom(&cx, TITLE);
rsx!{cx, button { onclick: move |_| title.set("titlebar title".to_string()) } }
}
fn TitleCard(cx: Scope) -> Element {
let title = use_atom(&cx, TITLE);
rsx!{cx, button { onclick: move |_| title.set("title card".to_string()) } }
}
```
These two components can get and set the same value!
## Use with collections
Fermi gets *really* powerful when used to manage collections of data. Under the hood, Fermi uses immutable collections and tracks reads and writes of individual keys. This makes it easy to implement things like lists and infinite posts with little performance penalty. It also makes it really easy to refactor our app and add new fields.
```rust
static NAMES: AtomRef<Uuid, String> = |builder| {};
static CHECKED: AtomRef<Uuid, bool> = |builder| {};
static CREATED: AtomRef<Uuid, Instant> = |builder| {};
```
To use these collections:
```rust
#[inline_props]
fn Todo(cx: Scope, id: Uuid) -> Element {
let name = use_atom(&cx, NAMES.select(id));
let checked = use_atom(&cx, CHECKED.select(id));
let created = use_atom(&cx, CREATED.select(id));
// or
let (name, checked, created) = use_atom(&cx, (NAMES, CHECKED, CREATED).select(id));
}
```
This particular pattern might seem strange at first - "why isn't all of our state under one struct?" - but eventually shows its worth when dealing with large amounts of data. By composing collections together, we can get get the perks of the Entity-Component-System architecture in our Dioxus apps. Performance is quite predictable here and easy to trace.
## AtomRef
Much like `use_ref` can be used to manage complex state locally, `AtomRef` can be used to manage complex global state. `AtomRef` is basically a global `Rc<RefCell<T>>` with mutation tracking.
It too serves as a basic replacement for `use_ref`:
```rust
fn Todo(cx: Scope) -> Element {
let cfg = use_atom_ref(&cx, CFG);
cfg.write().enable_option();
}
```
## Future Reading
This guide is meant just to be an overview of Fermi. This page is just a short advertisement for what we consider the best state management solution available today for Dioxus apps.
For further reading, check out the [crate itself](https://github.com/DioxusLabs/dioxus/tree/master/packages/fermi)!
+29 -19
View File
@@ -1,28 +1,38 @@
# Managing State
Every app you'll build with Dioxus will have some sort of state that needs to be maintained and updated as your users interact with it. However, managing state can be particular challenging at times, and is frequently the source of bugs in many GUI frameworks.
Every app you'll build with Dioxus will have some sort of state that needs to be maintained and updated as your users interact with it. However, managing state can be particularly challenging at times, and is frequently the source of bugs in many GUI frameworks.
In this chapter, we'll cover the various ways to manage state, the appropriate terminology, various patterns, and then take an overview
## Terminology
In this chapter, we'll cover the various ways to manage state, the appropriate terminology, various patterns, and some problems you might run into.
> This section is currently under construction! 🏗
## The Problem
Why do people say state management is so difficult? What does it mean?
Generally, state management is the code you need to write to ensure that your app renders the *correct* content. If the user inputs a name, then you need to display the appropriate response - like alerts, validation, and disable/enable various elements on the page. Things can quickly become tricky if you need loading screens and cancellable tasks.
For the simplest of apps, all of your state can enter the app from the root props. This is common in server-side rendering - we can collect all of the required state *before* rendering the content.
```rust
let all_content = get_all_content().await;
let output = dioxus::ssr::render_lazy(rsx!{
div {
RenderContent { content: all_content }
}
});
```
With this incredibly simple setup, it is highly unlikely that you'll have rendering bugs. There simply is barely any state to manage.
However, most of your apps will store state inside of the Dioxus VirtualDom - either through local state or global state.
## Your options
To deal with complexity, you have a couple of options:
## Important hook: `use_state`
## Important hook: `use_ref`
## `provide_context` and `consume_context`
## Terminology
- Refactor state out of shared state and into reusable components and hooks.
- Lift state upwards to be spread across multiple components (fan out).
- Use the Context API to share state globally.
- Use a dedicated state management solution like Fermi.
+128 -2
View File
@@ -1,4 +1,130 @@
# Lifting State
# Lifting State and Fanning Out
Maintaining state local to components doesn't always work.
One of the most reliable state management patterns in large Dioxus apps is to `lift-up` and `fan-out`. Lifting up and fanning-out state is the ideal way to structure your app to maximize code reuse, testability, and deterministic rendering.
> This section is currently under construction! 🏗
## Lifting State
When building complex apps with Dioxus, the best approach is to start by placing your state and an UI all within a single component. Once your component has passed a few hundred lines, then it might be worth refactoring it into a few smaller components.
Here, we're now challenged with how to share state between these various components.
Let's say we refactored our component to separate an input and a display.
```rust
fn app(cx: Scope) -> Element {
cx.render(rsx!{
Title {}
Input {}
})
}
```
Whenever a value is inputted in our `Input` component, we need to somehow propagate those changes into the `Title` component.
A quick-and-dirty solution - which works for many apps - is to simply share a UseState between the two components.
```rust
fn app(cx: Scope) -> Element {
let text = use_state(&cx, || "default".to_string());
cx.render(rsx!{
Title { text: text.clone() }
Input { text: text.clone() }
})
}
```
> Note: since we `Cloned` our `text` `UseState` handle, both `Title` and `Input` will be memoized.
Here, we've "lifted" state out of our `Input` component and brought it up to the closest shared ancestor. In our input component, we can directly use this UseState handle as if it had been defined locally:
```rust
#[inline_props]
fn Input(cx: Scope, text: UseState<String>) -> Element {
cx.render(rsx!{
input { oninput: move |evt| text.set(evt.value.clone()) }
})
}
```
Similarly, our `Title` component would be straightforward:
```rust
#[inline_props]
fn Title(cx: Scope, text: UseState<String>) -> Element {
cx.render(rsx!{
h1 { "{text}" }
})
}
```
For more complicated use cases, we can take advantage of the EventHandler coercion talked about before to pass in any callback. Recall that fields on components that start with "on" are automatically "upgraded" into an `EventHandler` at the call site.
This lets us abstract over the exact type of state being used to store the data.
For the `Input` component, we would simply add a new `oninput` field:
```rust
#[inline_props]
fn Input<'a>(cx: Scope<'a>, oninput: EventHandler<'a, String>) -> Element {
cx.render(rsx!{
input { oninput: move |evt| oninput.call(evt.value.clone()), }
})
}
```
For our `Title` component, we could also abstract it to take any `&str`:
```rust
#[inline_props]
fn Title<'a>(cx: Scope<'a>, text: &'a str) -> Element {
cx.render(rsx!{
h1 { "{text}" }
})
}
```
## Fanning Out
As your app grows and grows, you might need to start pulling in global state to avoid prop drilling. This tends to solve a lot of problems, but generates even more.
For instance, let's say we built a beautiful `Title` component. Instead of passing props in, we instead are using a `use_read` hook from Fermi.
```rust
fn Title(cx: Scope) -> Element {
let title = use_read(&cx, TITLE);
cx.render(rsx!{
h1 { "{title}" }
})
}
```
This is great - all is well in the world. We get precise updates, automatic memoization, and a solid abstraction. But, what happens when we want to reuse this component in another project? This component is now deeply intertwined with our global state - which might not be the same in another app.
In this case, we want to "lift" our global state out of "view" components. With `lifting`, our individual components at "leaf" position of our VirtualDom are "pure", making them easily reusable, testable, and deterministic.
To enable our title component to be used across apps, we want to lift our atoms upwards and out of the Title component. We would organize a bunch of other components in this section of the app to share some of the same state.
```rust
fn DocsiteTitlesection(cx: Scope) {
// Use our global state in a wrapper component
let title = use_read(&cx, TITLE);
let subtitle = use_read(&cx, SUBTITLE);
let username = use_read(&cx, USERNAME);
let points = use_read(&cx, POINTS);
// and then pass our global state in from the outside
cx.render(rsx!{
Title { title: title.clone(), subtitle: subtitle.clone() }
User { username: username.clone(), points: points.clone() }
})
}
```
This particular wrapper component unfortunately cannot be reused across apps. However, because it simply wraps other real elements, it doesn't have to. We are free to reuse our TitleBar and UserBar components across apps with ease. We also know that this particular component is plenty performant because the wrapper doesn't have any props and is always memoized. The only times this component re-renders is when any of the atoms change.
This is the beauty of Dioxus - we always know where our components are likely to be re-rendered. Our wrapper components can easily prevent any large re-renders by simply memoizing their components. This system might not be as elegant or precise as signal systems found in libraries like Sycamore or SolidJS, but it is quite ergonomic.
+107 -1
View File
@@ -1,4 +1,110 @@
# Local State
The first step to dealing with complexity in your app is to refactor your state to be purely local. This encourages good code reuse and prevents leakage of abstractions across component boundaries.
## What it looks like
Let's say we're managing the state for a list of Todos. Whenever we edit the todo, we want the list to update. We might've started building our app but putting everything into a single `use_ref`.
```rust
struct Todo {
contents: String,
is_hovered: bool,
is_editing: bool,
}
let todos = use_ref(&cx, || vec![Todo::new()]);
cx.render(rsx!{
ul {
todos.read().iter().enumerate().map(|(id, todo)| rsx!{
li {
onhover: move |_| *todos.write()[id].is_hovered = true,
h1 { "{todo.contents}" }
}
})
}
})
```
As shown above, whenever the todo is hovered, we want to set its state:
```rust
todos.write()[0].is_hovered = true;
```
As the amount of interactions goes up, so does the complexity of our state. Should the "hover" state really be baked into our data model?
Instead, let's refactor our Todo component to handle its own state:
```rust
#[inline_props]
fn Todo<'a>(cx: Scope, todo: &'a Todo) -> Element {
let is_hovered = use_state(&cx, || false);
cx.render(rsx!{
li {
h1 { "{todo.contents}" }
onhover: move |_| is_hovered.set(true),
}
})
}
```
Now, we can simplify our Todo data model to get rid of local UI state:
```rust
struct Todo {
contents: String,
}
```
This is not only better for encapsulation and abstraction, but it's only more performant! Whenever a Todo is hovered, we won't need to re-render *every* Todo again - only the Todo that's currently being hovered.
Wherever possible, you should try to refactor the "view" layer *out* of your data model.
## Immutability
Storing all of your state inside a `use_ref` might be tempting. However, you'll quickly run into an issue where the `Ref` provided by `read()` "does not live long enough." An indeed - you can't return locally-borrowed value into the Dioxus tree.
In these scenarios consider breaking your `use_ref` into individual `use_state`s.
You might've started modeling your component with a struct and use_ref
```rust
struct State {
count: i32,
color: &'static str,
names: HashMap<String, String>
}
// in the component
let state = use_ref(&cx, State::new)
```
The "better" approach for this particular component would be to break the state apart into different values:
```rust
let count = use_state(&cx, || 0);
let color = use_state(&cx, || "red");
let names = use_state(&cx, HashMap::new);
```
You might recognize that our "names" value is a HashMap - which is not terribly cheap to clone every time we update its value. To solve this issue, we *highly* suggest using a library like [`im`](https://crates.io/crates/im) which will take advantage of structural sharing to make clones and mutations much cheaper.
When combined with the `make_mut` method on `use_state`, you can get really succinct updates to collections:
```rust
let todos = use_state(&cx, im_rc::HashMap::default);
todos.make_mut().insert("new todo", Todo {
contents: "go get groceries",
});
```
## Moving on
This particular local patterns are powerful but is not a cure-all for state management problems. Sometimes your state problems are much larger than just staying local to a component.
> This section is currently under construction! 🏗
+86
View File
@@ -0,0 +1,86 @@
# Router
In many of your apps, you'll want to have different "scenes". For a webpage, these scenes might be the different webpages with their own content.
You could write your own scene management solution - quite simply too. However, to save you the effort, Dioxus supports a first-party solution for scene management called Dioxus Router.
## What is it?
For an app like the Dioxus landing page (https://dioxuslabs.com), we want to have different pages. A quick sketch of an app would be something like:
- Homepage
- Blog
- Example showcase
Each of these scenes is independent - we don't want to render both the homepage and blog at the same time.
This is where the router crates come in handy. To make sure we're using the router, simply add the `"router"` feature to your dioxus dependency.
```toml
[dependencies]
dioxus = { version = "0.2", features = ["desktop", "router"] }
```
## Using the router
Unlike other routers in the Rust ecosystem, our router is built declaratively. This makes it possible to compose our app layout simply by arranging components.
```rust
rsx!{
Router {
Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} }
}
}
```
Whenever we visit this app, we will get either the Home component or the Blog component rendered depending on which route we enter at. If neither of these routes match the current location, then nothing will render.
We can fix this one of two ways:
- A fallback 404 page
```rust
rsx!{
Router {
Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} }
Route { to: "", NotFound {} }
}
}
```
- Redirect 404 to home
```rust
rsx!{
Router {
Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} }
Redirect { from: "", to: "/home" }
}
}
```
## Links
For our app to navigate these routes, we can provide clickable elements called Links. These simply wrap `<a>` elements that, when clicked, navigate the app to the given location.
```rust
rsx!{
Link {
to: "/home",
"Go home!"
}
}
```
## More reading
This page is just meant to be a very brief overview of the router to show you that there's a powerful solution already built for a very common problem. For more information about the router, definitely check out its book or check out some of the examples.
The router has its own documentation! [Available here](https://dioxuslabs.com/router/guide/).
+61 -3
View File
@@ -1,7 +1,65 @@
# Global State
If your app has finally gotten large enough where passing values through the tree ends up polluting the intent of your code, then it might be time to turn to global state.
cx.provide_context()
cx.consume_context()
In Dioxus, global state is shared through the Context API. This guide will show you how to use the Context API to simplify state management.
> This section is currently under construction! 🏗
## Provide Context and Consume Context
The simplest way of retrieving shared state in your app is through the Context API. The Context API allows you to provide and consume an item of state between two components.
Whenever a component provides a context, it is then accessible to any child components.
> Note: parent components cannot "reach down" and consume state from below their position in the tree.
The terminology here is important: `provide` context means that the component will expose state and `consume` context means that the child component can acquire a handle to state above it.
Instead of using keys or statics, Dioxus prefers the `NewType` pattern to search for parent state. This means each state you expose as a context should be its own unique type.
In practice, you'll have a component that exposes some state:
```rust
#[derive(Clone)]
struct Title(String);
fn app(cx: Scope) -> Element {
cx.use_hook(|_| {
cx.provide_context(Title("Hello".to_string()));
});
cx.render(rsx!{
Child {}
})
}
```
And then in our component, we can consume this state at any time:
```rust
fn Child(cx: Scope) -> Element {
let name = cx.consume_context::<Title>();
//
}
```
Note: calling "consume" state can be a rather expensive operation to perform during each render. Prefer to consume state within a `use_hook`:
```rust
fn Child(cx: Scope) -> Element {
// cache our "consume_context" operation
let name = cx.use_hook(|_| cx.consume_context::<Title>());
}
```
All `Context` must be cloned - the item will be cloned into each call of `consume_context`. To make this operation cheaper, consider wrapping your type in an `Rc` or `Arc`.
<!-- ## Coroutines
The `use_coroutine` hook -->
<!-- # `use_context` and `use_context_provider`
These -->
+367
View File
@@ -0,0 +1,367 @@
# Dioxus v0.2 Release: TUI, Router, Fermi, and Tooling
> March 9, 2022
Thanks to these amazing folks for their financial support on OpenCollective:
- [@t1m0t](https://github.com/t1m0t)
- [@alexkirsz](https://github.com/t1m0t)
- [@freopen](https://github.com/freopen)
- [@DannyMichaels](https://github.com/DannyMichaels)
- [@SweetLittleMUV](https://github.com/Fatcat560)
Thanks to these amazing folks for their code contributions:
- [@mrxiaozhuox](https://github.com/mrxiaozhuox)
- [@autarch](https://github.com/autarch)
- [@FruitieX](https://github.com/FruitieX)
- [@t1m0t](https://github.com/t1m0t)
- [@Demonthos](https://github.com/Demonthos)
- [@oovm](https://github.com/oovm)
- [@6asaaki](https://github.com/6asaaki)
Just over two months in, and we already a ton of awesome changes to Dioxus!
Dioxus is a recently-released library for building interactive user interfaces (GUI) with Rust. It is built around a Virtual DOM, making it portable for the web, desktop, server, mobile, and more. Dioxus looks and feels just like React, so if you know React, then you'll feel right at home.
```rust
fn app(cx: Scope) -> Element {
let mut count = use_state(&cx, || 0);
cx.render(rsx! {
h1 { "Count: {count}" }
button { onclick: move |_| count += 1, "+" }
button { onclick: move |_| count -= 1, "-" }
})
}
```
# What's new?
A *ton* of stuff happened in this release; 550+ commits, 23 contributors, 2 minor releases, and 6 backers on Open Collective.
Some of the major new features include:
- We now can render into the terminal, similar to Ink.JS - a huge thanks to [@Demonthos](https://github.com/Demonthos)
- We have a new router in the spirit of React-Router [@autarch](https://github.com/autarch)
- We now have Fermi for global state management in the spirit of [Recoil.JS](https://recoiljs.org)
- Our desktop platform got major upgrades, getting closer to parity with Electron [@mrxiaozhuox](https://github.com/mrxiaozhuox)
- Our CLI tools now support HTML-to-RSX translation for converting 3rd party HTML into Dioxus [@mrxiaozhuox](https://github.com/mrxiaozhuox)
- Dioxus-Web is sped up by 2.5x with JS-based DOM manipulation (3x faster than React)
We also fixed and improved a bunch of stuff - check out the full list down below.
## A New Renderer: Your terminal!
When Dioxus was initially released, we had very simple support for logging Dioxus elements out as TUI elements. In the past month or so, [@Demonthos](https://github.com/Demonthos) really stepped up and made the new crate a reality.
![Imgur](https://i.imgur.com/GL7uu3r.png)
The new TUI renderer even supports mouse movements, keyboard input, async tasks, borders, and a ton more.
<video controls autoplay muted>
<source src="https://i.imgur.com/q25tZST.mp4" type="video/mp4">
</vido>
## New Router
We totally revamped the router, switching away from the old yew-router approach to the more familiar [React-Router](http://reactrouter.com). It's less type-safe but provides more flexibility and support for beautiful URLs.
Apps with routers are *really* simple now. It's easy to compose the "Router", a "Route", and "Links" to define how your app is laid out:
```rust
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Router {
onchange: move |_| log::info!("Route changed!"),
ul {
Link { to: "/", li { "Go home!" } }
Link { to: "users", li { "List all users" } }
Link { to: "blog", li { "Blog posts" } }
}
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" }
}
})
}
```
We're also using hooks to parse the URL parameters and segments so you can interact with the router from anywhere deeply nested in your app.
```rust
#[derive(Deserialize)]
struct Query { name: String }
fn BlogPost(cx: Scope) -> Element {
let post = use_route(&cx).segment("post")?;
let query = use_route(&cx).query::<Query>()?;
cx.render(rsx!{
"Viewing post {post}"
"Name selected: {query}"
})
}
```
Give a big thanks to [@autarch](https://github.com/autarch) for putting in all the hard work to make this new router a reality.
The Router guide is [available here](https://dioxuslabs.com/nightly/router/) - thanks to [@dogedark](https://github.com/dogedark).
## Fermi for Global State Management
Managing state in your app can be challenging. Building global state management solutions can be even more challenging. For the first big attempt at building a global state management solution for Dioxus, we chose to keep it simple and follow in the footsteps of the [Recoil.JS](http://recoiljs.org) project.
Fermi uses the concept of "Atoms" for global state. These individual values can be get/set from anywhere in your app. Using state with Fermi is basically as simple as `use_state`.
```rust
// Create a single value in an "Atom"
static TITLE: Atom<&str> = |_| "Hello";
// Read the value from anywhere in the app, subscribing to any changes
fn app(cx: Scope) -> Element {
let title = use_read(&cx, TITLE);
cx.render(rsx!{
h1 { "{title}" }
Child {}
})
}
// Set the value from anywhere in the app
fn Child(cx: Scope) -> Element {
let set_title = use_set(&cx, TITLE);
cx.render(rsx!{
button {
onclick: move |_| set_title("goodbye"),
"Say goodbye"
}
})
}
```
## Inline Props Macro
For internal components, explicitly declaring props structs can become tedious. That's why we've built the new `inline_props` macro. This macro lets you inline your props definition right into your component function arguments.
Simply add the `inline_props` macro to your component:
```rust
#[inline_props]
fn Child<'a>(
cx: Scope,
name: String,
age: String,
onclick: EventHandler<'a, ClickEvent>
) -> Element {
cx.render(rsx!{
button {
"Hello, {name}"
"You are {age} years old"
onclick: move |evt| onclick.call(evt)
}
})
}
```
You won't be able to document each field or attach attributes so you should refrain from using it in libraries.
## Props optional fields
Sometimes you don't want to specify *every* value in a component's props, since there might a lot. That's why the `Props` macro now supports optional fields. You can use a combination of `default`, `strip_option`, and `optional` to tune the exact behavior of properties fields.
```rust
#[derive(Props, PartialEq)]
struct ChildProps {
#[props(default = "client")]
name: String,
#[props(default)]
age: Option<u32>,
#[props(optional)]
age: Option<u32>,
}
// then to use the accompanying component
rsx!{
Child {
name: "asd",
}
}
```
## Dioxus Web Speed Boost
We've changed how DOM patching works in Dioxus-Web; now, all of the DOM manipulation code is written in TypeScript and shared between our web, desktop, and mobile runtimes.
On an M1-max, the "create-rows" operation used to take 45ms. Now, it takes a mere 17ms - 3x faster than React. We expect an upcoming optimization to bring this number as low as 3ms.
Under the hood, we have a new string interning engine to cache commonly used tags and values on the Rust <-> JS boundary, resulting in significant performance improvements.
Overall, Dioxus apps are even more snappy than before.
Before and after:
![Before and After](https://imgur.com/byTBGlO.png)
## Dioxus Desktop Window Context
A very welcome change, thanks AGAIN to [@mrxiaozhuox](https://github.com/mrxiaozhuox) is support for imperatively controlling the desktop window from your Dioxus code.
A bunch of new methods were added:
- Minimize and maximize window
- Close window
- Focus window
- Enable devtools on the fly
And more!
In addition, Dioxus Desktop now autoresolves asset locations, so you can easily add local images, JS, CSS, and then bundle it into an .app without hassle.
You can now build entirely borderless desktop apps:
![img](https://i.imgur.com/97zsVS1.png)
## CLI Tool
Thanks to the amazing work by [@mrxiaozhuox](https://github.com/mrxiaozhuox), our CLI tool is fixed and working better than ever. The Dioxus-CLI sports a new development server, an HTML to RSX translation engine, a `cargo fmt`-style command, a configuration scheme, and much more.
Unlike its counterpart, `Trunk.rs`, the dioxus-cli supports running examples and tests, making it easier to test web-based projects and showcase web-focused libraries.
## Async Improvements
Working with async isn't the easiest part of Rust. To help improve things, we've upgraded async support across the board in Dioxus.
First, we upgraded the `use_future` hook. It now supports dependencies, which let you regenerate a future on the fly as its computed values change. It's never been easier to add datafetching to your Rust Web Apps:
```rust
fn RenderDog(cx: Scope, breed: String) -> Element {
let dog_request = use_future(&cx, (breed,), |(breed,)| async move {
reqwest::get(format!("https://dog.ceo/api/breed/{}/images/random", breed))
.await
.unwrap()
.json::<DogApi>()
.await
});
cx.render(match dog_request.value() {
Some(Ok(url)) => rsx!{ img { url: "{url}" } },
Some(Err(url)) => rsx!{ span { "Loading dog failed" } },
None => rsx!{ "Loading dog..." }
})
}
```
Additionally, we added better support for coroutines. You can now start, stop, resume, and message with asynchronous tasks. The coroutine is automatically exposed to the rest of your app via the Context API. For the vast majority of apps, Coroutines can satisfy all of your state management needs:
```rust
fn App(cx: Scope) -> Element {
let sync_task = use_coroutine(&cx, |rx| async move {
connect_to_server().await;
let state = MyState::new();
while let Some(action) = rx.next().await {
reduce_state_with_action(action).await;
}
});
cx.render(rsx!{
button {
onclick: move |_| sync_task.send(SyncAction::Username("Bob")),
"Click to sync your username to the server"
}
})
}
```
## All New Features
We've covered the major headlining features, but there were so many more!
- A new router @autarch
- Fermi for global state management
- Translation of docs and Readme into Chinese @mrxiaozhuox
- 2.5x speedup by using JS-based DOM manipulation (3x faster than React)
- Beautiful documentation overhaul
- InlineProps macro allows definition of props within a component's function arguments
- Improved dev server, hot reloading for desktop and web apps [@mrxiaozhuox](https://github.com/mrxiaozhuox)
- Templates: desktop, web, web/hydration, Axum + SSR, and more [@mrxiaozhuox](https://github.com/mrxiaozhuox)
- Web apps ship with console_error_panic_hook enabled, so you always get tracebacks
- Enhanced Hydration and server-side-rendering (recovery, validation)
- Optional fields for component properties
- Introduction of the `EventHandler` type
- Improved use_state hook to be closer to react
- Improved use_ref hook to be easier to use in async contexts
- New use_coroutine hook for carefully controlling long-running async tasks
- Prevent Default attribute
- Provide Default Context allows injection of global contexts to the top of the app
- push_future now has a spawn counterpart to be more consistent with rust
- Add gap and gap_row attributes [@FruitieX](https://github.com/FruitieX)
- File Drag n Drop support for Desktop
- Custom handler support for desktop
- Forms now collect all their values in oninput/onsubmit
- Async tasks now are dropped when components unmount
- Right-click menus are now disabled by default
## Fixes
- Windows support improved across the board
- Linux support improved across the board
- Bug in Calculator example
- Improved example running support
A ton more! Dioxus is now much more stable than it was at release!
## Breaking Changes and Migrations
- UseState is not `Copy` - but now supports Clone for use in async. `for_async` has been removed
- UseFuture and UseEffect now take dependency tuples
- UseCoroutine is now exposed via the context API and takes a receiver
- Async tasks are canceled when components are dropped
- The ContextAPI no longer uses RC to share state - anything that is `Clone` can be shared
## Community Additions
- [Styled Components macro](https://github.com/Zomatree/Revolt-Client/blob/master/src/utils.rs#14-27) [@Zomatree](https://github.com/Zomatree)
- [Dioxus-Websocket hook](https://github.com/FruitieX/dioxus-websocket-hooks) [@FruitieX](https://github.com/FruitieX)
- [Home automation server app](https://github.com/FruitieX/homectl) [@FruitieX](https://github.com/FruitieX)
- [Video Recording app](https://github.com/rustkid/recorder)
- [Music streaming app](https://github.com/autarch/Crumb/tree/master/web-frontend) [@autarch](https://github.com/autarch)
- [NixOS dependency installation](https://gist.github.com/FruitieX/73afe3eb15da45e0e05d5c9cf5d318fc) [@FruitieX](https://github.com/FruitieX)
- [Vercel Deploy Template](https://github.com/lucifer1004/dioxus-vercel-demo) [@lucifer1004](https://github.com/lucifer1004)
- [Render Katex in Dioxus](https://github.com/oovm/katex-wasm)
- [Render PrismJS in Dioxus](https://github.com/oovm/prism-wasm)
- [Compile-time correct TailwindCSS](https://github.com/houseabsolute/tailwindcss-to-rust)
- [Autogenerate tailwind CSS](https://github.com/oovm/tailwind-rs)
- [Heroicons library](https://github.com/houseabsolute/dioxus-heroicons)
- [RSX -> HTML translator app](https://dioxus-convert.netlify.app)
- [Toast Support](https://github.com/mrxiaozhuox/dioxus-toast)
- New Examples: forms, routers, linking, tui, and more!
## Looking Forward
Dioxus is still under rapid, active development. We'd love for you to get involved! For the next release, we're looking to add:
- Native WGPU renderer support
- A query library like react-query
- Multiwindow desktop app support
- Full LiveView integrations for Axum, Warp, and Actix
- A builder pattern for elements (no need for rsx!)
- Autoformatting of rsx! code (like cargo fmt)
- Improvements to the VSCode Extension
If you're interested in building an app with Dioxus, make sure to check us out on:
- [Github](http://github.com/dioxusLabs/dioxus)
- [Reddit](http://reddit.com/r/dioxus/)
- [Discord](https://discord.gg/XgGxMSkvUM)
- [Twitter](http://twitter.com/dioxuslabs)
+408
View File
@@ -0,0 +1,408 @@
# Introducing Dioxus v0.1 ✨
> Jan 3, 2022
> [@jkelleyrtp](https://github.com/jkelleyrtp), thanks [@alexkirsz](https://github.com/alexkirsz)
After many months of work, we're very excited to release the first version of Dioxus!
Dioxus is a new library for building interactive user interfaces (GUI) with Rust. It is built around a Virtual DOM, making it portable for the web, desktop, server, mobile, and more.
Dioxus has the following design goals:
- **Familiar**: Offer a React-like mental model and API surface
- **Robust**: Avoid runtime bugs by moving rules and error handling into the type system
- **Performant**: Scale to the largest apps and the largest teams
- **Productive**: Comprehensive inline documentation, fast recompiles, and deeply integrated tooling
- **Extensible**: Reusable hooks and components that work on every platform
Dioxus is designed to be familiar for developers already comfortable with React paradigms. Our goal is to ensure a smooth transition from TypeScript/React without having to learn any major new concepts.
To give you an idea of what Dioxus looks like, here's a simple counter app:
```rust
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app)
}
fn app(cx: Scope) -> Element {
let mut count = use_state(&cx, || 0);
cx.render(rsx! {
h1 { "Count: {count}" }
button { onclick: move |_| count += 1, "+" }
button { onclick: move |_| count -= 1, "-" }
})
}
```
This simple counter is a complete desktop application, running at native speeds on a native thread. Dioxus automatically shuttles all events from the WebView runtime into the application code. In our app, we can interact natively with system APIs, run multi-threaded code, and do anything a regular native Rust application might do. Running `cargo build --release` will compile a portable binary that looks and feels the same on Windows, macOS, and Linux. We can then use `cargo-bundle` to bundle our binary into a native `.app`/`.exe`/`.deb`.
Dioxus supports many of the same features React does including:
- Server-side-rendering, pre-rendering, and hydration
- Mobile, desktop, and web support
- Suspense, fibers, coroutines, and error handling
- Hooks, first-class state management, components
- Fragments, conditional rendering, and custom elements
However, some things are different in Dioxus:
- Automatic memoization (opt-out rather than opt-in)
- No effects - effectual code can only originate from actions or coroutines
- Suspense is implemented as hooks - _not_ deeply ingrained within Dioxus Core
- Async code is _explicit_ with a preference for _coroutines_ instead
As a demo, here's our teaser example running on all our current supported platforms:
![Teaser Example](/static/Untitled.png)
This very site is built with Dioxus, and the source code is available [here](https://github.com/dioxuslabs/docsite).
To get started with Dioxus, check out any of the "Getting Started" guides for your platform of choice, or check out the GitHub Repository for more details.
- [Getting Started with Dioxus](https://dioxuslabs.com/guide)
- [Getting Started with Web](https://dioxuslabs.com/reference/web)
- [Getting Started with Desktop](https://dioxuslabs.com/reference/desktop)
- [Getting Started with Mobile](https://dioxuslabs.com/reference/mobile)
- [Getting Started with SSR](https://dioxuslabs.com/reference/ssr)
## Show me some examples of what can be built!
- [File explorer desktop app](https://github.com/dioxuslabs/example-projects)
- [WiFi scanner desktop app](https://github.com/dioxuslabs/example-projects)
- [Dog CEO API Search](https://github.com/dioxuslabs/example-projects)
- [TodoMVC Mobile App](https://github.com/dioxuslabs/example-projects)
- [E-Commerce Liveview App](https://github.com/dioxuslabs/example-projects)
## Why should I use Rust and Dioxus for frontend?
We believe that Rust's ability to write high-level and statically typed code should make it easier for frontend teams to take on even the most ambitious of projects. Rust projects can be refactored fearlessly: the powerful type system prevents an entire class of bugs at compile-time. No more `cannot read property of undefined` ever again! With Rust, all errors must be accounted for at compile time. You cannot ship an app that does not — in some way — handle its errors.
### Difference from TypeScript/React:
TypeScript is still fundamentally JavaScript. If you've written enough TypeScript, you might be bogged down with lots of configuration options, lack of proper support for "go-to-source," or incorrect ad-hoc typing. With Rust, strong types are built-in, saving tons of headache like `cannot read property of undefined`.
By using Rust, we gain:
- Strong types for every library
- Immutability by default
- A simple and intuitive module system
- Integrated documentation (go to source actually goes to source instead of the `.d.ts` file)
- Advanced pattern matching
- Clean, efficient, composable iterators
- Inline built-in unit/integration testing
- High quality error handling
- Flexible standard library and traits
- Powerful macro system
- Access to the [crates.io](https://crates.io) ecosystem
Dioxus itself leverages this platform to provide the following guarantees:
- Correct use of immutable data structures
- Guaranteed handling of errors and null-values in components
- Native performance on mobile
- Direct access to system IO
And much more. Dioxus makes Rust apps just as fast to write as React apps, but affords more robustness, giving your frontend team greater confidence in making big changes in shorter timespans.
Semantically, TypeScript-React and Rust-Dioxus are very similar. In TypeScript, we would declare a simple component as:
```tsx
type CardProps = {
title: string,
paragraph: string,
};
const Card: FunctionComponent<CardProps> = (props) => {
let [count, set_count] = use_state(0);
return (
<aside>
<h2>{props.title}</h2>
<p> {props.paragraph} </p>
<button onclick={() => set_count(count + 1)}> Count {count} </button>
</aside>
);
};
```
In Dioxus, we would define the same component in a similar fashion:
```rust
#[derive(Props, PartialEq)]
struct CardProps {
title: String,
paragraph: String
}
static Card: Component<CardProps> = |cx| {
let mut count = use_state(&cx, || 0);
cx.render(rsx!(
aside {
h2 { "{cx.props.title}" }
p { "{cx.props.paragraph}" }
button { onclick: move |_| count+=1, "Count: {count}" }
}
))
};
```
However, we recognize that not every project needs Rust - many are fine with JavaScript! We also acknowledge that Rust/Wasm/Dioxus does not fix "everything that is wrong with frontend development." There are always going to be new patterns, frameworks, and languages that solve these problems better than Rust and Dioxus.
As a general rule of thumb, Dioxus is for you if:
- your app will become very large
- you need to share code across many platforms
- you want a fast way to build for desktop
- you want to avoid electron or need direct access to hardware
- you're tired of JavaScript tooling
Today, to publish a Dioxus app, you don't need NPM/WebPack/Parcel/etc. Dioxus simply builds with cargo, and for web builds, Dioxus happily works with the popular [trunk](http://trunkrs.dev) project.
## Show me more
Here, we'll dive into some features of Dioxus and why it's so fun to use. The [guide](https://dioxuslabs.com/guide/) serves as a deeper and more comprehensive look at what Dioxus can do.
## Building a new project is simple
To start a new project, all you need is Cargo, which comes with Rust. For a simple desktop app, all we'll need is the `dioxus` crate with the appropriate `desktop` feature. We start by initializing a new binary crate:
```shell
$ cargo init dioxus_example
$ cd dioxus_example
```
We then add a dependency on Dioxus to the `Cargo.toml` file, with the "desktop" feature enabled:
```rust
[dependencies]
dioxus = { version = "*", features = ["desktop"] }
```
We can add our counter from above.
```rust
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app)
}
fn app(cx: Scope) -> Element {
let mut count = use_state(&cx, || 0);
cx.render(rsx! {
h1 { "Count: {count}" }
button { onclick: move |_| count += 1, "+" }
button { onclick: move |_| count -= 1, "-" }
})
}
```
And voilà! We can `cargo run` our app
![Simple Counter Desktop App](/static/counter.png)
## Support for JSX-style templating
Dioxus ships with a templating macro called RSX, a spin on React's JSX. RSX is very similar to regular struct syntax for Rust so it integrates well with your IDE. If used with [Rust-Analyzer](https://github.com/rust-analyzer/rust-analyzer) (not tested anywhere else) RSX supports code-folding, block selection, bracket pair colorizing, autocompletion, symbol renaming — pretty much anything you would expect from writing regular struct-style code.
```rust
rsx! {
div { "Hello world" }
button {
onclick: move |_| log::info!("button pressed"),
"Press me"
}
}
```
If macros aren't your style, you can always drop down to the factory API:
```rust
LazyNodes::new(|f| {
f.fragment([
f.element(div, [f.text("hello world")], [], None, None)
f.element(
button,
[f.text("Press Me")],
[on::click(move |_| log::info!("button pressed"))],
None,
None
)
])
})
```
The `rsx!` macro generates idiomatic Rust code that uses the factory API — no different than what you'd write by hand yourself.
To make it easier to work with RSX, we've built a small [VSCode extension](https://github.com/DioxusLabs/studio) with useful utilities. This extension provides a command that converts a selected block of HTML into RSX so you can easily reuse existing web templates.
## Dioxus prioritizes developer experience
Many of the Rust UI frameworks are particularly difficult to work with. Even the ones branded as "ergonomic" are quite challenging to in comparison to TSX/JSX. With Dioxus, we've innovated on a number of Rust patterns to deliver a framework that is actually enjoyable to develop in.
For example, many Rust frameworks require you to clone your data in for *every* closure and handler you use. This can get really clumsy for large apps.
```rust
div()
.children([
button().onclick(cloned!(name, date, age, description => move |evt| { /* */ })
button().onclick(cloned!(name, date, age, description => move |evt| { /* */ })
button().onclick(cloned!(name, date, age, description => move |evt| { /* */ })
])
```
Dioxus understands the lifetimes of data borrowed from `Scope`, so you can safely return any borrowed data without declaring explicit captures. Hook handles all implement `Copy` so they can be shared between listeners without any ceremony.
```rust
let name = use_state(&cx, || "asd");
rsx! {
div {
button { onclick: move |_| name.set("abc") }
button { onclick: move |_| name.set("def") }
button { onclick: move |_| name.set("ghi") }
}
}
```
Because we know the lifetime of your handlers, we can also expose this to children. No other Rust frameworks let us share borrowed state through the tree, forcing use of Rc/Arc everywhere. With Dioxus, all the Rc/Arc magic is tucked away in hooks, and just beautiful borrowed interfaces are exposed to your code. You don't need to know how Rc/RefCell work to build a competent Dioxus app.
```rust
fn app(cx: Scope) -> Element {
let name = use_state(&cx, || "asd");
cx.render(rsx!{
Button { name: name }
})
}
#[derive(Props)]
struct ButtonProps<'a> {
name: UseState<'a, &'static str>
}
fn Button<'a>(cx: Scope<'a, Childprops<'a>>) -> Element {
cx.render(rsx!{
button {
onclick: move |_| cx.props.name.set("bob")
}
})
}
```
There's *way* more to this story, but hopefully we've convinced you that Dioxus' DX somewhat approximates JSX/React.
## Dioxus is perfected for the IDE
Note: all IDE-related features have only been tested with [Rust-Analyzer](https://github.com/rust-analyzer/rust-analyzer).
Dioxus code operates pleasantly with your IDE. For starters, most elements are documented through the Rustdoc system. A quick summary of the MDN docs is always under your finger tips:
![Elements have hover context](/static/ide_hover.png)
Dioxus also wraps platform-specific events with a custom synthetic event system. This means events enjoy proper autocomplete and documentation, unlike [Yew](https://yew.rs/) which currently relies on [web-sys](https://crates.io/crates/web-sys) with incomplete IDE support:
![Events are strongly typed](/static/ide_autocomplete.png)
Even element attributes and event handlers have top-notch documentation!
![Element attributes and listeners have hover context](/static/ide_listener.png)
The `rsx!` macro also benefits from code folding, batch renaming, and block selection, making most basic code navigation and completion tasks a breeze.
![Element blocks can be folded and renamed](/static/ide_selection.png)
Furthermore, the `rsx!` macro itself is documented, so if you ever forget how to use a certain feature, the documentation remains close at hand:
![The RSX documentation is provided on hover](/static/ide_rsx.png)
We spent a ton of time on this and we hope you enjoy it!
## Dioxus is extremely fast
We take the performance of Dioxus seriously. Instead of resolving to "good enough," Dioxus is designed to push the limits of what a declarative React-like framework can achieve. Dioxus is designed with multi-tenancy in mind: a single machine should be able to run thousands of simultaneous low-latency LiveView apps without skipping a beat. To accomplish this goal we've implemented a large number of optimizations:
- Usage of bump memory allocators and double-buffering
- Compile-time hashing of templates
- Automatic component memoization
- Fiber-like scheduler
- DOM Patch Batching
Dioxus is humbly built off the work done by [Dodrio](https://github.com/fitzgen/dodrio), a now-archived research project by fitzgen exploring the use of bump allocators in UI frameworks.
Dioxus is *substantially* more performant than many of the other Rust DOM-based UI libraries (Yew/Percy) and is *significantly* more performant than React - roughly competitive with InfernoJS. While not as performant as libraries like SolidJS/Sycamore, Dioxus imposes roughly a ~3% overhead over DOM patching, so it's *plenty* fast.
## Works on Desktop and Mobile
We’ve mentioned before that Dioxus works practically anywhere that Rust does. When running natively as a desktop or mobile app, your Dioxus code will run on its own thread, not inside of a web runtime. This means you can access hardware, file system, and platform APIs directly without needing to go through a shim layer. In our examples, we feature a [file explorer app](https://github.com/DioxusLabs/example-projects/tree/master/file-explorer) and [WiFi scanner app](https://github.com/DioxusLabs/example-projects/tree/master/wifi-scanner) where platform access occurs inside an asynchronous multithreaded coroutine. This solves the problem faced by React Native and other cross-platform toolkits where JavaScript apps incur a massive performance penalty with substantial maintenance overhead associated with platform API shims.
A desktop app:
[![Example Dioxus desktop app](https://github.com/DioxusLabs/example-projects/raw/master/file-explorer/image.png)](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer)
A mobile app:
[![Example Dioxus mobile app](https://github.com/DioxusLabs/example-projects/raw/master/ios_demo/assets/screenshot_smaller.jpeg)](https://github.com/DioxusLabs/example-projects/blob/master/ios_demo)
However, be warned that mobile is currently considered very experimental and there will likely be quirks. Dioxus is leveraging the work done by the [Tauri](https://github.com/tauri-apps/tauri) team to enable mobile support, and mobile support isn't technically complete in Tauri yet.
iOS should be supported out of the box, but Android support will take custom some boilerplate that hasn't been completely figured out. If you're interested in contributing to Dioxus, improving mobile support would be extremely helpful.
### Did someone say TUI support?
Yes, you can even build terminal user interfaces with Dioxus. Full support is still a work in progress, but the foundation is there.
[![TUI Support](https://github.com/DioxusLabs/rink/raw/master/examples/example.png)](https://github.com/dioxusLabs/rink)
### Things we didn't cover:
There are a bunch of things we didn't talk about here. Check out the guide for more information, or peruse the examples and reference for more context.
- Jank-free rendering with fiber scheduler
- [Support for borrowed props]()
- [Conditional rendering]()
- [CSS/Styling/Inline style support]()
- [Support for inline Context Providing/Consuming]()
- [First-class global state management]()
For a quick glance at party with React, check out the [Readme on Github](https://github.com/DioxusLabs/dioxus#parity-with-react).
## What's on the roadmap?
The world of Rust on the frontend is barely explored. Given the performance, ergonomics, and portability of Rust/Dioxus, we expect there to be a ton of different applications where having a React-like toolkit running natively can enable things previously considered impossible.
In the coming weeks, our plan is to finish the remaining outstanding features where Dioxus is lacking in comparison to React:
- Transition effects for Suspense
- Micro-optimizations and better cross-platform/browser bug mitigations
- Heuristics to guide the diffing algorithm
- Better support for subtree memoization (signals, etc.)
- More thorough documentation, fleshing out sore spots
We also need some help in important crates currently missing:
- First class cross-platform router (currently in progress)
- An extension to DioxusStudio that enables lazy bundling of static assets
- Animation library (see [React Spring](https://react-spring.io/), [Framer Motion](https://www.framer.com/motion/))
- A [TUI renderer for Dioxus](https://github.com/dioxuslabs/rink) (see [Ink](https://github.com/vadimdemedes/ink))
And finally, some bigger, forward-thinking projects that are too big for a one-person team:
- Completely native renderer for the Dioxus Virtual DOM (see [Flutter](https://flutter.dev/))
- Better support for LiveView
- Code-splitting
- 3D renderer (see [react-three-fiber](https://github.com/pmndrs/react-three-fiber))
Stay tuned for our next article, which will go over some of the optimization techniques that went into making Dioxus blazing fast.
## Community
The future is bright for Rust frontends! If you'd like to get involved, we have a [Discord server](https://discord.gg/XgGxMSkvUM), [a subreddit](http://reddit.com/r/dioxus), and [GitHub discussion pages](https://github.com/DioxusLabs/dioxus/discussions).
Let us know what you build!
Check out the original `/r/rust` thread here.
+6 -5
View File
@@ -2,11 +2,12 @@
- [Introduction](README.md)
- [Web](web/index.md)
- [Desktop](desktop/index.md)
- [Mobile](mobile/index.md)
- [SSR](ssr/index.md)
- [TUI](tui/index.md)
- [Platforms](platforms/index.md)
- [Web](platforms/web.md)
- [Server Side Rendering](platforms/ssr.md)
- [Desktop](platforms/desktop.md)
- [Mobile](platforms/mobile.md)
- [TUI](platforms/tui.md)
- [Advanced Guides](guide/index.md)
- [RSX in Depth](guide/rsx_in_depth.md)
@@ -1,4 +1,4 @@
# Desktop
# 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.
+10
View File
@@ -0,0 +1,10 @@
# 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)
@@ -1,15 +1,15 @@
# Getting started: mobile
Dioxus is unique in that it actually supports mobile. However, support is very young and you might need to dip down into some of the primitives until better supported is ready.
Dioxus is unique in that it actually supports mobile. However, support is very young and you might need to dip down into some of the primitives until better support is ready.
Currently, only iOS is supported through us, however you *can* add android support by following the same instructions below, but using the `android` guide in `cargo-mobile`.
Currently, only iOS is supported through us, however you *can* add android support by following the same instructions below by using the `android` guide in `cargo-mobile`.
Also, Dioxus Desktop and Dioxus Mobile share the same codebase, and dioxus-mobile currently just re-exports dioxus-desktop.
## Getting Set up
Getting set up with mobile can but quite challenging. The tooling here isn't great (yet) and might take some hacking around to get things working. macOS M1 is broadly unexplored and might not work for you.
Getting set up with mobile can be quite challenging. The tooling here isn't great (yet) and might take some hacking around to get things working. macOS M1 is broadly unexplored and might not work for you.
We're going to be using `cargo-mobile` to build for mobile. First, install it:
@@ -21,7 +21,7 @@ $ cargo install --git https://github.com/BrainiumLLC/cargo-mobile
And then initialize your app for the right platform. Use the `winit` template for now. Right now, there's no "Dioxus" template in cargo-mobile.
```shell
$ cargo moble init
$ cargo mobile init
```
We're going to completely clear out the `dependencies` it generates for us, swapping out `winit` with `dioxus-mobile`.
@@ -2,11 +2,8 @@
The Dioxus VirtualDom can be rendered to a string by traversing the Element Tree. This is implemented in the `ssr` crate where your Dioxus app can be directly rendered to HTML to be served by a web server.
## Setup
If you just want to render `rsx!` or a VirtualDom to HTML, check out the API docs. It's pretty simple:
```rust
@@ -19,8 +16,7 @@ println!("{}", dioxus::ssr::render_vdom(&vdom));
println!( "{}", dioxus::ssr::render_lazy(rsx! { h1 { "Hello, world!" } } );
```
However, for this guide, we're going to show how to use Dioxus SSR with `Axum`.
However, for this guide, we're going to show how to use Dioxus SSR with `Axum`.
Make sure you have Rust and Cargo installed, and then create a new project:
@@ -29,15 +25,16 @@ $ cargo new --bin demo
$ cd app
```
Add Dioxus with the `desktop` feature:
Add Dioxus with the `ssr` feature:
```shell
$ cargo add dioxus --features ssr
```
Next, add all the Axum dependencies. This will be different if you're using a different Web Framework
```
$ cargo add dioxus tokio --features full
$ cargo add tokio --features full
$ cargo add axum
```
@@ -45,12 +42,11 @@ Your dependencies should look roughly like this:
```toml
[dependencies]
axum = "0.4.3"
axum = "0.4.5"
dioxus = { version = "*", features = ["ssr"] }
tokio = { version = "1.15.0", features = ["full"] }
```
Now, setup your Axum app to respond on an endpoint.
```rust
@@ -63,7 +59,11 @@ async fn main() {
println!("listening on http://{}", addr);
axum::Server::bind(&addr)
.serve(Router::new().route("/", get(app_endpoint)))
.serve(
Router::new()
.route("/", get(app_endpoint))
.into_make_service(),
)
.await
.unwrap();
}
@@ -88,14 +88,14 @@ async fn app_endpoint() -> Html<String> {
}
let mut app = VirtualDom::new(app);
let _ = app.rebuild();
Html(dioxus::ssr::render_vdom(&app))
}
```
And that's it!
> You might notice that you cannot hold the VirtualDom across an await point. Dioxus is currently not ThreadSafe, so it *must* remain on the thread it started. We are working on loosening this requirement.
> You might notice that you cannot hold the VirtualDom across an await point. Dioxus is currently not ThreadSafe, so it _must_ remain on the thread it started. We are working on loosening this requirement.
## Future Steps
+100
View File
@@ -0,0 +1,100 @@
# Getting Started: TUI
TUI support is currently quite experimental. Even the project name will change. But, if you're willing to venture into the realm of the unknown, this guide will get you started.
[TUI Support](https://github.com/DioxusLabs/rink/raw/master/examples/example.png)
## Getting Set up
To tinker with TUI support, start by making a new package and adding our TUI feature.
```shell
$ cargo new --bin demo
$ cd demo
$ cargo add dioxus --features tui
```
Then, edit your `main.rs` with the basic template.
```rust
// main
use dioxus::prelude::*;
fn main() {
dioxus::tui::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
width: "100%",
height: "10px",
background_color: "red",
justify_content: "center",
align_items: "center",
"Hello world!"
}
})
}
```
To run our app:
```shell
$ cargo run
```
Press "ctrl-c" to close the app. To switch from "ctrl-c" to just "q" to quit you can launch the app with a configuration to disable the default quit and use the root TuiContext to quit on your own.
```rust
// main
use dioxus::events::{KeyCode, KeyboardEvent};
use dioxus::prelude::*;
use dioxus::tui::TuiContext;
fn main() {
dioxus::tui::launch_cfg(
app,
dioxus::tui::Config {
ctrl_c_quit: false,
// Some older terminals only support 16 colors or ANSI colors if your terminal is one of these change this to BaseColors or ANSI
rendering_mode: dioxus::tui::RenderingMode::Rgb,
},
);
}
fn app(cx: Scope) -> Element {
let tui_ctx: TuiContext = cx.consume_context().unwrap();
cx.render(rsx! {
div {
width: "100%",
height: "10px",
background_color: "red",
justify_content: "center",
align_items: "center",
onkeydown: move |k: KeyboardEvent| if let KeyCode::Q = k.data.key_code {
tui_ctx.quit();
},
"Hello world!"
}
})
}
```
## Notes
- Our TUI package uses flexbox for layout
- 1px is one character lineheight. Your regular CSS px does not translate.
- If your app panics, your terminal is wrecked. This will be fixed eventually.
## Future Steps
Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't!
@@ -1,4 +1,4 @@
# Getting Started: Dioxus for Web
# Getting Started: Web
[*"Pack your things, we're going on an adventure!"*](https://trunkrs.dev)
@@ -77,7 +77,7 @@ trunk serve
To build our app and publish it to Github:
- Make sure Github Pages is set up for your repo
- Build your app with `trunk build --release`
- Build your app with `trunk build --release` (include `--public-url <repo-name>` to update asset prefixes if using a project site)
- Move your generated HTML/CSS/JS/Wasm from `dist` into the folder configured for Github Pages
- Add and commit with git
- Push to Github
-67
View File
@@ -1,67 +0,0 @@
# TUI
TUI support is currently quite experimental. Even the project name will change. But, if you're willing to venture into the realm of the unknown, this guide will get you started.
[TUI Support](https://github.com/DioxusLabs/rink/raw/master/examples/example.png)
## Getting Set up
To tinker with TUI support, start by making a new package and adding our TUI package from git.
```shell
$ cargo new --bin demo
$ cd demo
$ cargo add dioxus
$ cargo add rink --git https://github.com/DioxusLabs/rink.git
```
Then, edit your `main.rs` with the basic template.
```rust
// main
use dioxus::prelude::*;
fn main() {
let mut dom = VirtualDom::new(app);
dom.rebuild();
rink::render_vdom(&mut dom).unwrap();
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
width: "100%",
height: "10px",
background_color: "red",
justify_content: "center",
align_items: "center",
"Hello world!"
}
})
}
```
To run our app:
```shell
$ cargo run
```
Press "q" to close the app (yes, this is hardcoded, we are working on handlers to expose this in your code.)
## Notes
- Our TUI package uses flexbox for layout
- 1px is one character lineheight. Your regular CSS px does not translate.
- If your app panics, your terminal is wrecked. This will be fixed eventually.
## Future Steps
Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't!
+10
View File
@@ -0,0 +1,10 @@
# 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.
Dioxus comes with a router built-in. To start utilizing Dioxus Router, enable the ``router`` feature in your ``Cargo.toml`` file.
```toml
[dependencies]
dioxus = { version = "x.x.x", features = [.., "router"] }
```
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).
+9 -1
View File
@@ -1,3 +1,11 @@
# Summary
- [Chapter 1](./chapter_1.md)
- [Introduction](./README.md)
- [Guide](./guide/index.md)
- [Getting Started](./guide/getting-started.md)
- [Creating Our First Route](./guide/first-route.md)
- [Building a Nest](./guide/building-a-nest.md)
- [Redirection Perfection](./guide/redirection-perfection.md)
- [Reference](./reference/index.md)
- [X]()
-3
View File
@@ -1,3 +0,0 @@
# Chapter 1
The Dioxus Router is very important!!
+201
View File
@@ -0,0 +1,201 @@
# 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.
+95
View File
@@ -0,0 +1,95 @@
# 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.
+68
View File
@@ -0,0 +1,68 @@
# 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.
+14
View File
@@ -0,0 +1,14 @@
# 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).
@@ -0,0 +1,51 @@
# 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.
+2
View File
@@ -0,0 +1,2 @@
# Dioxus Router: Reference
This section includes a reference to Dioxus Router's API and functionality.
+10 -8
View File
@@ -41,6 +41,8 @@ These examples are not necessarily meant to be run, but rather serve as a refere
| [Anti-patterns](./antipatterns.rs) | A collection of discouraged patterns | ✅ |
| [Complete rsx reference](./rsx_usage.rs) | A complete reference for all rsx! usage | ✅ |
| [Event Listeners](./listener.rs) | Attach closures to events on elements | ✅ |
| [Inline Props](./inlineprops.rs) | Using the `#[inline_props]` macro | ✅ |
| [Eval](./eval.rs) | Evaluate dynamic JavaScript code | ✅ |
## Show me some examples!
@@ -51,7 +53,7 @@ In our collection of examples, guides, and tutorials, we have:
- The reference (a collection of examples with heavy documentation)
- The general examples
- The platform-specific examples (web, ssr, desktop, mobile, server)
Here's what a few common tasks look like in Dioxus:
Nested components with children and internal state:
@@ -80,7 +82,7 @@ Controlled inputs:
```rust
fn App(cx: Scope) -> Element {
let value = use_state(&cx, String::new);
cx.render(rsx!(
cx.render(rsx!(
input {
"type": "text",
value: "{value}",
@@ -96,16 +98,16 @@ fn App(cx: Scope) -> Element {
let list = (0..10).map(|i| {
rsx!(li { key: "{i}", "Value: {i}" })
});
let title = match list.len() {
0 => rsx!("Not enough"),
_ => rsx!("Plenty!"),
};
if should_show {
cx.render(rsx!(
cx.render(rsx!(
title,
ul { list }
ul { list }
))
} else {
None
@@ -165,18 +167,18 @@ fn App(cx: Scope) -> Element {
Route::Home => rsx!( Home {} ),
Route::Post(id) => rsx!( Post { id: id })
}
}))
}))
}
```
Suspense
Suspense
```rust
fn App(cx: Scope) -> Element {
let doggo = use_suspense(cx,
|| async { reqwest::get("https://dog.ceo/api/breeds/image/random").await.unwrap().json::<Response>().await.unwrap() },
|response| cx.render(rsx!( img { src: "{response.message}" }))
);
cx.render(rsx!{
div {
"One doggo coming right up:",
+411
View File
@@ -0,0 +1,411 @@
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
align_content: "a",
align_items: "a",
align_self: "a",
alignment_adjust: "a",
alignment_baseline: "a",
all: "a",
alt: "a",
animation: "a",
animation_delay: "a",
animation_direction: "a",
animation_duration: "a",
animation_fill_mode: "a",
animation_iteration_count: "a",
animation_name: "a",
animation_play_state: "a",
animation_timing_function: "a",
azimuth: "a",
backface_visibility: "a",
background: "a",
background_attachment: "a",
background_clip: "a",
background_color: "a",
background_image: "a",
background_origin: "a",
background_position: "a",
background_repeat: "a",
background_size: "a",
background_blend_mode: "a",
baseline_shift: "a",
bleed: "a",
bookmark_label: "a",
bookmark_level: "a",
bookmark_state: "a",
border: "a",
border_color: "a",
border_style: "a",
border_width: "a",
border_bottom: "a",
border_bottom_color: "a",
border_bottom_style: "a",
border_bottom_width: "a",
border_left: "a",
border_left_color: "a",
border_left_style: "a",
border_left_width: "a",
border_right: "a",
border_right_color: "a",
border_right_style: "a",
border_right_width: "a",
border_top: "a",
border_top_color: "a",
border_top_style: "a",
border_top_width: "a",
border_collapse: "a",
border_image: "a",
border_image_outset: "a",
border_image_repeat: "a",
border_image_slice: "a",
border_image_source: "a",
border_image_width: "a",
border_radius: "a",
border_bottom_left_radius: "a",
border_bottom_right_radius: "a",
border_top_left_radius: "a",
border_top_right_radius: "a",
border_spacing: "a",
bottom: "a",
box_decoration_break: "a",
box_shadow: "a",
box_sizing: "a",
box_snap: "a",
break_after: "a",
break_before: "a",
break_inside: "a",
buffered_rendering: "a",
caption_side: "a",
clear: "a",
clear_side: "a",
clip: "a",
clip_path: "a",
clip_rule: "a",
color: "a",
color_adjust: "a",
color_correction: "a",
color_interpolation: "a",
color_interpolation_filters: "a",
color_profile: "a",
color_rendering: "a",
column_fill: "a",
column_gap: "a",
column_rule: "a",
column_rule_color: "a",
column_rule_style: "a",
column_rule_width: "a",
column_span: "a",
columns: "a",
column_count: "a",
column_width: "a",
contain: "a",
content: "a",
counter_increment: "a",
counter_reset: "a",
counter_set: "a",
cue: "a",
cue_after: "a",
cue_before: "a",
cursor: "a",
direction: "a",
display: "a",
display_inside: "a",
display_outside: "a",
display_extras: "a",
display_box: "a",
dominant_baseline: "a",
elevation: "a",
empty_cells: "a",
enable_background: "a",
fill: "a",
fill_opacity: "a",
fill_rule: "a",
filter: "a",
float: "a",
float_defer_column: "a",
float_defer_page: "a",
float_offset: "a",
float_wrap: "a",
flow_into: "a",
flow_from: "a",
flex: "a",
flex_basis: "a",
flex_grow: "a",
flex_shrink: "a",
flex_flow: "a",
flex_direction: "a",
flex_wrap: "a",
flood_color: "a",
flood_opacity: "a",
font: "a",
font_family: "a",
font_size: "a",
font_stretch: "a",
font_style: "a",
font_weight: "a",
font_feature_settings: "a",
font_kerning: "a",
font_language_override: "a",
font_size_adjust: "a",
font_synthesis: "a",
font_variant: "a",
font_variant_alternates: "a",
font_variant_caps: "a",
font_variant_east_asian: "a",
font_variant_ligatures: "a",
font_variant_numeric: "a",
font_variant_position: "a",
footnote_policy: "a",
glyph_orientation_horizontal: "a",
glyph_orientation_vertical: "a",
grid: "a",
grid_auto_flow: "a",
grid_auto_columns: "a",
grid_auto_rows: "a",
grid_template: "a",
grid_template_areas: "a",
grid_template_columns: "a",
grid_template_rows: "a",
grid_area: "a",
grid_column: "a",
grid_column_start: "a",
grid_column_end: "a",
grid_row: "a",
grid_row_start: "a",
grid_row_end: "a",
hanging_punctuation: "a",
height: "a",
hyphenate_character: "a",
hyphenate_limit_chars: "a",
hyphenate_limit_last: "a",
hyphenate_limit_lines: "a",
hyphenate_limit_zone: "a",
hyphens: "a",
icon: "a",
image_orientation: "a",
image_resolution: "a",
image_rendering: "a",
ime: "a",
ime_align: "a",
ime_mode: "a",
ime_offset: "a",
ime_width: "a",
initial_letters: "a",
inline_box_align: "a",
isolation: "a",
justify_content: "a",
justify_items: "a",
justify_self: "a",
kerning: "a",
left: "a",
letter_spacing: "a",
lighting_color: "a",
line_box_contain: "a",
line_break: "a",
line_grid: "a",
line_height: "a",
line_slack: "a",
line_snap: "a",
list_style: "a",
list_style_image: "a",
list_style_position: "a",
list_style_type: "a",
margin: "a",
margin_bottom: "a",
margin_left: "a",
margin_right: "a",
margin_top: "a",
marker: "a",
marker_end: "a",
marker_mid: "a",
marker_pattern: "a",
marker_segment: "a",
marker_start: "a",
marker_knockout_left: "a",
marker_knockout_right: "a",
marker_side: "a",
marks: "a",
marquee_direction: "a",
marquee_play_count: "a",
marquee_speed: "a",
marquee_style: "a",
mask: "a",
mask_image: "a",
mask_repeat: "a",
mask_position: "a",
mask_clip: "a",
mask_origin: "a",
mask_size: "a",
mask_box: "a",
mask_box_outset: "a",
mask_box_repeat: "a",
mask_box_slice: "a",
mask_box_source: "a",
mask_box_width: "a",
mask_type: "a",
max_height: "a",
max_lines: "a",
max_width: "a",
min_height: "a",
min_width: "a",
mix_blend_mode: "a",
nav_down: "a",
nav_index: "a",
nav_left: "a",
nav_right: "a",
nav_up: "a",
object_fit: "a",
object_position: "a",
offset_after: "a",
offset_before: "a",
offset_end: "a",
offset_start: "a",
opacity: "a",
order: "a",
orphans: "a",
outline: "a",
outline_color: "a",
outline_style: "a",
outline_width: "a",
outline_offset: "a",
overflow: "a",
overflow_x: "a",
overflow_y: "a",
overflow_style: "a",
overflow_wrap: "a",
padding: "a",
padding_bottom: "a",
padding_left: "a",
padding_right: "a",
padding_top: "a",
page: "a",
page_break_after: "a",
page_break_before: "a",
page_break_inside: "a",
paint_order: "a",
pause: "a",
pause_after: "a",
pause_before: "a",
perspective: "a",
perspective_origin: "a",
pitch: "a",
pitch_range: "a",
play_during: "a",
pointer_events: "a",
position: "a",
quotes: "a",
region_fragment: "a",
resize: "a",
rest: "a",
rest_after: "a",
rest_before: "a",
richness: "a",
right: "a",
ruby_align: "a",
ruby_merge: "a",
ruby_position: "a",
scroll_behavior: "a",
scroll_snap_coordinate: "a",
scroll_snap_destination: "a",
scroll_snap_points_x: "a",
scroll_snap_points_y: "a",
scroll_snap_type: "a",
shape_image_threshold: "a",
shape_inside: "a",
shape_margin: "a",
shape_outside: "a",
shape_padding: "a",
shape_rendering: "a",
size: "a",
speak: "a",
speak_as: "a",
speak_header: "a",
speak_numeral: "a",
speak_punctuation: "a",
speech_rate: "a",
stop_color: "a",
stop_opacity: "a",
stress: "a",
string_set: "a",
stroke: "a",
stroke_dasharray: "a",
stroke_dashoffset: "a",
stroke_linecap: "a",
stroke_linejoin: "a",
stroke_miterlimit: "a",
stroke_opacity: "a",
stroke_width: "a",
tab_size: "a",
table_layout: "a",
text_align: "a",
text_align_all: "a",
text_align_last: "a",
text_anchor: "a",
text_combine_upright: "a",
text_decoration: "a",
text_decoration_color: "a",
text_decoration_line: "a",
text_decoration_style: "a",
text_decoration_skip: "a",
text_emphasis: "a",
text_emphasis_color: "a",
text_emphasis_style: "a",
text_emphasis_position: "a",
text_emphasis_skip: "a",
text_height: "a",
text_indent: "a",
text_justify: "a",
text_orientation: "a",
text_overflow: "a",
text_rendering: "a",
text_shadow: "a",
text_size_adjust: "a",
text_space_collapse: "a",
text_spacing: "a",
text_transform: "a",
text_underline_position: "a",
text_wrap: "a",
top: "a",
touch_action: "a",
transform: "a",
transform_box: "a",
transform_origin: "a",
transform_style: "a",
transition: "a",
transition_delay: "a",
transition_duration: "a",
transition_property: "a",
unicode_bidi: "a",
vector_effect: "a",
vertical_align: "a",
visibility: "a",
voice_balance: "a",
voice_duration: "a",
voice_family: "a",
voice_pitch: "a",
voice_range: "a",
voice_rate: "a",
voice_stress: "a",
voice_volumn: "a",
volume: "a",
white_space: "a",
widows: "a",
width: "a",
will_change: "a",
word_break: "a",
word_spacing: "a",
word_wrap: "a",
wrap_flow: "a",
wrap_through: "a",
writing_mode: "a",
z_index: "a",
}
})
}
+1 -1
View File
@@ -42,7 +42,7 @@ struct C1Props<'a> {
}
fn Child1<'a>(cx: Scope<'a, C1Props<'a>>) -> Element {
let (left, right) = cx.props.text.split_once("=").unwrap();
let (left, right) = cx.props.text.split_once('=').unwrap();
cx.render(rsx! {
div {
+26 -34
View File
@@ -18,20 +18,16 @@ fn main() {
}
fn app(cx: Scope) -> Element {
let (display_value, set_display_value) = use_state(&cx, || String::from("0"));
let val = use_state(&cx, || String::from("0"));
let input_digit = move |num: u8| {
if display_value == "0" {
set_display_value(String::new());
if val.get() == "0" {
val.set(String::new());
}
set_display_value
.make_mut()
.push_str(num.to_string().as_str());
val.make_mut().push_str(num.to_string().as_str());
};
let input_operator = move |key: &str| {
set_display_value.make_mut().push_str(key);
};
let input_operator = move |key: &str| val.make_mut().push_str(key);
cx.render(rsx!(
style { [include_str!("./assets/calculator.css")] }
@@ -54,34 +50,34 @@ fn app(cx: Scope) -> Element {
KeyCode::Num8 => input_digit(8),
KeyCode::Num9 => input_digit(9),
KeyCode::Backspace => {
if !display_value.len() != 0 {
set_display_value.make_mut().pop();
if !val.len() != 0 {
val.make_mut().pop();
}
}
_ => {}
},
div { class: "calculator-display", [display_value.to_string()] }
div { class: "calculator-display", [val.to_string()] }
div { class: "calculator-keypad",
div { class: "input-keys",
div { class: "function-keys",
button {
class: "calculator-key key-clear",
onclick: move |_| {
set_display_value(String::new());
if !display_value.is_empty(){
set_display_value("0".into());
val.set(String::new());
if !val.is_empty(){
val.set("0".into());
}
},
[if display_value.is_empty() { "C" } else { "AC" }]
[if val.is_empty() { "C" } else { "AC" }]
}
button {
class: "calculator-key key-sign",
onclick: move |_| {
let temp = calc_val(display_value.clone());
let temp = calc_val(val.as_str());
if temp > 0.0 {
set_display_value(format!("-{}", temp));
val.set(format!("-{}", temp));
} else {
set_display_value(format!("{}", temp.abs()));
val.set(format!("{}", temp.abs()));
}
},
"±"
@@ -89,8 +85,8 @@ fn app(cx: Scope) -> Element {
button {
class: "calculator-key key-percent",
onclick: move |_| {
set_display_value(
format!("{}", calc_val(display_value.clone()) / 100.0)
val.set(
format!("{}", calc_val(val.as_str()) / 100.0)
);
},
"%"
@@ -100,7 +96,7 @@ fn app(cx: Scope) -> Element {
button { class: "calculator-key key-0", onclick: move |_| input_digit(0),
"0"
}
button { class: "calculator-key key-dot", onclick: move |_| set_display_value.make_mut().push('.'),
button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'),
"●"
}
(1..10).map(|k| rsx!{
@@ -114,25 +110,21 @@ fn app(cx: Scope) -> Element {
}
}
div { class: "operator-keys",
button { class: "calculator-key key-divide",
onclick: move |_| input_operator("/"),
button { class: "calculator-key key-divide", onclick: move |_| input_operator("/"),
"÷"
}
button { class: "calculator-key key-multiply",
onclick: move |_| input_operator("*"),
button { class: "calculator-key key-multiply", onclick: move |_| input_operator("*"),
"×"
}
button { class: "calculator-key key-subtract",
onclick: move |_| input_operator("-"),
button { class: "calculator-key key-subtract", onclick: move |_| input_operator("-"),
"−"
}
button { class: "calculator-key key-add",
onclick: move |_| input_operator("+"),
button { class: "calculator-key key-add", onclick: move |_| input_operator("+"),
"+"
}
button { class: "calculator-key key-equals",
onclick: move |_| {
set_display_value(format!("{}", calc_val(display_value.clone())));
val.set(format!("{}", calc_val(val.as_str())));
},
"="
}
@@ -145,8 +137,7 @@ fn app(cx: Scope) -> Element {
))
}
fn calc_val(val: String) -> f64 {
let mut result;
fn calc_val(val: &str) -> f64 {
let mut temp = String::new();
let mut operation = "+".to_string();
@@ -169,7 +160,8 @@ fn calc_val(val: String) -> f64 {
temp_value.push(c);
start_index += 1;
}
result = temp_value.parse::<f64>().unwrap();
let mut result = temp_value.parse::<f64>().unwrap();
if start_index + 1 >= val.len() {
return result;
+3 -3
View File
@@ -36,7 +36,7 @@ pub fn NonUpdatingEvents(cx: Scope) -> Element {
cx.render(rsx! {
div {
button {
onclick: move |_| log::info!("Did not cause any updates!"),
onclick: move |_| log::trace!("Did not cause any updates!"),
"Click me to log!"
}
}
@@ -46,9 +46,9 @@ pub fn NonUpdatingEvents(cx: Scope) -> Element {
pub fn DisablePropagation(cx: Scope) -> Element {
cx.render(rsx! {
div {
onclick: move |_| log::info!("event propagated to the div!")
onclick: move |_| log::trace!("event propagated to the div!")
button {
onclick: move |evt| log::info!("Button will allow propagation"),
onclick: move |evt| log::trace!("Button will allow propagation"),
}
}
})
+23 -37
View File
@@ -6,11 +6,6 @@ use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app);
}
enum Scene {
ClientsList,
NewClientForm,
Settings,
}
#[derive(Clone, Debug, Default)]
pub struct Client {
@@ -21,10 +16,9 @@ pub struct Client {
fn app(cx: Scope) -> Element {
let clients = use_ref(&cx, || vec![] as Vec<Client>);
let (scene, set_scene) = use_state(&cx, || Scene::ClientsList);
let (firstname, set_firstname) = use_state(&cx, String::new);
let (lastname, set_lastname) = use_state(&cx, String::new);
let (description, set_description) = use_state(&cx, String::new);
let firstname = use_state(&cx, String::new);
let lastname = use_state(&cx, String::new);
let description = use_state(&cx, String::new);
cx.render(rsx!(
body {
@@ -35,11 +29,9 @@ fn app(cx: Scope) -> Element {
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
crossorigin: "anonymous",
}
h1 {"Dioxus CRM Example"}
match scene {
Scene::ClientsList => rsx!(
Router {
Route { to: "/",
div { class: "crm",
h2 { margin_bottom: "10px", "List of clients" }
div { class: "clients", margin_left: "10px",
@@ -51,11 +43,11 @@ fn app(cx: Scope) -> Element {
})
)
}
button { class: "pure-button pure-button-primary", onclick: move |_| set_scene(Scene::NewClientForm), "Add New" }
button { class: "pure-button", onclick: move |_| set_scene(Scene::Settings), "Settings" }
Link { to: "/new", class: "pure-button pure-button-primary", "Add New" }
Link { to: "/new", class: "pure-button", "Settings" }
}
),
Scene::NewClientForm => rsx!(
}
Route { to: "/new",
div { class: "crm",
h2 { margin_bottom: "10px", "Add new client" }
form { class: "pure-form",
@@ -63,41 +55,39 @@ fn app(cx: Scope) -> Element {
class: "new-client firstname",
placeholder: "First name",
value: "{firstname}",
oninput: move |e| set_firstname(e.value.clone())
oninput: move |e| firstname.set(e.value.clone())
}
input {
class: "new-client lastname",
placeholder: "Last name",
value: "{lastname}",
oninput: move |e| set_lastname(e.value.clone())
oninput: move |e| lastname.set(e.value.clone())
}
textarea {
class: "new-client description",
placeholder: "Description",
value: "{description}",
oninput: move |e| set_description(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).clone(),
first_name: (*firstname).clone(),
last_name: (*lastname).clone(),
description: description.to_string(),
first_name: firstname.to_string(),
last_name: lastname.to_string(),
});
set_description(String::new());
set_firstname(String::new());
set_lastname(String::new());
description.set(String::new());
firstname.set(String::new());
lastname.set(String::new());
},
"Add New"
}
button { class: "pure-button", onclick: move |_| set_scene(Scene::ClientsList),
"Go Back"
}
Link { to: "/", class: "pure-button", "Go Back" }
}
),
Scene::Settings => rsx!(
}
Route { to: "/settings",
div {
h2 { margin_bottom: "10px", "Settings" }
button {
@@ -106,13 +96,9 @@ fn app(cx: Scope) -> Element {
onclick: move |_| clients.write().clear(),
"Remove all clients"
}
button {
class: "pure-button pure-button-primary",
onclick: move |_| set_scene(Scene::ClientsList),
"Go Back"
}
Link { to: "/", class: "pure-button pure-button-primary", "Go Back" }
}
)
}
}
}
))
+37
View File
@@ -0,0 +1,37 @@
//! This example shows how to use a custom index.html and custom <HEAD> extensions
//! to add things like stylesheets, scripts, and third-party JS libraries.
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch_cfg(app, |c| {
c.with_custom_head("<style>body { background-color: red; }</style>".into())
});
dioxus::desktop::launch_cfg(app, |c| {
c.with_custom_index(
r#"
<!DOCTYPE html>
<html>
<head>
<title>Dioxus app</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>body { background-color: blue; }</style>
</head>
<body>
<div id="main"></div>
</body>
</html>
"#
.into(),
)
});
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
h1 {"hello world!"}
}
})
}
+5 -3
View File
@@ -5,13 +5,15 @@ fn main() {
}
fn app(cx: Scope) -> Element {
let (disabled, set_disabled) = use_state(&cx, || false);
let disabled = use_state(&cx, || false);
cx.render(rsx! {
div {
button {
onclick: move |_| set_disabled(!disabled),
"click to " [if *disabled {"enable"} else {"disable"} ] " the lower button"
onclick: move |_| disabled.set(!disabled),
"click to "
[if disabled == true {"enable"} else {"disable"}]
" the lower button"
}
button {
+21 -30
View File
@@ -16,7 +16,9 @@ struct ListBreeds {
}
fn app(cx: Scope) -> Element {
let breeds = use_future(&cx, || async move {
let breed = use_state(&cx, || None);
let breeds = use_future(&cx, (), |_| async move {
reqwest::get("https://dog.ceo/api/breeds/list/all")
.await
.unwrap()
@@ -24,26 +26,23 @@ fn app(cx: Scope) -> Element {
.await
});
let (breed, set_breed) = use_state(&cx, || None);
match breeds.value() {
Some(Ok(breeds)) => cx.render(rsx! {
div {
h1 {"Select a dog breed!"}
h1 { "Select a dog breed!" }
div { display: "flex",
ul { flex: "50%",
breeds.message.keys().map(|breed| rsx!(
breeds.message.keys().map(|cur_breed| rsx!(
li {
button {
onclick: move |_| set_breed(Some(breed.clone())),
"{breed}"
onclick: move |_| breed.set(Some(cur_breed.clone())),
"{cur_breed}"
}
}
))
}
div { flex: "50%",
match breed {
match breed.get() {
Some(breed) => rsx!( Breed { breed: breed.clone() } ),
None => rsx!("No Breed selected"),
}
@@ -51,34 +50,26 @@ fn app(cx: Scope) -> Element {
}
}
}),
Some(Err(_e)) => cx.render(rsx! {
div { "Error fetching breeds" }
}),
None => cx.render(rsx! {
div { "Loading dogs..." }
}),
Some(Err(_e)) => cx.render(rsx! { div { "Error fetching breeds" } }),
None => cx.render(rsx! { div { "Loading dogs..." } }),
}
}
#[derive(serde::Deserialize, Debug)]
struct DogApi {
message: String,
}
#[inline_props]
fn Breed(cx: Scope, breed: String) -> Element {
#[derive(serde::Deserialize, Debug)]
struct DogApi {
message: String,
}
let endpoint = format!("https://dog.ceo/api/breed/{}/images/random", breed);
let fut = use_future(&cx, || async move {
reqwest::get(endpoint).await.unwrap().json::<DogApi>().await
let fut = use_future(&cx, (breed,), |(breed,)| async move {
reqwest::get(format!("https://dog.ceo/api/breed/{}/images/random", breed))
.await
.unwrap()
.json::<DogApi>()
.await
});
let (name, set_name) = use_state(&cx, || breed.clone());
if name != breed {
set_name(breed.clone());
fut.restart();
}
cx.render(match fut.value() {
Some(Ok(resp)) => rsx! {
button {
+22
View File
@@ -0,0 +1,22 @@
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app);
}
fn app(cx: Scope) -> Element {
let val = use_state(&cx, || "0.0001");
let num = match val.parse::<f32>() {
Err(_) => return cx.render(rsx!("Parsing failed")),
Ok(num) => num,
};
cx.render(rsx! {
h1 { "The parsed value is {num}" }
button {
onclick: move |_| val.set("invalid"),
"Set an invalid number"
}
})
}
+25
View File
@@ -0,0 +1,25 @@
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app);
}
fn app(cx: Scope) -> Element {
let script = use_state(&cx, String::new);
let eval = use_eval(&cx);
cx.render(rsx! {
div {
input {
placeholder: "Enter an expression",
value: "{script}",
oninput: move |e| script.set(e.value.clone()),
}
button {
onclick: move |_| eval(script),
"Execute"
}
}
})
}
+26
View File
@@ -0,0 +1,26 @@
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
button {
ondblclick: move |_| {
//
println!("double clicked!");
},
"Click me!"
}
input {
onfocusin: move |_| {
//
println!("blurred!");
},
"onblur": "console.log('blurred!')"
}
}
})
}
+29
View File
@@ -0,0 +1,29 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app)
}
static NAME: Atom<String> = |_| "world".to_string();
fn app(cx: Scope) -> Element {
let name = use_read(&cx, NAME);
cx.render(rsx! {
div { "hello {name}!" }
Child {}
})
}
fn Child(cx: Scope) -> Element {
let set_name = use_set(&cx, NAME);
cx.render(rsx! {
button {
onclick: move |_| set_name("dioxus".to_string()),
"reset name"
}
})
}
+1 -1
View File
@@ -28,7 +28,7 @@ fn app(cx: Scope) -> Element {
}
main {
files.read().path_names.iter().enumerate().map(|(dir_id, path)| {
let path_end = path.split('/').last().unwrap_or_else(|| path.as_str());
let path_end = path.split('/').last().unwrap_or(path.as_str());
let icon_type = if path_end.contains('.') {
"description"
} else {
+1 -1
View File
@@ -12,7 +12,7 @@ fn main() {
fn app(cx: Scope) -> Element {
cx.render(rsx!(
div {
h1 { "drag an file here" }
h1 { "drag a file here and check your console" }
}
))
}
+37
View File
@@ -0,0 +1,37 @@
use dioxus::desktop::tao::dpi::LogicalSize;
use dioxus::prelude::*;
fn main() {
env_logger::init();
dioxus::desktop::launch_cfg(app, |c| {
c.with_window(|c| {
c.with_title("Spinsense Client")
.with_inner_size(LogicalSize::new(600, 1000))
.with_resizable(false)
})
})
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Router {
Route { to: "/", "Home" }
Route { to: "/games", "Games" }
Route { to: "/play", "Play" }
Route { to: "/settings", "Settings" }
p {
"----"
}
nav {
ul {
Link { to: "/", li { "Home" } }
Link { to: "/games", li { "Games" } }
Link { to: "/play", li { "Play" } }
Link { to: "/settings", li { "Settings" } }
}
}
}
})
}
+13 -9
View File
@@ -11,15 +11,19 @@ fn main() {
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
h1 { "Form" }
form {
onsubmit: move |ev| println!("Submitted {:?}", ev.values),
oninput: move |ev| println!("Input {:?}", ev.values),
input { r#type: "text", name: "username" }
input { r#type: "text", name: "full-name" }
input { r#type: "password", name: "password" }
button { "Submit the form" }
Router {
div {
h1 { "Form" }
form {
onsubmit: move |ev| println!("Submitted {:?}", ev.values),
oninput: move |ev| println!("Input {:?}", ev.values),
input { r#type: "text", name: "username" }
input { r#type: "text", name: "full-name" }
input { r#type: "password", name: "password" }
input { r#type: "radio", name: "color", value: "red" }
input { r#type: "radio", name: "color", value: "blue" }
button { r#type: "submit", value: "Submit", "Submit the form" }
}
}
}
})
+2 -2
View File
@@ -33,7 +33,7 @@ impl Label {
fn app(cx: Scope) -> Element {
let items = use_ref(&cx, Vec::new);
let (selected, set_selected) = use_state(&cx, || None);
let selected = use_state(&cx, || None);
cx.render(rsx! {
div { class: "container",
@@ -71,7 +71,7 @@ fn app(cx: Scope) -> Element {
rsx!(tr { class: "{is_in_danger}",
td { class:"col-md-1" }
td { class:"col-md-1", "{item.key}" }
td { class:"col-md-1", onclick: move |_| set_selected(Some(id)),
td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
a { class: "lbl", item.labels }
}
td { class: "col-md-1",
+28
View File
@@ -0,0 +1,28 @@
//! This example shows that you can place heavy work on the main thread, and then
//!
//! You *should* be using `tokio::spawn_blocking` instead.
//!
//! Your app runs in an async runtime (Tokio), so you should avoid blocking
//! the rendering of the VirtualDom.
//!
//!
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app);
}
fn app(cx: Scope) -> Element {
// This is discouraged
std::thread::sleep(std::time::Duration::from_millis(2_000));
// This is suggested
tokio::task::spawn_blocking(move || {
std::thread::sleep(std::time::Duration::from_millis(2_000));
});
cx.render(rsx! {
div { "Hello, world!" }
})
}
Loaded 100 of 281 files, more files were not shown because too many files have changed in this diff. Show more