Introduction

dioxuslogo

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.


#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    let mut count = use_state(&cx, || 0);

    cx.render(rsx!(
        h1 { "High-Five counter: {count}" }
        button { onclick: move |_| count += 1, "Up high!" }
        button { onclick: move |_| count -= 1, "Down low!" }
    ))
};
}

In general, Dioxus and React share many functional similarities. If this guide is lacking in any general concept or an error message is confusing, React's documentation might be more helpful. We are dedicated to providing a familiar toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc). If you know React, then you already know Dioxus. If you don't know either, this guide will still help you!

This is an introduction book! For advanced topics, check out the Reference instead.

Multiplatform

Dioxus is a portable toolkit, meaning the Core implementation can run anywhere with no platform-dependent linking. Unlike many other Rust frontend toolkits, Dioxus is not intrinsically linked to WebSys. In fact, every element and event listener can be swapped out at compile time. By default, Dioxus ships with the html feature enabled, but this can be disabled depending on your target renderer.

Right now, we have several 1st-party renderers:

  • WebSys (for WASM)
  • Tao/Tokio (for Desktop apps)
  • Tao/Tokio (for Mobile apps)
  • SSR (for generating static markup)
  • TUI/Rink (for terminal-based apps)

Web Support


The Web is the best-supported target platform for Dioxus. To run on the Web, your app must be compiled to WebAssembly and depend on the dioxus crate with the web feature enabled. Because of the limitations of Wasm not every crate will work with your web-apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc).

Because the web is a fairly mature platform, we expect there to be very little API churn for web-based features.

Jump to the getting started guide for the web.

Examples:

TodoMVC example

SSR Support


Dioxus supports server-side rendering!

For rendering statically to an .html file or from a WebServer, then you'll want to make sure the ssr feature is enabled in the dioxus crate and use the dioxus::ssr API. We don't expect the SSR API to change drastically in the future.


#![allow(unused)]
fn main() {
let contents = dioxus::ssr::render_vdom(&dom);
}

Jump to the getting started guide for SSR.

Examples:

Desktop Support


The desktop is a powerful target for Dioxus, but is currently limited in capability when compared to the Web platform. Currently, desktop apps are rendered with the platform's WebView library, but your Rust code is running natively on a native thread. This means that browser APIs are not available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs are accessible, so streaming, WebSockets, filesystem, etc are all viable APIs. In the future, we plan to move to a custom webrenderer-based DOM renderer with WGPU integrations.

Desktop APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart.

Jump to the getting started guide for Desktop.

Examples:

File ExplorerExample

Mobile Support


Mobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with the platform's WebView, meaning that animations, transparency, and native widgets are not currently achievable. In addition, iOS is the only supported Mobile Platform. It is possible to get Dioxus running on Android and rendered with WebView, but the Rust windowing library that Dioxus uses - tao - does not currently supported Android.

Mobile support is currently best suited for CRUD-style apps, ideally for internal teams who need to develop quickly but don't care much about animations or native widgets.

Jump to the getting started guide for Mobile.

Examples:

LiveView / Server Component Support


The internal architecture of Dioxus was designed from day one to support the LiveView use-case, where a web server hosts a running app for each connected user. As of today, there is no first-class LiveView support - you'll need to wire this up yourself.

While not currently fully implemented, the expectation is that LiveView apps can be a hybrid between Wasm and server-rendered where only portions of a page are "live" and the rest of the page is either server-rendered, statically generated, or handled by the host SPA.

Multithreaded Support


The Dioxus VirtualDom, sadly, is not currently Send. Internally, we use quite a bit of interior mutability which is not thread-safe. This means you can't easily use Dioxus with most web frameworks like Tide, Rocket, Axum, etc.

To solve this, you'll want to spawn a VirtualDom on its own thread and communicate with it via channels.

When working with web frameworks that require Send, it is possible to render a VirtualDom immediately to a String - but you cannot hold the VirtualDom across an await point. For retained-state SSR (essentially LiveView), you'll need to create a pool of VirtualDoms.

Ultimately, you can always wrap the VirtualDom with a Send type and manually uphold the Send guarantees yourself.

Roadmap & Feature-set

Before we dive into Dioxus, feel free to take a look at our feature set and roadmap to see if what Dioxus can do today works for you.

If a feature that you need doesn't exist or you want to contribute to projects on the roadmap, feel free to get involved by joining the discord.

Generally, here's the status of each platform:

  • Web: Dioxus is a great choice for pure web-apps - especially for CRUD/complex apps. However, it does lack the ecosystem of React, so you might be missing a component library or some useful hook.

  • SSR: Dioxus is a great choice for pre-rendering, hydration, and rendering HTML on a web endpoint. Be warned - the VirtualDom is not (currently) Send + Sync.

  • Desktop: You can build very competent single-window desktop apps right now. However, multi-window apps require support from Dioxus core and are not ready.

  • Mobile: Mobile support is very young. You'll be figuring things out as you go and there are not many support crates for peripherals.

  • LiveView: LiveView support is very young. You'll be figuring things out as you go. Thankfully, none of it is too hard and any work can be upstreamed into Dioxus.

Features


FeatureStatusDescription
Conditional Renderingif/then to hide/show component
Map, Iteratormap/filter/reduce to produce rsx!
Keyed Componentsadvanced diffing with keys
Webrenderer for web browser
Desktop (webview)renderer for desktop
Shared State (Context)share state through the tree
Hooksmemory cells in components
SSRrender directly to string
Component Childrencx.children() as a list of nodes
Headless componentscomponents that don't return real elements
Fragmentsmultiple elements without a real root
Manual PropsManually pass in props with spread syntax
Controlled Inputsstateful wrappers around inputs
CSS/Inline Stylessyntax for inline styles/attribute groups
Custom elementsDefine new element primitives
Suspenseschedule future render from future/promise
Integrated error handlingGracefully handle errors with ? syntax
NodeRefgain direct access to nodes
Re-hydrationPre-render to HTML to speed up first contentful paint
Jank-Free RenderingLarge diffs are segmented across frames for silky-smooth transitions
EffectsRun effects after a component has been committed to render
Portals🛠Render nodes outside of the traditional tree structure
Cooperative Scheduling🛠Prioritize important events over non-important events
Server Components🛠Hybrid components for SPA and Server
Bundle Splitting👀Efficiently and asynchronously load the app
Lazy Components👀Dynamically load the new components as the page is loaded
1st class global stateredux/recoil/mobx on top of context
Runs nativelyruns as a portable binary w/o a runtime (Node)
Subtree Memoizationskip diffing static element subtrees
High-efficiency templates🛠rsx! calls are translated to templates on the DOM's side
Compile-time correctThrow errors on invalid template layouts
Heuristic Enginetrack component memory usage to minimize future allocations
Fine-grained reactivity👀Skip diffing for fine-grain updates
  • ✅ = implemented and working
  • 🛠 = actively being worked on
  • 👀 = not yet implemented or being worked on
  • ❓ = not sure if will or can implement

Roadmap

These Features are planned for the future of Dioxus:


Core

  • Release of Dioxus Core
  • Upgrade documentation to include more theory and be more comprehensive
  • Support for HTML-side templates for lightning-fast dom manipulation
  • Support for multiple renderers for same virtualdom (subtrees)
  • Support for ThreadSafe (Send + Sync)
  • Support for Portals

SSR

  • SSR Support + Hydration
  • Integrated suspense support for SSR

Desktop

  • Declarative window management
  • Templates for building/bundling
  • Fully native renderer
  • Access to Canvas/WebGL context natively

Mobile

  • Mobile standard library
    • GPS
    • Camera
    • filesystem
    • Biometrics
    • WiFi
    • Bluetooth
    • Notifications
    • Clipboard
  • Animations
  • Native Renderer

Bundling (CLI)

  • 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

Essential hooks

  • Router
  • Global state management
  • Resize observer

Overview

In this chapter, we're going to get set up with a small desktop application.

We'll learn about:

  • Installing the Rust programming language
  • Installing the Dioxus CLI for bundling and developing
  • Suggested cargo extensions

For platform-specific guides, check out the Platform Specific Guides.

Setting up Dioxus

Dioxus requires a few main things to get up and running:

Dioxus integrates very well with the Rust-Analyzer IDE plugin which will provide appropriate syntax highlighting, code navigation, folding, and more.

Installing Rust

Head over to https://rust-lang.org and install the Rust compiler.

Once installed, make sure to install wasm32-unknown-unknown as a target if you're planning on deploying your app to the web.

rustup target add wasm32-unknown-unknown

Platform-Specific Dependencies

If you are running a modern, mainstream operating system, you should need no additional setup to build WebView-based Desktop apps. However, if you are running an older version of Windows or a flavor of Linux with no default web rendering engine, you might need to install some additional dependencies.

Windows

Windows Desktop apps depend on WebView2 - a library which should be installed in all modern Windows distributions. If you have Edge installed, then Dioxus will work fine. If you don't have Webview2, then you can install it through Microsoft. MS provides 3 options:

  1. A tiny "evergreen" bootstrapper which will fetch an installer from Microsoft's CDN
  2. A tiny installer which will fetch Webview2 from Microsoft's CDN
  3. A statically linked version of Webview2 in your final binary for offline users

For development purposes, use Option 1.

Linux

Webview Linux apps require WebkitGtk. When distributing, this can be part of your dependency tree in your .rpm or .deb. However, it's very likely that your users will already have WebkitGtk.

sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev

When using Debian/bullseye libappindicator3-dev is no longer available but replaced by libayatana-appindicator3-dev.

# on Debian/bullseye use:
sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatanta-appindicator3-dev

If you run into issues, make sure you have all the basics installed, as outlined in the Tauri docs.

macOS

Currently - everything for macOS is built right in! However, you might run into an issue if you're using nightly Rust due to some permissions issues in our Tao dependency (which have been resolved but not published).

Dioxus-CLI for dev server, bundling, etc.

We also recommend installing the Dioxus CLI. The Dioxus CLI automates building and packaging for various targets and integrates with simulators, development servers, and app deployment. To install the CLI, you'll need cargo (which should be automatically installed with Rust):

$ cargo install dioxus-cli

You can update dioxus-cli at any time with:

$ cargo install --force dioxus-cli

We provide this 1st-party tool to save you from having to run potentially untrusted code every time you add a crate to your project - as is standard in the NPM ecosystem.

Suggested extensions

If you want to keep your traditional npm install XXX workflow for adding packages, you might want to install cargo-edit and a few other fun cargo extensions:

  • cargo edit for adding dependencies from the CLI
  • cargo-expand for expanding macro calls
  • cargo tree - an integrated cargo command that lets you inspect your dependency tree

That's it! We won't need to touch NPM/WebPack/Babel/Parcel, etc. However, you can configure your app to use WebPack with traditional WASM-pack tooling.

Rust Knowledge

