diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f0d2a4b80..81b1b64cb 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -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 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 7003b3925..6ff2b1e9c 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -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: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 029e50e88..27116f17d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index cee707dde..961adee97 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -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: diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict index 06845ebf9..28e2a4e92 100644 --- a/.vscode/spellright.dict +++ b/.vscode/spellright.dict @@ -79,3 +79,7 @@ clonable oninput Webview idanarye +Katex +mrxiaozhuox +Fruitie +Vercel diff --git a/Cargo.toml b/Cargo.toml index 5e3a0a032..8291a290c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 \ No newline at end of file diff --git a/README.md b/README.md index 02857ed18..5e87587e3 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@

Dioxus

-

- Frontend that scales. -

@@ -45,37 +42,30 @@ | Examples | - Guide (0.1.8) - | - Guide (Master) - -
- -
-

- English + Guide | δΈ­ζ–‡

+
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... @@ -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 diff --git a/packages/core/benches/create.rs b/benches/create.rs similarity index 100% rename from packages/core/benches/create.rs rename to benches/create.rs diff --git a/packages/core/benches/jsframework.rs b/benches/jsframework.rs similarity index 100% rename from packages/core/benches/jsframework.rs rename to benches/jsframework.rs diff --git a/benches/tui_update.rs b/benches/tui_update.rs new file mode 100644 index 000000000..d46921a8d --- /dev/null +++ b/benches/tui_update.rs @@ -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) -> 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) -> 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, + } + } + }) +} diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..325c05ec8 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,2 @@ +comment: false +fail_ci_if_error: false diff --git a/docs/cli/.gitignore b/docs/cli/.gitignore new file mode 100644 index 000000000..7585238ef --- /dev/null +++ b/docs/cli/.gitignore @@ -0,0 +1 @@ +book diff --git a/docs/cli/book.toml b/docs/cli/book.toml new file mode 100644 index 000000000..c5c216fe4 --- /dev/null +++ b/docs/cli/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["YuKun Liu"] +language = "en" +multilingual = false +src = "src" +title = "Dioxus Cli" diff --git a/docs/cli/src/SUMMARY.md b/docs/cli/src/SUMMARY.md new file mode 100644 index 000000000..ed7bcbfaf --- /dev/null +++ b/docs/cli/src/SUMMARY.md @@ -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) \ No newline at end of file diff --git a/docs/cli/src/cmd/README.md b/docs/cli/src/cmd/README.md new file mode 100644 index 000000000..c642b475f --- /dev/null +++ b/docs/cli/src/cmd/README.md @@ -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] + +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 +``` \ No newline at end of file diff --git a/docs/cli/src/cmd/build.md b/docs/cli/src/cmd/build.md new file mode 100644 index 000000000..e81973f2d --- /dev/null +++ b/docs/cli/src/cmd/build.md @@ -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 [default: ""] + --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 +``` \ No newline at end of file diff --git a/docs/cli/src/cmd/clean.md b/docs/cli/src/cmd/clean.md new file mode 100644 index 000000000..2f889c1a8 --- /dev/null +++ b/docs/cli/src/cmd/clean.md @@ -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 +``` + diff --git a/docs/cli/src/cmd/serve.md b/docs/cli/src/cmd/serve.md new file mode 100644 index 000000000..e5c10a902 --- /dev/null +++ b/docs/cli/src/cmd/serve.md @@ -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 [default: ""] + --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 +``` \ No newline at end of file diff --git a/docs/cli/src/cmd/translate.md b/docs/cli/src/cmd/translate.md new file mode 100644 index 000000000..415a88fba --- /dev/null +++ b/docs/cli/src/cmd/translate.md @@ -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 file, stdout if not present + +OPTIONS: + -c, --component Activate debug mode + -f, --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 +
+

Hello World