With Rust, things like benchmarking, testing, and documentation are included in the language. We strongly recommend going through the official Rust book completely. However, our hope is that a Dioxus app can serve as a great first Rust project. With Dioxus you'll learn about:

  • Error handling
  • Structs, Functions, Enums
  • Closures
  • Macros

We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need deep knowledge on async, lifetimes, or smart pointers until you really start building complex Dioxus apps.

We strongly encourage exploring the guides for more information on how to work with the integrated tooling:

"Hello, World" desktop app

Let's put together a simple "hello world" desktop application to get acquainted with Dioxus.

In this chapter, we'll cover:

  • Starting a new Dioxus project with Cargo
  • Adding Dioxus as a dependency
  • Launching our first component as the app root

A new project with Cargo

First, let's start a new project. Rust has the concept of executables and libraries. Executables have a main.rs and libraries have lib.rs. A project may have both. Our hello world will be an executable - we expect our app to launch when we run it! Cargo provides this for us:

$ cargo new --bin hello-dioxus

Now, we can cd into our project and poke around:

$ cd hello-dioxus
$ tree
.
├── Cargo.toml
├── .git
├── .gitignore
└── src
    └── main.rs

We are greeted with a pre-initialized git repository, our code folder (src) and our project file (Cargo.toml).

Our src folder holds our code. Our main.rs file holds our fn main which will be executed when our app is run.

$ more src/main.rs
fn main() {
    println!("Hello, world!");
}

Right now, whenever our app is launched, "Hello world" will be echoed to the terminal.

$ cargo run
   Compiling hello-dioxus v0.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 0.41s
     Running `target/debug/hello-dioxus`
Hello, world!

Our Cargo.toml file holds our dependencies and project flags.

$ cat Cargo.toml
[package]
name = "hello-dioxus"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

Adding Dioxus as a dependency

To use the Dioxus library, we'll want to add the most recent version of Dioxus to our crate. If you have cargo edit installed, simply call:

$ 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:

$ # On Debian/bullseye use:
$ cargo add dioxus --features desktop --features ayatana

If you plan to develop extensions for the Dioxus ecosystem, please use the dioxus crate with the core feature to limit the amount of dependencies your project brings in.

Our first app

Now, let's edit our main.rs file:

use dioxus::prelude::*;


fn main() {
    dioxus::desktop::launch(app);
}

fn app(cx: Scope) -> Element {
    cx.render(rsx! (
        div { "Hello, world!" }
    ))
}

At this point, you could call cargo run and be greeted with a simple Hello, World! screen:

hello world

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.


#![allow(unused)]
fn main() {
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.

fn main() {
    dioxus::desktop::launch(app);
}

Finally, our app. Every component in Dioxus is a function that takes in Context and Props and returns an Element.


#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    cx.render(rsx! {
        div { "Hello, world!" }
    })
}
}

What is this Scope object?

Coming from React, the Scope object might be confusing. In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering.

In Dioxus, you are given an explicit Scope object to control how the component renders and stores data. The Scope object provides a handful of useful APIs for features like suspense, rendering, and more.

For now, just know that Scope lets you store state with hooks and render elements with cx.render.

Moving on

Congrats! You've built your first desktop application with Dioxus. Next, we're going to learn about the basics of building interactive user interfaces.

Core Topics

In this chapter, we'll cover some core topics about how Dioxus works and how to best leverage the features to build a beautiful, reactive app.

At a very high level, Dioxus is simply a Rust framework for declaring user interfaces and reacting to changes.

  1. We declare what we want our user interface to look like given a state using Rust-based logic and control flow.
  2. We declare how we want our state to change when the user triggers an event.

Declarative UI

Dioxus is a declarative framework. This means that instead of manually writing calls to "create element" and "set element background to red," we simply declare what we want the element to look like and let Dioxus handle the differences.

Let's pretend that we have a stoplight we need to control - it has a color state with red, yellow, and green as options.

Using an imperative approach, we would have to manually declare each element and then handlers for advancing the stoplight.


#![allow(unused)]
fn main() {
let container = Container::new();

let green_light = Light::new().color("green").enabled(true);
let yellow_light = Light::new().color("yellow").enabled(false);
let red_light = Light::new().color("red").enabled(false);
container.push(green_light);
container.push(yellow_light);
container.push(red_light);

container.set_onclick(move |_| {
    if red_light.enabled() {
        red_light.set_enabled(false);
        green_light.set_enabled(true);
    } else if yellow_light.enabled() {
        yellow_light.set_enabled(false);
        red_light.set_enabled(true);
    } else if green_light.enabled() {
        green_light.set_enabled(false);
        yellow_light.set_enabled(true);
    }
});
}

As the UI grows in scale, our logic to keep each element in the proper state would grow exponentially. This can become very unwieldy and lead to out-of-sync UIs that harm user experience.

Instead, with Dioxus, we declare what we want our UI to look like:


#![allow(unused)]
fn main() {
let mut state = use_state(&cx, || "red");

cx.render(rsx!(
    Container {
        Light { color: "red", enabled: state == "red", }
        Light { color: "yellow", enabled: state == "yellow", }
        Light { color: "green", enabled: state == "green", }

        onclick: move |_| {
            state.set(match *state {
                "green" => "yellow",
                "yellow" => "red",
                "red" => "green",
            })
        }
    }
))
}

Remember: this concept is not new! Many frameworks are declarative - with React being the most popular. Declarative frameworks tend to be much more enjoyable to work with than imperative frameworks.

Here's some reading about declaring UI in React:

Declaring your first UI with Elements

Every user interface you've ever used is just a symphony of tiny widgets working together to abstract over larger complex functions. In Dioxus, we call these tiny widgets "Elements." Using Components, you can easily compose Elements into larger groups to form even larger structures: Apps.

In this chapter, we'll cover:

  • Declaring our first Element
  • Composing Elements together
  • Element properties

Declaring our first Element

Because Dioxus is mostly used with HTML/CSS renderers, the default Element "collection" is HTML. Provided the html feature is not disabled, we can declare Elements using the rsx! macro:


#![allow(unused)]
fn main() {
rsx!(
    div {}
)
}

As you might expect, we can render this call using Dioxus-SSR to produce valid HTML:


#![allow(unused)]
fn main() {
dioxus::ssr::render_lazy(rsx!(
    div {}
))
}

Produces:

<div></div>

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.


#![allow(unused)]
fn main() {
use dioxus::prelude::*;
rsx!(
    div {
        h1 {}
        h2 {}
        p {}
    }
)
}

As you might expect, the generated HTML for this structure would look like:

<div>
    <h1></h1>
    <h2></h2>
    <p></p>
</div>

With the default configuration, any Element defined within the dioxus-html crate can be declared in this way. To create your own new elements, see the Custom Elements Advanced Guide.

Text Elements

Dioxus also supports a special type of Element: Text. Text Elements do not accept children, just a string literal denoted by double quotes.


#![allow(unused)]
fn main() {
rsx! (
    "hello world"
)
}

Text Elements can be composed within other Elements:


#![allow(unused)]
fn main() {
rsx! (
    div {
        h1 { "hello world" }
        p { "Some body content" }
    }
)
}

Text can also be formatted with any value that implements Display. We use the same syntax as Rust format strings – which will already be familiar for Python and JavaScript users:


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
rsx!( [format_args!("Hello {}", if enabled { "Jack" } else { "Bob" } )] )
}

Alternatively, &str can be included directly, though it must also be inside square braces:


#![allow(unused)]
fn main() {
rsx!( "Hello ",  [if enabled { "Jack" } else { "Bob" }] )
}

This is different from React's way of generating arbitrary markup but fits within idiomatic Rust.

Typically, with Dioxus, you'll just want to compute your substrings outside of the rsx! call and leverage the f-string formatting:


#![allow(unused)]
fn main() {
let name = if enabled { "Jack" } else { "Bob" };
rsx! ( "hello {name}" )
}

Attributes

Every Element in your user interface will have some sort of properties that the renderer will use when drawing to the screen. These might inform the renderer if the component should be hidden, what its background color should be, or to give it a specific name or ID.

To do this, we use the familiar struct-style syntax that Rust provides:


#![allow(unused)]
fn main() {
rsx!(
    div {
        hidden: "true",
        background_color: "blue",
        class: "card color-{mycolor}"
    }
)
}

Each field is defined as a method on the element in the dioxus-html crate. This prevents you from misspelling a field name and lets us provide inline documentation. When you need to use a field not defined as a method, you have two options:

  1. file an issue if the attribute should be enabled
  2. add a custom attribute on-the-fly

To use custom attributes, simply put the attribute name in quotes:


#![allow(unused)]
fn main() {
rsx!(
    div {
        "customAttr": "important data here"
    }
)
}

Note: the name of the custom attribute must match exactly what you want the renderer to output. All attributes defined as methods in dioxus-html follow the snake_case naming convention. However, they internally translate their snake_case convention to HTML's camelCase convention. When using custom attributes, make sure the name of the attribute exactly matches what the renderer is expecting.

All element attributes must occur before child elements. The rsx! macro will throw an error if your child elements come before any of your attributes. If you don't see the error, try editing your Rust-Analyzer IDE setting to ignore macro-errors. This is a temporary workaround because Rust-Analyzer currently throws two errors instead of just the one we care about.


#![allow(unused)]
fn main() {
// settings.json
{
  "rust-analyzer.diagnostics.disabled": [
    "macro-error"
  ],
}
}

Listeners

Listeners are a special type of Attribute that only accept functions. Listeners let us attach functionality to our Elements by running a provided closure whenever the specified Listener is triggered.

We'll cover listeners in more depth in the Listeners chapter, but for now, just know that every listener must start with the on keyword and accepts closures.


#![allow(unused)]
fn main() {
rsx!(
    div {
        onclick: move |_| log::debug!("div clicked!"),
    }
)
}

Moving On

This chapter just scratches the surface on how Elements can be defined.

We learned:

  • Elements are the basic building blocks of User Interfaces
  • Elements can contain other elements
  • Elements can either be a named container or text
  • Some Elements have properties that the renderer can use to draw the UI to the screen

Next, we'll compose Elements together using Rust-based logic.

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.

In this chapter, you'll learn:

  • How to return different Elements depending on a condition
  • How to conditionally include an Element in your structure
  • Common patterns like matching and bool mapping

Conditionally returning Elements

In some components, you might want to render different markup given some condition. The typical example for conditional rendering is showing a "Log In" screen for users who aren't logged into your app. To break down this condition, we can consider two states:

  • Logged in: show the app
  • Logged out: show the login screen

Using the knowledge from the previous section on components, we'll start by making the app's props:


#![allow(unused)]
fn main() {
#[derive(Props, PartialEq)]
struct AppProps {
    logged_in: bool
}
}

Now that we have a "logged_in" flag accessible in our props, we can render two different screens:


#![allow(unused)]
fn main() {
fn App(cx: Scope<AppProps>) -> Element {
    if cx.props.logged_in {
        cx.render(rsx!{
            DashboardScreen {}
        })
    } else {
        cx.render(rsx!{
            LoginScreen {}
        })
    }
}
}

When the user is logged in, then this component will return the DashboardScreen. If they're not logged in, the component will render the LoginScreen.

Using match statements

Rust provides us algebraic datatypes: enums that can contain values. Using the match keyword, we can execute different branches of code given a condition.

For instance, we could run a function that returns a Result:


#![allow(unused)]
fn main() {
fn App(cx: Scope)-> Element {
    match get_name() {
        Ok(name) => cx.render(rsx!( "Hello, {name}!" )),
        Err(err) => cx.render(rsx!( "Sorry, I don't know your name, because an error occurred: {err}" )),
    }
}
}

We can even match against values:


#![allow(unused)]
fn main() {
fn App(cx: Scope)-> Element {
    match get_name() {
        "jack" => cx.render(rsx!( "Hey Jack, how's Diane?" )),
        "diane" => cx.render(rsx!( "Hey Diane, how's Jack?" )),
        name => cx.render(rsx!( "Hello, {name}!" )),
    }
}
}

Do note: the rsx! macro does not return an Element, but rather a wrapper struct for a Closure (an anonymous function). To turn our rsx! into an Element, we need to call cx.render.

To make patterns like these less verbose, the rsx! macro accepts an optional first argument on which it will call render. Our previous component can be shortened with this alternative syntax:


#![allow(unused)]
fn main() {
fn App(cx: Scope)-> Element {
    match get_name() {
        "jack" => rsx!(cx, "Hey Jack, how's Diane?" ),
        "diane" => rsx!(cx, "Hey Diana, how's Jack?" ),
        name => rsx!(cx, "Hello, {name}!" ),
    }
}
}

Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to cx.render:


#![allow(unused)]
fn main() {
fn App(cx: Scope)-> Element {
    let greeting = match get_name() {
        "jack" => rsx!("Hey Jack, how's Diane?" ),
        "diane" => rsx!("Hey Diana, how's Jack?" ),
        name => rsx!("Hello, {name}!" ),
    };
    cx.render(greeting)
}
}

Nesting RSX

By looking at other examples, you might have noticed that it's possible to include rsx! calls inside other rsx! calls. We can include anything in our rsx! that implements IntoVnodeList: a marker trait for iterators that produce Elements. rsx! itself implements this trait, so we can include it directly:


#![allow(unused)]
fn main() {
rsx!(
    div {
        rsx!(
            "more rsx!"
        )
    }
)
}

As you might expect, we can refactor this structure into two separate calls using variables:


#![allow(unused)]
fn main() {
let title = rsx!( "more rsx!" );

rsx!(
    div {
        title
    }
)
}

In the case of a log-in screen, we might want to display the same NavBar and Footer for both logged in and logged out users. We can model this entirely by assigning a screen variable to a different Element depending on a condition:


#![allow(unused)]
fn main() {
let screen = match logged_in {
    true => rsx!(DashboardScreen {}),
    false => rsx!(LoginScreen {})
};

cx.render(rsx!{
    Navbar {}
    screen,
    Footer {}
})
}

Rendering Nothing

Sometimes, you don't want your component to return anything at all. Under the hood, the Element type is just an alias for Option<VNode>, so you can simply return None.

This can be helpful in certain patterns where you need to perform some logical side-effects but don't want to render anything.


#![allow(unused)]
fn main() {
fn demo(cx: Scope) -> Element {
    None
}
}

Boolean Mapping

In the spirit of highly-functional apps, we suggest using the "boolean mapping" pattern when trying to conditionally hide/show an Element.

By default, Rust lets you convert any boolean into an Option of any other type with then(). We can use this in Components by mapping to some Element.


#![allow(unused)]
fn main() {
let show_title = true;
rsx!(
    div {
        // Renders nothing by returning None when show_title is false
        show_title.then(|| rsx!{
            "This is the title"
        })
    }
)
}

We can use this pattern for many things, including options:


#![allow(unused)]
fn main() {
let user_name = Some("bob");
rsx!(
    div {
        // Renders nothing if user_name is None
        user_name.map(|name| rsx!("Hello {name}"))
    }
)
}

Moving Forward:

In this chapter, we learned how to render different Elements from a Component depending on a condition. This is a very powerful building block to assemble complex user interfaces!

In the next chapter, we'll cover how to renderer lists inside your rsx!.

Conditional Lists and Keys

You will often want to display multiple similar components from a collection of data.

In this chapter, you will learn:

  • How to use iterators in rsx!
  • How to filter and transform data into a list of Elements
  • How to create efficient lists with keys

Rendering data from lists

If we wanted to build the Reddit app, then we need to implement a list of data that needs to be rendered: the list of posts. This list of posts is always changing, so we cannot just hardcode the lists into our app directly, like so:


#![allow(unused)]
fn main() {
// we shouldn't ship our app with posts that don't update!
rsx!(
    div {
        Post {
            title: "Post A",
            votes: 120,
        }
        Post {
            title: "Post B",
            votes: 14,
        }
        Post {
            title: "Post C",
            votes: 999,
        }
    }
)
}

Instead, we need to transform the list of data into a list of Elements.

For convenience, rsx! supports any type in curly braces that implements the IntoVnodeList trait. Conveniently, every iterator that returns something that can be rendered as an Element also implements IntoVnodeList.

As a simple example, let's render a list of names. First, start with our input data:


#![allow(unused)]
fn main() {
let names = ["jim", "bob", "jane", "doe"];
}

Then, we create a new iterator by calling iter and then map. In our map function, we'll render our template.


#![allow(unused)]
fn main() {
let name_list = names.iter().map(|name| rsx!(
    li { "{name}" }
));
}

We can include this list in the final Element:


#![allow(unused)]
fn main() {
rsx!(
    ul {
        name_list
    }
)
}

Rather than storing name_list in a temporary variable, we could also include the iterator inline:


#![allow(unused)]
fn main() {
rsx!(
    ul {
        names.iter().map(|name| rsx!(
            li { "{name}" } 
        ))
    }
)
}

The rendered HTML list is what you would expect:

<ul>
    <li> jim </li>
    <li> bob </li>
    <li> jane </li>
    <li> doe </li>
</ul>

Filtering Iterators

Rust's iterators are extremely powerful, especially when used for filtering tasks. When building user interfaces, you might want to display a list of items filtered by some arbitrary check.

As a very simple example, let's set up a filter where we only list names that begin with the letter "j".

Using the list from above, let's create a new iterator. Before we render the list with map as in the previous example, we'll filter the names to only allow those that start with "j".


#![allow(unused)]
fn main() {
let name_list = names
    .iter()
    .filter(|name| name.starts_with('j'))
    .map(|name| rsx!( li { "{name}" }));
}

Rust's Iterators are very versatile – check out their documentation for more things you can do with them!

For keen Rustaceans: notice how we don't actually call collect on the name list. If we collected our filtered list into new Vec, we would need to make an allocation to store these new elements, which slows down rendering. Instead, we create an entirely new lazy iterator which Dioxus will consume in the render call. The render method is extraordinarily efficient, so it's best practice to let it do most of the allocations for us.

Keeping list items in order with key

The examples above demonstrate the power of iterators in rsx! but all share the same issue: if your array items move (e.g. due to sorting), get inserted, or get deleted, Dioxus has no way of knowing what happened. This can cause Elements to be unnecessarily removed, changed and rebuilt when all that was needed was to change their position – this is inneficient.

To solve this problem, each item in the list must be uniquely identifiable. You can achieve this by giving it a unique, fixed "key". In Dioxus, a key is a string that identifies an item among others in the list.


#![allow(unused)]
fn main() {
rsx!( li { key: "a" } )
}

Now, if an item has already been rendered once, Dioxus can use the key to match it up later to make the correct updates – and avoid unnecessary work.

NB: the language from this section is strongly borrowed from React's guide on keys.

Where to get your key

Different sources of data provide different sources of keys:

  • Data from a database: If your data is coming from a database, you can use the database keys/IDs, which are unique by nature.
  • Locally generated data: If your data is generated and persisted locally (e.g. notes in a note-taking app), keep track of keys along with your data. You can use an incrementing counter or a package like uuid to generate keys for new items – but make sure they stay the same for the item's lifetime.

Remember: keys let Dioxus uniquely identify an item among its siblings. A well-chosen key provides more information than the position within the array. Even if the position changes due to reordering, the key lets Dioxus identify the item throughout its lifetime.

Rules of keys

  • Keys must be unique among siblings. However, it’s okay to use the same keys for Elements in different arrays.
  • An item's key must not change – don’t generate them on the fly while rendering. Otherwise, Dioxus will be unable to keep track of which item is which, and we're back to square one.

You might be tempted to use an item's index in the array as its key. In fact, that’s what Dioxus will use if you don’t specify a key at all. This is only acceptable if you can guarantee that the list is constant – i.e., no re-ordering, additions or deletions. In all other cases, do not use the index for the key – it will lead to the performance problems described above.

Note that if you pass the key to a custom component you've made, it won't receive the key as a prop. It’s only used as a hint by Dioxus itself. If your component needs an ID, you have to pass it as a separate prop:


#![allow(unused)]
fn main() {
Post { key: "{key}", id: "{key}" }
}

Moving on

In this section, we learned:

  • How to render lists of data
  • How to use iterator tools to filter and transform data
  • How to use keys to render lists efficiently

Moving forward, we'll learn more about attributes.

Special Attributes

Dioxus tries its hardest to stay close to React, but there are some divergences and "special behavior" that you should review before moving on.

In this section, we'll cover special attributes built into Dioxus:

  • dangerous_inner_html
  • Boolean attributes
  • prevent_default
  • event handlers as string attributes
  • value, checked, and selected

The HTML escape hatch: dangerous_inner_html

One thing you might've missed from React is the ability to render raw HTML directly to the DOM. If you're working with pre-rendered assets, output from templates, or output from a JS library, then you might want to pass HTML directly instead of going through Dioxus. In these instances, reach for dangerous_inner_html.

For example, shipping a markdown-to-Dioxus converter might significantly bloat your final application size. Instead, you'll want to pre-render your markdown to HTML and then include the HTML directly in your output. We use this approach for the Dioxus homepage:


#![allow(unused)]
fn main() {
fn BlogPost(cx: Scope) -> Element {
    let contents = include_str!("../post.html");
    cx.render(rsx!{
        div {
            class: "markdown",
            dangerous_inner_html: "{contents}",
        }
    })
}
}

Note! This attribute is called "dangerous_inner_html" because it is dangerous to pass it data you don't trust. If you're not careful, you can easily expose cross-site-scripting (XSS) attacks to your users.

If you're handling untrusted input, make sure to sanitize your HTML before passing it into dangerous_inner_html – or just pass it to a Text Element to escape any HTML tags.

Boolean Attributes