+ Link +
+``` + +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" + } + } + }) +} +``` \ No newline at end of file diff --git a/docs/cli/src/configure.md b/docs/cli/src/configure.md new file mode 100644 index 000000000..f274ce70d --- /dev/null +++ b/docs/cli/src/configure.md @@ -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 `` 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 = [] +``` \ No newline at end of file diff --git a/docs/cli/src/creating.md b/docs/cli/src/creating.md new file mode 100644 index 000000000..fa725a4f7 --- /dev/null +++ b/docs/cli/src/creating.md @@ -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 +``` \ No newline at end of file diff --git a/docs/cli/src/installation.md b/docs/cli/src/installation.md new file mode 100644 index 000000000..7151de1ff --- /dev/null +++ b/docs/cli/src/installation.md @@ -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`. \ No newline at end of file diff --git a/docs/cli/src/introduction.md b/docs/cli/src/introduction.md new file mode 100644 index 000000000..2f857a2a5 --- /dev/null +++ b/docs/cli/src/introduction.md @@ -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) \ No newline at end of file diff --git a/docs/fermi/.gitignore b/docs/fermi/.gitignore new file mode 100644 index 000000000..7585238ef --- /dev/null +++ b/docs/fermi/.gitignore @@ -0,0 +1 @@ +book diff --git a/docs/fermi/book.toml b/docs/fermi/book.toml new file mode 100644 index 000000000..dd6d2eacb --- /dev/null +++ b/docs/fermi/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["Jonathan Kelley"] +language = "en" +multilingual = false +src = "src" +title = "Fermi Guide" diff --git a/docs/fermi/src/SUMMARY.md b/docs/fermi/src/SUMMARY.md new file mode 100644 index 000000000..7390c8289 --- /dev/null +++ b/docs/fermi/src/SUMMARY.md @@ -0,0 +1,3 @@ +# Summary + +- [Chapter 1](./chapter_1.md) diff --git a/docs/fermi/src/chapter_1.md b/docs/fermi/src/chapter_1.md new file mode 100644 index 000000000..b743fda35 --- /dev/null +++ b/docs/fermi/src/chapter_1.md @@ -0,0 +1 @@ +# Chapter 1 diff --git a/docs/guide/src/README.md b/docs/guide/src/README.md index bbbd9f85f..c20258066 100644 --- a/docs/guide/src/README.md +++ b/docs/guide/src/README.md @@ -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) diff --git a/docs/guide/src/ROADMAP.md b/docs/guide/src/ROADMAP.md index 4f78e6043..9d2ff542c 100644 --- a/docs/guide/src/ROADMAP.md +++ b/docs/guide/src/ROADMAP.md @@ -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 diff --git a/docs/guide/src/SUMMARY.md b/docs/guide/src/SUMMARY.md index fff21fd02..8060bbe0b 100644 --- a/docs/guide/src/SUMMARY.md +++ b/docs/guide/src/SUMMARY.md @@ -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) + - [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) + + + + + + + - [Next Steps and Advanced Topics](final.md) diff --git a/docs/guide/src/async/coroutines.md b/docs/guide/src/async/coroutines.md new file mode 100644 index 000000000..f59e9102f --- /dev/null +++ b/docs/guide/src/async/coroutines.md @@ -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| 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) { + // do stuff +} + +async fn sync_service(rx: UnboundedReceiver) { + // do stuff +} + +async fn editor_service(rx: UnboundedReceiver) { + // 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 = |_| "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, 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| { + 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::(&cx); + + sync_task.send(SyncAction::SetUsername); +} +``` diff --git a/docs/guide/src/async/fetching.md b/docs/guide/src/async/fetching.md index b31c3d03b..f4cd29a36 100644 --- a/docs/guide/src/async/fetching.md +++ b/docs/guide/src/async/fetching.md @@ -1,4 +1,2 @@ # Fetching - -This section is currently under construction! πŸ— diff --git a/docs/guide/src/async/index.md b/docs/guide/src/async/index.md index 0ec02e57d..b0abb494f 100644 --- a/docs/guide/src/async/index.md +++ b/docs/guide/src/async/index.md @@ -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 + '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! πŸ— diff --git a/docs/guide/src/async/loading_state.md b/docs/guide/src/async/loading_state.md new file mode 100644 index 000000000..66ea79ba9 --- /dev/null +++ b/docs/guide/src/async/loading_state.md @@ -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 diff --git a/docs/guide/src/async/sockets.md b/docs/guide/src/async/sockets.md new file mode 100644 index 000000000..7627a1570 --- /dev/null +++ b/docs/guide/src/async/sockets.md @@ -0,0 +1 @@ +# WebSockets diff --git a/docs/guide/src/async/use_future.md b/docs/guide/src/async/use_future.md new file mode 100644 index 000000000..0c45ddcb3 --- /dev/null +++ b/docs/guide/src/async/use_future.md @@ -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::() + .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::() + .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::() + .await + }); + + // some code as before +} +``` diff --git a/docs/guide/src/components/component_children.md b/docs/guide/src/components/component_children.md index ea9b1fc45..9fb0f8552 100644 --- a/docs/guide/src/components/component_children.md +++ b/docs/guide/src/components/component_children.md @@ -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) -> 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) -> 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) -> 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) -> 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) -> 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. + - 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 -
hello
+
hello
``` 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!{ } } ``` - diff --git a/docs/guide/src/elements/vnodes.md b/docs/guide/src/elements/vnodes.md index 4adbf962d..5312306f0 100644 --- a/docs/guide/src/elements/vnodes.md +++ b/docs/guide/src/elements/vnodes.md @@ -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!( diff --git a/docs/guide/src/hello_world.md b/docs/guide/src/hello_world.md index e1d4777d9..d01122959 100644 --- a/docs/guide/src/hello_world.md +++ b/docs/guide/src/hello_world.md @@ -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. - - diff --git a/docs/guide/src/helpers/index.md b/docs/guide/src/helpers/index.md new file mode 100644 index 000000000..0a6858446 --- /dev/null +++ b/docs/guide/src/helpers/index.md @@ -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) + + diff --git a/docs/guide/src/interactivity/event_handlers.md b/docs/guide/src/interactivity/event_handlers.md index 8fcfe6edb..89139adb4 100644 --- a/docs/guide/src/interactivity/event_handlers.md +++ b/docs/guide/src/interactivity/event_handlers.md @@ -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 +} + +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. + } +} +``` diff --git a/docs/guide/src/interactivity/hooks.md b/docs/guide/src/interactivity/hooks.md index a90afe2dc..4f298a143 100644 --- a/docs/guide/src/interactivity/hooks.md +++ b/docs/guide/src/interactivity/hooks.md @@ -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(); diff --git a/docs/guide/src/interactivity/importanthooks.md b/docs/guide/src/interactivity/importanthooks.md index 161c5becf..dcffb1c86 100644 --- a/docs/guide/src/interactivity/importanthooks.md +++ b/docs/guide/src/interactivity/importanthooks.md @@ -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` - -```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 = set_count.current(); -``` - -Or, we can get the inner handle to set the value - -```rust -let setter: Rc = 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>` (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"); - } - } -}) -``` diff --git a/docs/guide/src/interactivity/index.md b/docs/guide/src/interactivity/index.md index 6075a4a5a..02c39b5b1 100644 --- a/docs/guide/src/interactivity/index.md +++ b/docs/guide/src/interactivity/index.md @@ -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. + + diff --git a/docs/posts/release-0-2-0.md b/docs/posts/release-0-2-0.md new file mode 100644 index 000000000..79a341e91 --- /dev/null +++ b/docs/posts/release-0-2-0.md @@ -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. + +