Most attributes, when rendered, will be rendered exactly as the input you provided. However, some attributes are considered "boolean" attributes and just their presence determines whether or not they affect the output. For these attributes, a provided value of "false" will cause them to be removed from the target element.

So this RSX:


#![allow(unused)]
fn main() {
rsx!{
    div {
        hidden: "false",
        "hello"
    }
}
}

wouldn't actually render the hidden attribute:

<div>hello</div> 

Not all attributes work like this however. Only the following attributes have this behavior:

  • allowfullscreen
  • allowpaymentrequest
  • async
  • autofocus
  • autoplay
  • checked
  • controls
  • default
  • defer
  • disabled
  • formnovalidate
  • hidden
  • ismap
  • itemscope
  • loop
  • multiple
  • muted
  • nomodule
  • novalidate
  • open
  • playsinline
  • readonly
  • required
  • reversed
  • selected
  • truespeed

For any other attributes, a value of "false" will be sent directly to the DOM.

Stopping form input and navigation with prevent_default

Currently, calling prevent_default on events in EventHandlers is not possible from Desktop/Mobile. Until this is supported, it's possible to prevent default using the prevent_default attribute.

Note: you cannot conditionally prevent default with this approach. This is a limitation until synchronous event handling is available across the Webview boundary

To use prevent_default, simply attach the prevent_default attribute to a given element and set it to the name of the event handler you want to prevent default on. We can attach this attribute multiple times for multiple attributes.


#![allow(unused)]
fn main() {
rsx!{
    input {
        oninput: move |_| {},
        prevent_default: "oninput",

        onclick: move |_| {},
        prevent_default: "onclick",
    }
}
}

Controlled inputs and value, checked, and selected

In Dioxus, there is a distinction between controlled and uncontrolled inputs. Most inputs you'll use are controlled, meaning we both drive the value of the input and react to the oninput.

Controlled components:


#![allow(unused)]
fn main() {
let value = use_state(&cx, || String::from("hello world"));

rsx! {
    input {
        oninput: move |evt| value.set(evt.value.clone()),
        value: "{value}",
    }
}
}

With uncontrolled inputs, we won't actually drive the value from the component. This has its advantages when we don't want to re-render the component when the user inputs a value. We could either select the element directly - something Dioxus doesn't support across platforms - or we could handle oninput and modify a value without causing an update:


#![allow(unused)]
fn main() {
let value = use_ref(&cx, || String::from("hello world"));

rsx! {
    input {
        oninput: move |evt| *value.write_silent() = evt.value.clone(),
        // no "value" is driven here – the input keeps track of its own value, and you can't change it
    }
}
}

Strings for handlers like onclick

For element fields that take a handler like onclick or oninput, Dioxus will let you attach a closure. Alternatively, you can also pass a string using normal attribute syntax and assign this attribute on the DOM.

This lets you use JavaScript (only if your renderer can execute JavaScript).


#![allow(unused)]
fn main() {
rsx!{
    div {
        // handle oninput with rust
        oninput: move |_| {},

        // or handle oninput with javascript
        oninput: "alert('hello world')",
    }
}

}

Wrapping up

In this chapter, we learned:

  • How to declare elements
  • How to conditionally render parts of your UI
  • How to render lists
  • Which attributes are "special"

Introduction to Components

In the previous chapter, we learned about Elements and how they can be composed to create a basic user interface. Now, we'll learn how to group Elements together to form Components. We'll cover:

  • What makes a Component
  • How to model a component and its properties in Dioxus
  • How to "think declaratively"

What is a component?

In short, a component is a special function that takes input properties and outputs an Element. Much like a function encapsulates some specific computation task, a Component encapsulates some specific rendering task – typically, rendering an isolated part of the user interface.

Real-world example

Let's use a Reddit post as an example:

Reddit Post

If we look at the layout of the component, we notice quite a few buttons and pieces of functionality:

  • Upvote/Downvote
  • View comments
  • Share
  • Save
  • Hide
  • Give award
  • Report
  • Crosspost
  • Filter by site
  • View article
  • Visit user

If we included all this functionality in one rsx! call it would be huge! Instead, let's break the post down into Components:

Post as Component

  • VoteButton: Upvote/Downvote
  • TitleCard: Title, Filter-By-Url
  • MetaCard: Original Poster, Time Submitted
  • ActionCard: View comments, Share, Save, Hide, Give award, Report, Crosspost

In this chapter, we'll learn how to define these components.

Component Properties

Dioxus components are functions that accept Props as input and output an Element. In fact, the App function you saw in the previous chapter was a component with no Props! Most components, however, will need to take some Props to render something useful – so, in this section, we'll learn about props:

  • Deriving the Props trait
  • Memoization through PartialEq
  • Optional fields on props
  • The inline_props macro

Props

The input of your Component must be passed in a single struct, which must implement the Props trait. We can derive this trait automatically with #[derive(Props)].

Dioxus Props is very similar to @idanarye's TypedBuilder crate and supports many of the same parameters.

There are 2 flavors of Props: owned and borrowed.

  • All Owned Props must implement PartialEq
  • Borrowed props borrow values from the parent Component

Owned Props

Owned Props are very simple – they don't borrow anything. Example:


#![allow(unused)]
fn main() {
// Remember: owned props must implement PartialEq!
#[derive(PartialEq, Props)]
struct VoteButtonProps {
    score: i32
}

fn VoteButton(cx: Scope<VoteButtonProps>) -> Element {
    cx.render(rsx!{
        div {
            div { "+" }
            div { "{cx.props.score}"}
            div { "-" }
        }
    })
}
}

Now, we can use the VoteButton Component like we would use a regular HTML element:

fn main() {
    dioxus::desktop::launch(App);
}

fn App(cx: Scope) -> Element {
    cx.render(rsx! (
        VoteButton { score: 42 }
    ))
}

And we can see that the Component indeed gets rendered:

Screenshot of running app. Text: "+ \ 42 \ -"

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 ().


#![allow(unused)]
fn main() {
// this scope
Scope<()> 

// is the same as this scope
Scope
}

Borrowed Props

Owning props works well if your props are easy to copy around - like a single number. But what if we need to pass a larger data type, like a String from an App Component to a TitleCard subcomponent? A naive solution might be to .clone() the String, creating a copy of it for the subcomponent – but this would be inefficient, especially for larger Strings.

Rust allows for something more efficient – borrowing the String as a &str. Instead of creating a copy, this will give us a reference to the original String – this is what Borrowed Props are for!

However, if we create a reference a String, Rust will require us to show that the String will not go away while we're using the reference. Otherwise, if we referenced something that doesn't exist, Bad Things could happen. To prevent this, Rust asks us to define a lifetime for the reference:


#![allow(unused)]
fn main() {
#[derive(Props)]
struct TitleCardProps<'a> {
    title: &'a str,
}

fn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element {
    cx.render(rsx!{
        h1 { "{cx.props.title}" }
    })
}
}

This lifetime 'a tells the compiler that as long as title exists, the String it was created from must also exist. Dioxus will happily accept such a component – we can now render it alongside our VoteButton!


#![allow(unused)]
fn main() {
fn App(cx: Scope) -> Element {
    // For the sake of an example, we create the &str here.
    // But you might as well borrow it from an owned String type.
    let hello = "Hello Dioxus!";

    cx.render(rsx! (
        VoteButton { score: 42 },
        TitleCard { title: hello }
    ))
}
}

New screenshot of running app, now including a "Hello Dioxus!" heading.

Memoization

Dioxus uses Memoization for a more efficient user interface. Memoization is the process in which we check if a component actually needs to be re-rendered when its props change. If a component's properties change but they wouldn't affect the output, then we don't need to re-render the component, saving time!

For example, let's say we have a component that has two children:


#![allow(unused)]
fn main() {
fn Demo(cx: Scope) -> Element {
    // don't worry about these 2, we'll cover them later
    let name = use_state(&cx, || String::from("bob"));
    let age = use_state(&cx, || 21);

    cx.render(rsx!{
        Name { name: name }
        Age { age: age }
    })
}
}

If name changes but age does not, then there is no reason to re-render our Age component since the contents of its props did not meaningfully change.

Dioxus memoizes owned components. It uses PartialEq to determine if a component needs rerendering, or if it has stayed the same. This is why you must derive PartialEq!

This means you can always rely on props with PartialEq or no props at all to act as barriers in your app. This can be extremely useful when building larger apps where properties frequently change. By moving our state into a global state management solution, we can achieve precise, surgical re-renders, improving the performance of our app.

Borrowed Props cannot be safely memoized. However, this is not a problem – Dioxus relies on the memoization of their parents to determine if they need to be rerendered.

Optional Props

You can easily create optional fields by attaching the optional modifier to a field:


#![allow(unused)]
fn main() {
#[derive(Props, PartialEq)]
struct MyProps {
    name: String,

    #[props(optional)]
    description: Option<String>
}

fn Demo(cx: MyProps) -> Element {
    todo!()
}
}

Then, we can completely omit the description field when calling the component:


#![allow(unused)]
fn main() {
rsx!{
    Demo {
        name: "Thing".to_string(),
        // description is omitted
    }
}
}

The optional modifier is a combination of two separate modifiers: default and strip_option. The full list of 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 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

So far, every Component function we've seen had a corresponding ComponentProps struct to pass in props. This was quite verbose... Wouldn't it be nice to have props as simple function arguments? Then we wouldn't need to define a Props struct, and instead of typing cx.props.whatever, we could just use whatever directly!

inline_props allows you to do just that. Instead of typing the "full" version:


#![allow(unused)]
fn main() {
#[derive(Props, PartialEq)]
struct TitleCardProps {
    title: String,
}

fn TitleCard(cx: Scope<TitleCardProps>) -> Element {
    cx.render(rsx!{
        h1 { "{cx.props.title}" }
    })
}
}

...you can define a function that accepts props as arguments. Then, just annotate it with #[inline_props], and the macro will turn it into a regular Component for you:


#![allow(unused)]
fn main() {
#[inline_props]
fn TitleCard(cx: Scope, title: String) -> Element {
    cx.render(rsx!{
        h1 { "{title}" }
    })
}
}

While the new Component is shorter and easier to read, this macro should not be used by library authors since you have less control over Prop documentation.

Reusing, Importing, and Exporting Components

As your application grows in size, you'll want to start breaking your UI into components and, eventually, different files. This is a great idea to encapsulate functionality of your UI and scale your team.

In this chapter we'll cover:

  • Rust's modules
  • Pub/Private components
  • Structure for large components

Breaking it down

Let's say our app looks something like this:

├── Cargo.toml
└── src
    └── main.rs
// main.rs
use dioxus::prelude::*;

fn main() {
    dioxus::desktop::launch(App);
}

fn App(Scope) -> Element {}

#[derive(PartialEq, Props)]
struct PostProps{}
fn Post(Scope<PostProps>) -> Element {}

#[derive(PartialEq, Props)]
struct VoteButtonsProps {}
fn VoteButtons(Scope<VoteButtonsProps>) -> Element {}

#[derive(PartialEq, Props)]
struct TitleCardProps {}
fn TitleCard(Scope<TitleCardProps>) -> Element {}

#[derive(PartialEq, Props)]
struct MetaCardProps {}
fn MetaCard(Scope<MetaCardProps>) -> Element {}

#[derive(PartialEq, Props)]
struct ActionCardProps {}
fn ActionCard(Scope<ActionCardProps>) -> Element {}

That's a lot of components for one file! We've successfully refactored our app into components, but we should probably start breaking it up into a file for each component.

Breaking into different files

Fortunately, Rust has a built-in module system that's much cleaner than what you might be used to in JavaScript. Because VoteButtons, TitleCard, MetaCard, and ActionCard all belong to the Post component, let's put them all in a folder together called "post". We'll make a file for each component and move the props and render function.


#![allow(unused)]
fn main() {
// src/post/action.rs

use dioxus::prelude::*;

#[derive(PartialEq, Props)]
struct ActionCardProps {}
fn ActionCard(Scope<ActionCardProps>) -> Element {}
}

We should also create a mod.rs file in the post folder so we can use it from our main.rs. Our Post component and its props will go into this file.


#![allow(unused)]
fn main() {
// src/post/mod.rs

use dioxus::prelude::*;

#[derive(PartialEq, Props)]
struct PostProps {}
fn Post(Scope<PostProps>) -> Element {}
}
├── Cargo.toml
└── src
    ├── main.rs
    └── post
        ├── vote.rs
        ├── title.rs
        ├── meta.rs
        ├── action.rs
        └── mod.rs

In our main.rs, we'll want to declare the post module so we can access our Post component.

// main.rs
use dioxus::prelude::*;

fn main() {
    dioxus::desktop::launch(App);
}

mod post;

fn App(Scope) -> Element {
    cx.render(rsx!{
        post::Post {
            id: Uuid::new_v4(),
            score: 10,
            comment_count: 10,
            post_time: std::Instant::now(),
            url: "example".to_string(),
            title: "Title".to_string(),
            original_poster: "me".to_string()
        }
    })
}

If you tried to build this app right now, you'll get an error message saying that Post is private, try changing it to public. This is because we haven't properly exported our component! To fix this, we need to make sure both the Props and Component are declared as "public":


#![allow(unused)]
fn main() {
// src/post/mod.rs

use dioxus::prelude::*;

#[derive(PartialEq, Props)]
pub struct PostProps {}
pub fn Post(Scope<PostProps>) -> Element {}
}

While we're here, we also need to make sure each of our subcomponents are included as modules and exported.

Our "post/mod.rs" file will eventually look like this:


#![allow(unused)]
fn main() {
use dioxus::prelude::*;

mod vote;
mod title;
mod meta;
mod action;

#[derive(Props, PartialEq)]
pub struct PostProps {
    id: uuid::Uuid,
    score: i32,
    comment_count: u32,
    post_time: std::time::Instant,
    url: String,
    title: String,
    original_poster: String
}

pub fn Post(Scope<PostProps>) -> Element {
    cx.render(rsx!{
        div { class: "post-container"
            vote::VoteButtons {
                score: props.score,
            }
            title::TitleCard {
                title: props.title,
                url: props.url,
            }
            meta::MetaCard {
                original_poster: props.original_poster,
                post_time: props.post_time,
            }
            action::ActionCard {
                post_id: props.id
            }
        }
    })
}
}

Ultimately, including and exporting components is governed by Rust's module system. The Rust book is a great resource to learn about these concepts in greater detail.

Final structure:

├── Cargo.toml
└── src
    ├── main.rs
    └── post
        ├── vote.rs
        ├── title.rs
        ├── meta.rs
        ├── action.rs
        └── mod.rs
// main.rs:
use dioxus::prelude::*;

fn main() {
    dioxus::desktop::launch(App);
}

mod post;

fn App(Scope) -> Element {
    cx.render(rsx!{
        post::Post {
            id: Uuid::new_v4(),
            score: 10,
            comment_count: 10,
            post_time: std::Instant::now(),
            url: "example".to_string(),
            title: "Title".to_string(),
            original_poster: "me".to_string()
        }
    })
}

#![allow(unused)]
fn main() {
// src/post/mod.rs
use dioxus::prelude::*;

mod vote;
mod title;
mod meta;
mod action;

#[derive(Props, PartialEq)]
pub struct PostProps {
    id: uuid::Uuid,
    score: i32,
    comment_count: u32,
    post_time: std::time::Instant,
    url: String,
    title: String,
    original_poster: String
}

pub fn Post(Scope<PostProps>) -> Element {
    cx.render(rsx!{
        div { class: "post-container"
            vote::VoteButtons {
                score: props.score,
            }
            title::TitleCard {
                title: props.title,
                url: props.url,
            }
            meta::MetaCard {
                original_poster: props.original_poster,
                post_time: props.post_time,
            }
            action::ActionCard {
                post_id: props.id
            }
        }
    })
}
}

#![allow(unused)]
fn main() {
// src/post/vote.rs
use dioxus::prelude::*;

#[derive(PartialEq, Props)]
pub struct VoteButtonsProps {}
pub fn VoteButtons(Scope<VoteButtonsProps>) -> Element {}
}

#![allow(unused)]
fn main() {
// src/post/title.rs
use dioxus::prelude::*;

#[derive(PartialEq, Props)]
pub struct TitleCardProps {}
pub fn TitleCard(Scope<TitleCardProps>) -> Element {}
}

#![allow(unused)]
fn main() {
// src/post/meta.rs
use dioxus::prelude::*;

#[derive(PartialEq, Props)]
pub struct MetaCardProps {}
pub fn MetaCard(Scope<MetaCardProps>) -> Element {}
}

#![allow(unused)]
fn main() {
// src/post/action.rs
use dioxus::prelude::*;

#[derive(PartialEq, Props)]
pub struct ActionCardProps {}
pub fn ActionCard(Scope<ActionCardProps>) -> Element {}
}

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.

In this chapter, you'll learn about:

  • Passing elements into components
  • Passing attributes into components

The use case

Let's say you're building a user interface and want to make some part of it a clickable link to another website. You would normally start with the HTML <a> tag, like so:


#![allow(unused)]
fn main() {
rsx!(
    a {
        href: "https://google.com"
        "Link to google"
    }
)
}

But, what if we wanted to style our <a> tag? Or wrap it with some helper icon? We could abstract our RSX into its own component:


#![allow(unused)]
fn main() {
#[derive(Props)]
struct ClickableProps<'a> {
    href: &'a str,
    title: &'a str
}

fn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
    cx.render(rsx!(
        a {
            href: "{cx.props.href}"
            "{cx.props.title}"
        }
    ))
}
}

And then use it in our code like so:


#![allow(unused)]
fn main() {
rsx!(
    Clickable {
        href: "https://google.com"
        title: "Link to Google"
    }
)
}

Let's say we don't just want the text to be clickable, but we want another element, like an image, to be clickable. How do we implement that?

Passing children

If we want to pass an image into our component, we can just adjust our props and component to allow any Element.


#![allow(unused)]
fn main() {
#[derive(Props)]
struct ClickableProps<'a> {
    href: &'a str,
    body: Element<'a>
}

fn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
    cx.render(rsx!(
        a {
            href: "{cx.props.href}",
            &cx.props.body
        }
    ))
}
}

Then, at the call site, we can render some nodes and pass them in:


#![allow(unused)]
fn main() {
rsx!(
    Clickable {
        href: "https://google.com"
        body: cx.render(rsx!(
            img { src: "https://www.google.com/logos/doodles/..." }
        ))
    }
)
}

Auto Conversion of the Children field

This pattern can become tedious in some instances, so Dioxus actually performs an implicit conversion of any rsx calls inside components into Elements at the children field. This means you must explicitly declare if a component can take children.


#![allow(unused)]
fn main() {
#[derive(Props)]
struct ClickableProps<'a> {
    href: &'a str,
    children: Element<'a>
}

fn Clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
    cx.render(rsx!(
        a {
            href: "{cx.props.href}",
            &cx.props.children
        }
    ))
}
}

Now, whenever we use Clickable in another component, we don't need to call render on child nodes - it will happen automatically!


#![allow(unused)]
fn main() {
rsx!(
    Clickable {
        href: "https://google.com"
        img { src: "https://www.google.com/logos/doodles/...." }
    }
)
}

Note: Passing children into components will break any memoization due to the associated lifetime.

While technically allowed, it's an antipattern to pass children more than once in a component and will probably cause your app to crash.

However, because the Element is transparently a VNode, we can actually match on it to extract the nodes themselves, in case we are expecting a specific format:


#![allow(unused)]
fn main() {
fn clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
    match cx.props.children {
        Some(VNode::Text(text)) => {
            // ...
        }
        _ => {
            // ...
        }
    }
}
}

Passing nodes through props means that they are immutable. If you find yourself needing to mutate nodes passed through props, consider creating a new node in its place that takes on its attributes, children, and listeners.

Passing handlers

Dioxus also provides some implicit conversions from listener attributes into an EventHandler for any field on components that starts with on. IE onclick, onhover, etc. For properties, we want to define our on fields as an event handler:


#![allow(unused)]
fn main() {
#[derive(Props)]
struct ClickableProps<'a> {
    onclick: EventHandler<'a, MouseEvent>
}

fn clickable<'a>(cx: Scope<'a, ClickableProps<'a>>) -> Element {
    cx.render(rsx!(
        a {
            onclick: move |evt| cx.props.onclick.call(evt)
        }
    ))
}
}

Then, we can attach a listener at the call site:


#![allow(unused)]
fn main() {
rsx!(
    Clickable {
        onclick: move |_| log::info!("Clicked"),
    }
)
}

Currently, Dioxus does not support an arbitrary amount of listeners - they must be strongly typed in Properties. If you need this use case, you can pass in an element with these listeners, or dip down into the NodeFactory API.

Wrapping up

In this chapter, we learned:

  • How to pass arbitrary nodes through the tree
  • How the children field works on component properties
  • How the attributes field works on component properties
  • How to convert listeners into EventHandlers for components
  • How to extend any node with custom attributes and children

Thinking in Reactively

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.

In this section, we'll talk about:

  • One-way data flow
  • Modifying data
  • Forcing renders
  • How renders propagate

This section is a bit long, but worth the read. We recommend coffee, tea, and/or snacks.

Reactive Programming

Dioxus is one of a handful of Rust libraries that provide a "Reactive Programming Model". The term "Reactive programming" is a classification of programming paradigm - much like functional or imperative programming. This is a very important distinction since it affects how we think about our code.

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.

We consider the rendered GUI to be the final result of our Dioxus apps. The datasources for our apps include local and global state.

For example, the model presented in the figure below is comprised of two data sources: time and a constant. These values are passed through our computation graph to achieve a final result: g.

Reactive Model

Whenever our seconds variable changes, we will then reevaluate the computation for t. Because g relies on t, we will also reevaluate its computation too. Notice that we would've reevaluated the computation for g even if t didn't change because seconds is used to calculate g.

However, if we somehow changed our constant from 1 to 2, then we need to reevaluate t. If, for whatever reason, this change did not affect the result of t, then we wouldn't try to reevaluate g.

In Reactive Programming, we don't think about whether or not we should reevaluate t or g; instead, we simply provide functions of computation and let the framework figure out the rest for us.

In Rust, our reactive app would look something like:


#![allow(unused)]
fn main() {
fn compute_g(t: i32, seconds: i32) -> bool {
    t > seconds
}

fn compute_t(constant: i32, seconds: i32) -> i32 {
    constant + seconds
}

fn compute_graph(constant: i32, seconds: i32) -> bool {
    let t = compute_t(constant, seconds);
    let g = compute_g(t, seconds);
    g
}
}

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.

If we represented the reactive graph presented above in Dioxus, it would look very similar:


#![allow(unused)]
fn main() {
// Declare a component that holds our datasources and calculates `g`
fn RenderGraph(cx: Scope) -> Element {
    let seconds = use_datasource(SECONDS);
    let constant = use_state(&cx, || 1);

    cx.render(rsx!(
        RenderG { seconds: seconds }
        RenderT { seconds: seconds, constant: constant }
    ))
}

// "calculate" g by rendering `t` and `seconds`
#[inline_props]
fn RenderG(cx: Scope, seconds: i32) -> Element {
    cx.render(rsx!{ "There are {seconds} seconds remaining..." })
}

// calculate and render `t` in its own component
#[inline_props]
fn RenderT(cx: Scope, seconds: i32, constant: i32) -> Element {
    let res = seconds + constant;
    cx.render(rsx!{ "{res}" })
}
}

With this app, we've defined three components. Our top-level component provides our datasources (the hooks), computation nodes (child components), and a final value (what's "rendered").

Now, whenever the constant changes, our RenderT component will be re-rendered. However, if seconds doesn't change, then we don't need to re-render RenderG because the input is the same. If seconds does change, then both RenderG and RenderT will be reevaluated.

Dioxus is "Reactive" because it provides this framework for us. All we need to do is write our own tiny units of computation and Dioxus figures out which components need to be reevaluated automatically.

These extra checks and algorithms add some overhead, which is why you see projects like Sycamore and SolidJS eliminating them altogether. Dioxus is really fast, so we're willing to exchange the added overhead for improved developer experience.

How do we update values in our dataflow graph?

Dioxus will automatically figure out how to regenerate parts of our app when datasources change. But how exactly can we update our data sources?

In Dioxus there are two datasources:

  1. Local state in use_hook and all other hooks
  2. Global state through provide_context.

Technically, the root props of the VirtualDom are a third datasource, but since we cannot modify them, they are not worth talking about.

Local State

For local state in hooks, Dioxus gives us the use_hook method which returns an &mut T without any requirements. This means raw hook values are not tracked by Dioxus. In fact, we could write a component that modifies a hook value directly:


#![allow(unused)]
fn main() {
fn app(cx: Scope) -> Element {
    let mut count = cx.use_hook(|_| 0);
    cx.render(rsx!{
        button {
            onclick: move |_| *count += 1,
            "Count: {count}"
        }
    })
}
}

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:


#![allow(unused)]
fn main() {
button {
    onclick: move |_| {
        *count += 1;
        cx.needs_update();
    },
    "Count: {count}"
}
}

Now, whenever we click the button, the value will change and the component will be re-rendered.

Re-rendering is when Dioxus calls your function component again. Component functions will be called over and over throughout their lifetime, so they should be mostly side-effect free.

Understand this!

Your component functions will be called ("rendered" in our lingo) for as long as the component is present in the tree.

A single component will be called multiple times, modifying its own internal state or rendering new nodes with new values from its properties.

App-Global State

With the provide_context and consume_context methods on Scope, we can share values to descendants without having to pass values through component props. This has the side-effect of making our datasources less obvious from a high-level perspective, but it makes our components more modular within the same codebase.

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.

To regenerate any component in your app, you can get a handle to the Dioxus' internal scheduler through schedule_update_any:


#![allow(unused)]
fn main() {
let force_render = cx.schedule_update_any();

// force a render of the root component
force_render(ScopeId(0));
}

What does it mean for a component to "re-render"?

In our guides, we frequently use the phrase "re-render" to describe updates to our app. You'll often hear this paired with "preventing unnecessary re-renders." But what exactly does this mean?

When we call dioxus::desktop::launch, Dioxus will create a new Scope object and call the component we gave it. Our rsx! calls will create new nodes which we return back to the VirtualDom. Dioxus will then look through these nodes for child components, call their functions, and so on until every component has been "rendered." We consider these nodes "rendered" because they were created because of our explicit actions.

The tree of UI that dioxus creates will roughly look like the tree of components presented earlier:

Tree of UI

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."

These nodes are stored in an extremely efficient memory allocator called a "bump arena." For example, a div with a handler and attribute would be stored in memory in two locations: the "old" tree and the "new" tree.

Bump Arenas

From here, Dioxus computes the difference between these trees and updates the Real DOM to make it look like the new version of what we've declared.

Diffing

Suppressing Renders

So, we know how to make Dioxus render, but how do we stop it? What if we know that our state didn't change and we shouldn't render and diff new nodes because they'll be exactly the same as the last time?

In these cases, you want to reach for memoization. In Dioxus, memoization involves preventing a component from rendering again if its props didn't change since the last time it attempted to render.

Visually, you can tell that a component will only re-render if the new value is sufficiently different than the old one.

props.valre-render
10true
20true
20false
20false
10true
30false

This is why when you derive(Props), you must also implement the PartialEq trait. To override the memoization strategy for a component, you can simply implement your own PartialEq.


#![allow(unused)]
fn main() {
struct CustomProps {
    val: i32,
}

impl PartialEq for CustomProps {
    fn partial_eq(&self, other: &Self) -> bool {
        // we don't render components that have a val less than 5
        if other.val > 5 && self.val > 5{
            self.val == other.val
        }
    }
}
}

However, for components that borrow data, it doesn't make sense to implement PartialEq since the actual references in memory might be different.

You can technically override this behavior by implementing the Props trait manually, though it's unsafe and easy to mess up:


#![allow(unused)]
fn main() {
unsafe impl Properties for CustomProps {
    fn memoize(&self, other &Self) -> bool {
        self != other
    }
}
}

TLDR:

  • Dioxus checks if props changed between renders
  • If props changed according to PartialEq, Dioxus re-renders the component
  • Props that have a lifetime (ie <'a>) will always be re-rendered

Wrapping Up

Wow, that was a lot of material!

Let's see if we can recap what was presented:

  • Reactive programming calculates a final value from datasources and computation
  • Dioxus is "reactive" since it figures out which computations to check
  • schedule_update must be called to mark a component as dirty
  • dirty components will be re-rendered (called multiple times) to produce a new UI
  • Renders can be suppressed with memoization

This theory is crucial to understand how to compose components and how to control renders in your app.

Adding Interactivity

So far, we've learned how to describe the structure and properties of our user interfaces. Unfortunately, they're static and quite uninteresting. In this chapter, we're going to learn how to add interactivity through events, state, and tasks.

Primer on interactivity

Before we get too deep into the mechanics of interactivity, we should first understand how Dioxus exactly chooses to handle user interaction and updates to your app.

What is state?

Every app you'll ever build has some sort of information that needs to be rendered to the screen. Dioxus is responsible for translating your desired user interface to what is rendered to the screen. You are responsible for providing the content.

The dynamic data in your user interface is called State.

When you first launch your app with dioxus::web::launch_with_props you'll be providing the initial state. You need to declare the initial state before starting the app.

fn main() {
    // declare our initial state
    let props = PostProps {
        id: Uuid::new_v4(),
        score: 10,
        comment_count: 0,
        post_time: std::time::Instant::now(),
        url: String::from("dioxuslabs.com"),
        title: String::from("Hello, world"),
        original_poster: String::from("dioxus")
    };

    // start the render loop
    dioxus::desktop::launch_with_props(Post, props);
}

When Dioxus renders your app, it will pass an immutable reference to PostProps into your Post component. Here, you can pass the state down into children.


#![allow(unused)]
fn main() {
fn App(cx: Scope<PostProps>) -> Element {
    cx.render(rsx!{
        Title { title: &cx.props.title }
        Score { score: &cx.props.score }
        // etc
    })
}
}

State in Dioxus follows a pattern called "one-way data-flow." As your components create new components as their children, your app's structure will eventually grow into a tree where state gets passed down from the root component into "leaves" of the tree.

You've probably seen the tree of UI components represented using a directed acyclic graph:

image

With Dioxus, your state will always flow down from parent components into child components.

How do I change my app's state?

We've talked about the data flow of state, but we haven't yet talked about how to change that state dynamically. Dioxus provides a variety of ways to change the state of your app while it's running.

For starters, we could use the update_root_props method on the VirtualDom to provide an entirely new root state of your App. However, for most applications, you probably don't want to regenerate your entire app just to update some text or a flag.

Instead, you'll want to store state internally in your components and let that flow down the tree. To store state in your components, you'll use something called a hook. Hooks are special functions that reserve a slot of state in your component's memory and provide some functionality to update that state.

The most common hook you'll use for storing state is use_state. use_state provides a slot for some data that allows you to read and update the value without accidentally mutating it.


#![allow(unused)]
fn main() {
fn App(cx: Scope)-> Element {
    let (post, set_post) = use_state(&cx, || {
        PostData {
            id: Uuid::new_v4(),
            score: 10,
            comment_count: 0,
            post_time: std::time::Instant::now(),
            url: String::from("dioxuslabs.com"),
            title: String::from("Hello, world"),
            original_poster: String::from("dioxus")
        }
    });

    cx.render(rsx!{
        Title { title: &post.title }
        Score { score: &post.score }
        // etc
    })
}
}

Whenever we have a new post that we want to render, we can call set_post and provide a new value:


#![allow(unused)]
fn main() {
set_post(PostData {
    id: Uuid::new_v4(),
    score: 20,
    comment_count: 0,
    post_time: std::time::Instant::now(),
    url: String::from("google.com"),
    title: String::from("goodbye, world"),
    original_poster: String::from("google")
})
}

We'll dive deeper into how exactly these hooks work later.

When do I update my state?

There are a few different approaches to choosing when to update your state. You can update your state in response to user-triggered events or asynchronously in some background task.

Updating state in listeners

When responding to user-triggered events, we'll want to "listen" for an event on some element in our component.

For example, let's say we provide a button to generate a new post. Whenever the user clicks the button, they get a new post. To achieve this functionality, we'll want to attach a function to the onclick method of button. Whenever the button is clicked, our function will run, and we'll get new Post data to work with.


#![allow(unused)]
fn main() {
fn App(cx: Scope)-> Element {
    let (post, set_post) = use_state(&cx, || PostData::new());

    cx.render(rsx!{
        button {
            onclick: move |_| set_post(PostData::random())
            "Generate a random post"
        }
        Post { props: &post }
    })
}
}

We'll dive much deeper into event listeners later.

Updating state asynchronously

We can also update our state outside of event listeners with futures and coroutines.

  • Futures are Rust's version of promises that can execute asynchronous work by an efficient polling system. We can submit new futures to Dioxus either through push_future which returns a TaskId or with spawn.
  • Coroutines are asynchronous blocks of our component that have the ability to cleanly interact with values, hooks, and other data in the component.

Since coroutines and Futures stick around between renders, the data in them must be valid for the 'static lifetime. We must explicitly declare which values our task will rely on to avoid the stale props problem common in React.

We can use tasks in our components to build a tiny stopwatch that ticks every second.

Note: The use_future hook will start our coroutine immediately. The use_coroutine hook provides more flexibility over starting and stopping futures on the fly.


#![allow(unused)]
fn main() {
fn App(cx: Scope)-> Element {
    let (elapsed, set_elapsed) = use_state(&cx, || 0);

    use_future(&cx, || {
        to_owned![set_elapsed]; // explicitly capture this hook for use in async
        async move {
            loop {
                TimeoutFuture::from_ms(1000).await;
                set_elapsed.modify(|i| i + 1)
            }
        }
    });

    rsx!(cx, div { "Current stopwatch time: {sec_elapsed}" })
}
}

Using asynchronous code can be difficult! This is just scratching the surface of what's possible. We have an entire chapter on using async properly in your Dioxus Apps. We have an entire section dedicated to using async properly later in this book.

How do I tell Dioxus that my state changed?

Whenever you inform Dioxus that the component needs to be updated, it will "render" your component again, storing the previous and current Elements in memory. Dioxus will automatically figure out the differences between the old and the new and generate a list of edits that the renderer needs to apply to change what's on the screen. This process is called "diffing":

Diffing

In React, the specifics of when a component gets re-rendered is somewhat blurry. With Dioxus, any component can mark itself as "dirty" through a method on Context: needs_update. In addition, any component can mark any other component as dirty provided it knows the other component's ID with needs_update_any.

With these building blocks, we can craft new hooks similar to use_state that let us easily tell Dioxus that new information is ready to be sent to the screen.

How do I update my state efficiently?

In general, Dioxus should be plenty fast for most use cases. However, there are some rules you should consider following to ensure your apps are quick.

    1. Don't call set_state while rendering. This will cause Dioxus to unnecessarily re-check the component for updates or enter an infinite loop.
    1. 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.
    1. Move local state down. Dioxus will need to re-check child components of your app if the root component is constantly being updated. You'll get best results if rapidly-changing state does not cause major re-renders.

The Scope object

Though very similar to React, Dioxus is different in a few ways. Most notably, React components will not have a Scope parameter in the component declaration.

Have you ever wondered how the useState() call works in React without a this object to actually store the state?

// in React:
function Component(props) {
    // This state persists between component renders, but where does it live?
    let [state, set_state] = useState(10);
}

React uses global variables to store this information. However, global mutable variables must be carefully managed and are broadly discouraged in Rust programs. Because Dioxus needs to work with the rules of Rust it uses the Scope rather than a global state object to maintain some internal bookkeeping.

That's what the Scope object is: a place for the Component to store state, manage listeners, and allocate elements. Advanced users of Dioxus will want to learn how to properly leverage the Scope object to build robust and performant extensions for Dioxus.


#![allow(unused)]
fn main() {
fn Post(cx: Scope<PostProps>) -> Element {
    cx.render(rsx!("hello"))
}
}

Moving On

This overview was a lot of information - but it doesn't tell you everything!

In the next sections we'll go over:

  • use_state in depth
  • use_ref and other hooks
  • Handling user input

Event handlers

To make our boring UIs less static and more interesting, we want to add the ability to interact to 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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
// 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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
form {
    prevent_default: "onclick",
    onclick: move |_|{
        // handle the event without navigating the page.
    }
}
}

Hooks and Internal State

In the Adding Interactivity section, we briefly covered the concept of hooks and state stored internal to components.

In this section, we'll dive a bit deeper into hooks, exploring both the theory and mechanics.

Theory of Hooks

Over the past several decades, computer scientists and engineers have long sought the "right way" of designing user interfaces. With each new programming language, novel features are unlocked that change the paradigm in which user interfaces are coded.

Generally, a number of patterns have emerged, each with their own strengths and tradeoffs.

Broadly, there are two types of GUI structures:

  • Immediate GUIs: re-render the entire screen on every update
  • Retained GUIs: only re-render the portion of the screen that changed

Typically, immediate-mode GUIs are simpler to write but can slow down as more features, like styling, are added.

Many GUIs today are written in Retained mode - your code changes the data of the user interface but the renderer is responsible for actually drawing to the screen. In these cases, our GUI's state sticks around as the UI is rendered. To help accommodate retained mode GUIs, like the web browser, Dioxus provides a mechanism to keep state around.

Note: Even though hooks are accessible, you should still prefer one-way data flow and encapsulation. Your UI code should be as predictable as possible. Dioxus is plenty fast, even for the largest apps.

Mechanics of Hooks

In order to have state stick around between renders, Dioxus provides the hook through the use_hook API. This gives us a mutable reference to data returned from the initialization function.


#![allow(unused)]
fn main() {
fn example(cx: Scope) -> Element {
    let name: &mut String = cx.use_hook(|| "John Doe".to_string());

    //
}
}

We can even modify this value directly from an event handler:


#![allow(unused)]
fn main() {
fn example(cx: Scope) -> Element {
    let name: &mut String = cx.use_hook(|| "John Doe".to_string());

    cx.render(rsx!(
        button {
            onclick: move |_| name.push_str(".."),
        }
    ))
}
}

Mechanically, each call to use_hook provides us with &mut T for a new value.


#![allow(unused)]
fn main() {
fn example(cx: Scope) -> Element {
    let name: &mut String = cx.use_hook(|| "John Doe".to_string());
    let age: &mut u32 = cx.use_hook(|| 10);
    let friends: &mut Vec<String> = cx.use_hook(|| vec!["Jane Doe".to_string()]);

    //
}
}

Internally, Dioxus is creating a list of hook values with each call to use_hook advancing the index of the list to return the next value.

Our internal HookList would look something like:


#![allow(unused)]
fn main() {
[
    Hook<String>,
    Hook<u32>,
    Hook<String>,
]
}

This is why hooks called out of order will fail - if we try to downcast a Hook<String> to Hook<u32>, Dioxus has no choice but to panic. We do provide a try_use_hook but you should never need that in practice.

This pattern might seem strange at first, but it can be a significant upgrade over structs as blobs of state, which tend to be difficult to use in Rust given the ownership system.

Rules of hooks

Hooks are sensitive to how they are used. To use hooks, you must abide by the "rules of hooks" (borrowed from react):

  • Functions with "use_" should not be called in callbacks
  • Functions with "use_" should not be called out of order
  • Functions with "use_" should not be called in loops or conditionals

Examples of "no-nos" include:

❌ Nested uses


#![allow(unused)]
fn main() {
// ❌ don't call use_hook or any `use_` function *inside* use_hook!
cx.use_hook(|_| {
    let name = cx.use_hook(|_| "ads");
})

// ✅ instead, move the first hook above
let name = cx.use_hook(|_| "ads");
cx.use_hook(|_| {
    // do something with name here
})
}

❌ Uses in conditionals


#![allow(unused)]
fn main() {
// ❌ don't call use_ in conditionals!
if do_thing {
    let name = use_state(&cx, || 0);
}

// ✅ instead, *always* call use_state but leave your logic
let name = use_state(&cx, || 0);
if do_thing {
    // do thing with name here
}
}

❌ Uses in loops


#![allow(unused)]
fn main() {
// ❌ Do not use hooks in loops!
let mut nodes = vec![];

for name in names {
    let age = use_state(&cx, |_| 0);
    nodes.push(cx.render(rsx!{
        div { "{age}" }
    }))
}

// ✅ Instead, consider refactoring your usecase into components 
#[inline_props]
fn Child(cx: Scope, name: String) -> Element {
    let age = use_state(&cx, |_| 0);
    cx.render(rsx!{ div { "{age}" } })
}

// ✅ Or, use a hashmap with use_ref
let ages = use_ref(&cx, || HashMap::new());

names.iter().map(|name| {
    let age = ages.get(name).unwrap();
    cx.render(rsx!{ div { "{age}" } })
})
}

Building new Hooks

However, most hooks you'll interact with don't return an &mut T since this is not very useful in a real-world situation.

Consider when we try to pass our &mut String into two different handlers:


#![allow(unused)]
fn main() {
fn example(cx: Scope) -> Element {
    let name: &mut String = cx.use_hook(|| "John Doe".to_string());

    cx.render(rsx!(
        button { onclick: move |_| name.push_str("yes"), }
        button { onclick: move |_| name.push_str("no"), }
    ))
}
}

Rust will not allow this to compile! We cannot Copy unique mutable references - they are, by definition, unique. However, we can reborrow our &mut T as an &T which are non-unique references and share those between handlers:


#![allow(unused)]
fn main() {
fn example(cx: Scope) -> Element {
    let name: &String = &*cx.use_hook(|| "John Doe".to_string());

    cx.render(rsx!(
        button { onclick: move |_| log::info!("{}", name), }
        button { onclick: move |_| log::info!("{}", name), }
    ))
}
}

So, for any custom hook we want to design, we need to enable mutation through interior mutability - IE move to runtime borrow checking. We might incur a tiny runtime cost for each time we grab a new value from the hook, but this cost is extremely minimal.

This example uses the Cell type to let us replace the value through interior mutability. Cell has practically zero overhead, but is slightly more limited that its RefCell cousin.


#![allow(unused)]
fn main() {
fn example(cx: Scope) -> Element {
    let name: &Cell<&'static str> = cx.use_hook(|| Cell::new("John Doe"));

    cx.render(rsx!(
        button { onclick: move |_| name.set("John"), }
        button { onclick: move |_| name.set("Jane"), }
    ))
}
}

Driving state updates through hooks

Hooks like use_state and use_ref wrap this runtime borrow checking in a type that does implement Copy. Additionally, they also mark the component as "dirty" whenever a new value has been set. This way, whenever use_state has a new value set, the component knows to update.


#![allow(unused)]
fn main() {
fn example(cx: Scope) -> Element {
    let name = use_state(&cx, || "Jack");

    cx.render(rsx!(
        "Hello, {name}"
        button { onclick: move |_| name.set("John"), }
        button { onclick: move |_| name.set("Jane"), }
    ))
}
}

Internally, our set function looks something like this:


#![allow(unused)]
fn main() {
impl<'a, T> UseState<'a, T> {
    fn set(&self, new: T) {
        // Replace the value in the cell
        self.value.set(new);

        // Mark our component as dirty
        self.cx.needs_update();
    }
}
}

Most hooks we provide implement Deref on their values since they are essentially smart pointers. To access the underlying value, you'll often need to use the deref operator:


#![allow(unused)]
fn main() {
fn example(cx: Scope) -> Element {
    let name = use_state(&cx, || "Jack");

    match *name {
        "Jack" => {}
        "Jill" => {}
        _ => {}
    }

    // ..
}

}

Hooks provided by the Dioxus-Hooks package

By default, we bundle a handful of hooks in the Dioxus-Hooks package. Feel free to click on each hook to view its definition and associated documentation.

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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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:


#![allow(unused)]
fn main() {
let current: Rc<T> = count.current();
}

Or, we can get the inner handle to set the value:


#![allow(unused)]
fn main() {
let setter: Rc<dyn Fn(T)> = count.setter();
}

Modify

Or, we can set a new value using the current value as a reference:


#![allow(unused)]
fn main() {
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:


#![allow(unused)]
fn main() {
count.with_mut(|i| *i += 1);
}

Alternatively, we can call make_mut which gives us a RefMut to the underlying value:


#![allow(unused)]
fn main() {
*count.make_mut() += 1;
}

Use in Async

Plus, the set_count handle is cloneable into async contexts, so we can use it in futures.


#![allow(unused)]
fn main() {
// 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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]

fn main() {
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 {
    // ...
}
}

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).

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:


#![allow(unused)]
fn main() {
let names = use_ref(&cx, || vec!["susie", "calvin"]);
}

To read the hook values, use the read() method:


#![allow(unused)]
fn main() {
cx.render(rsx!{
    ul {
        names.read().iter().map(|name| {
            rsx!{ "{name}" }
        })
    }
})
}

And then to write into the hook value, use the write method:


#![allow(unused)]
fn main() {
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:


#![allow(unused)]
fn main() {
names.write_silent().push("Transmogrifier");
}

Again, like UseState, the UseRef handle is clonable into async contexts:


#![allow(unused)]
fn main() {
// 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.


#![allow(unused)]
fn main() {
let val = use_state(&cx, || vec![1, 3, 3, 7]);
let val = use_state(&cx, || (0..10000).collect::<Vec<_>x>());
let val = use_state(&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.


#![allow(unused)]

fn main() {
fn app(cx: Scope) -> Element {
    let val = use_ref(&cx, || 0);

    cx.render(rsx!{
        Child { val: val.clone() }
    })
}

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.


#![allow(unused)]

fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
// 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.


#![allow(unused)]
fn main() {
// 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(),
    }
})
}

User Input and Controlled Components

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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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, 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.


#![allow(unused)]
fn main() {
form {
    oninput: move |evt| {
        if !validate_input(evt.values) {
            // display what errors validation resulted in
        }
    },
    input { name: "name", }
    input { name: "age", }
    input { name: "date", }
}
}

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.

In this chapter, we'll cover the various ways to manage state, the appropriate terminology, various patterns, and some problems you might run into.

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 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.


#![allow(unused)]
fn main() {
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:

  • 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.

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.


#![allow(unused)]
fn main() {
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 {
                h1 { "{todo.contents}" }
                onhover: move |_| *todos.write()[id].is_hovered = true;
            }
        })
    }
})
}

As shown above, whenever the todo is hovered, we want to set its state:


#![allow(unused)]
fn main() {
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:


#![allow(unused)]
fn main() {
#[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:


#![allow(unused)]
fn main() {
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_states.

You might've started modeling your component with a struct and use_ref


#![allow(unused)]
fn main() {
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:


#![allow(unused)]
fn main() {
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 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:


#![allow(unused)]
fn main() {
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.

Global State

If your app has finally gotten large enough where passing values through the tree ends up polluting the intent of your code, then it might be time to turn to global state.

In Dioxus, global state is shared through the Context API. This guide will show you how to use the Context API to simplify state management.

Provide Context and Consume Context

The simplest way of retrieving shared state in your app is through the Context API. The Context API allows you to provide and consume an item of state between two components.

Whenever a component provides a context, it is then accessible to any child components.

Note: parent components cannot "reach down" and consume state from below their position in the tree.

The terminology here is important: provide context means that the component will expose state and consume context means that the child component can acquire a handle to state above it.

Instead of using keys or statics, Dioxus prefers the NewType pattern to search for parent state. This means each state you expose as a context should be its own unique type.

In practice, you'll have a component that exposes some state:


#![allow(unused)]
fn main() {
#[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:


#![allow(unused)]
fn main() {
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:


#![allow(unused)]
fn main() {
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.

Lifting State and Fanning Out

Maintaining state local to components doesn't always work.

One of the most reliable state management patterns in large Dioxus apps is to lift-up and fan-out. Lifting up and fanning-out state is the ideal way to structure your app to maximize code reuse, testability, and deterministic rendering.

Lifting State

When building complex apps with Dioxus, the best approach is to start by placing your state and an UI all within a single component. Once your component has passed a few hundred lines, then it might be worth refactoring it into a few smaller components.

Here, we're now challenged with how to share state between these various components.

Let's say we refactored our component to separate an input and a display.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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:


#![allow(unused)]
fn main() {
#[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:


#![allow(unused)]
fn main() {
#[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:


#![allow(unused)]
fn main() {
#[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:


#![allow(unused)]
fn main() {
#[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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.

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:


#![allow(unused)]
fn main() {
fn App(cx: Scope) -> Element {
    None
}
}

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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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:


#![allow(unused)]
fn main() {
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).


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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:


#![allow(unused)]
fn main() {
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.

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

Fermi

Global state as easy as use_state.

Link

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.

Link

3rd-party - HeroIcon

Collection of helpful hero icons, curtesy of @autarch.

Link

3rd-party - Katex

Draw beautiful equations, curtesy of @oovm

Link

3rd-party - PrimsJS

Highlight your code blocks with ease, curtesy of @oovm

Link

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.

[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."


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
static NAMES: AtomRef<Uuid, String> = |builder| {};
static CHECKED: AtomRef<Uuid, bool> = |builder| {};
static CREATED: AtomRef<Uuid, Instant> = |builder| {};
}

To use these collections:


#![allow(unused)]
fn main() {
#[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:


#![allow(unused)]
fn main() {
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!

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.

[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.


#![allow(unused)]
fn main() {
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

#![allow(unused)]
fn main() {
rsx!{
    Router {
        Route { to: "/home", Home {} }
        Route { to: "/blog", Blog {} }
        Route { to: "", NotFound {} }
    }
}
}
  • Redirect 404 to home

#![allow(unused)]
fn main() {
rsx!{
    Router {
        Route { to: "/home", Home {} }
        Route { to: "/blog", Blog {} }
        Redirect { from: "", to: "/home" }
    }
}
}

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.


#![allow(unused)]
fn main() {
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.

Working with Async

Not all apps you'll build can be self-contained with synchronous code. You'll often need to interact with file systems, network interfaces, hardware, or timers.

So far, we've only talked about building apps with synchronous code, so this chapter will focus integrating asynchronous code into your app.

The Runtime

By default, Dioxus-Desktop ships with the Tokio runtime and automatically sets everything up for you. This is currently not configurable, though it would be easy to write an integration for Dioxus desktop that uses a different asynchronous runtime.

Send/Sync

Writing apps that deal with Send/Sync can be frustrating at times. Under the hood, Dioxus is not currently thread-safe, so any async code you write does not need to be Send/Sync. That means that you can use non-thread-safe structures like Cell, Rc, and RefCell.

All async code in your app is polled on a LocalSet, so you can also use tokio::spawn_local.

Spawning a future

Currently, all futures in Dioxus must be 'static. To spawn a future, simply call cx.spawn().


#![allow(unused)]
fn main() {
rsx!{
    button {
        onclick: move |_| cx.spawn(async move {
            let result = fetch_thing().await;
        })
    }
}
}

Calling spawn will give you a JoinHandle which lets you cancel or pause the future.

Setting state from within a future

If you start to write some async code, you'll quickly be greeted with the infamous error about borrowed items in static closures.

error[E0759]: `cx` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
  --> examples/tasks.rs:13:27
   |
12 | fn app(cx: Scope) -> Element {
   |            ----- this data with an anonymous lifetime `'_`...
13 |     let count = use_state(&cx, || 0);
   |                           ^^^ ...is used here...
14 |
15 |     use_future(&cx, (), move |_| {
   |     ---------- ...and is required to live as long as `'static` here
   |
note: `'static` lifetime requirement introduced by this bound
  --> /Users/jonkelley/Development/dioxus/packages/hooks/src/usefuture.rs:25:29
   |
25 |     F: Future<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:


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
cx.spawn({
    to_owned![count, age, name, description, etc];
    async move {
        tokio::time::sleep(Duration::from_millis(1000)).await;
        count += 1;
    }
});
}

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:


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
#[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
}
}

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.


#![allow(unused)]
fn main() {
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:


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
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.


#![allow(unused)]
fn main() {
fn Child(cx: Scope) -> Element {
    let sync_task = use_coroutine_handle::<SyncAction>(&cx);

    sync_task.send(SyncAction::SetUsername);
}
}

Congrats!

Congrats! You've made it through the learning Dioxus book. Throughout this tutorial, you've learned a ton:

  • How to build User Interfaces with Elements
  • How to compose Element groups together as Components
  • How to handle user input with event listeners
  • How to manage local and global state
  • How to work with async using tasks, coroutines, and suspense
  • How to build custom hooks and handlers

With any luck, you followed through the "Putting it All Together" mini guide and have your very own dog search engine app!

Next Steps and Advanced Topics

Continuing on your journey with Dioxus, you can try a number of things:

  • Build a simple TUI app
  • Publish your search engine app
  • Deploy a WASM app to GitHub
  • Design a custom hook
  • Contribute to the ecosystem!

There are a number of advanced topics we glossed over:

  • The underlying NodeFactory API
  • Static elements and templates
  • Anti-patterns
  • Bundling/distribution
  • Working with wasm apps

Contributing to the ecosystem

Dioxus is still quite young and could use your help!

The core team is actively working on:

  • Declarative window management (via Tauri) for Desktop apps
  • Portals for Dioxus Core
  • Mobile support
  • Integration with 3D renderers
  • Better async story (suspense, error handling)
  • Global state management
  • Web development server
  • LiveView
  • Broader platform support (iOS/Android/TV/embedded)

If there's something specifically interesting to you, don't be afraid to jump in!

Contributors

All pull requests (including those made by a team member) must be approved by at least one other team member. Larger, more nuanced decisions about design, architecture, breaking changes, trade offs, etc are made by team consensus.

Contributors to this guide: