🦀 Dioxus Library Cheatsheet

⚠️ WORK IN PROGRESS ! ⚠️

Source Code 🐙

Components

State

Hooks

  • ⚠️ Rules
  • ⚠️ Custom

Patterns

Dioxus Standard Library

  • ⚠️ Color Scheme
  • ⚠️ Localization (i18n)
  • ⚠️ Channels

Renderers

  • ⚠️ Web
  • ⚠️ Desktop
  • ⚠️ TUI
  • ⚠️ Blitz
  • ⚠️ Liveview
  • ⚠️ SSR
  • ⚠️ Fullstack
  • ⚠️ Freya

🔗Overview

Dioxus is a declarative library for building user interfaces (UI) in Rust 🦀. It is inspired by React, so it shares many of it's concepts like Components and Hooks. Dioxus is renderer agnostic, so you can use it with any renderer that is available, for example, there are renderers for web (WASM), Desktop (Webview), TUI, Blitz (WGPU), LiveView, Freya (non-official Skia renderer), etc.

🔗Components

Components are the building blocks of your application. They encapsulate contain the declared UI of your app. In Dioxus they are expressed in the form of functions.

fn Component(cx: Scope) -> Element {
    render!(
        div {
            "Hello World"
        }
    )
}

The function takes a Scope as an argument and returns an Element.

  • Scopes are unique to their components. This is why you will need to pass it to Hooks when calling them.
  • Element is the representation of your UI that will be generated by render!() or cx.render(rsx!()).

🔗RSX

RSX is a special syntax used to declare the UI that integrates with Rust.

fn app(cx: Scope) -> Element {
    render!(
        div {} // A div element
        Component {} // The `CoolComponent` component
    )
}

fn CoolComponent(cx: Scope) -> Element {
    let some_value = 123;

    render!(
        button { // A button element
            width: "100%", // An attribute
            onclick: |evt| println!("{evt:?}"), // An event listener
            p { // A nested element
                height: "20px",
                "Value is {some_value}" // A text node
            }
        }
    )
}

🔗Local State

Components can have local state, this is useful for storing data that is only relevant to the component itself. This can be archived by using the use_state or use_ref hooks.

🔗use_state

The use_state hook is used to store data that will change over time. It takes an initial value and returns a UseState object.

Every time you modify the state, the component will be re-run.

fn Component(cx: Scope) -> Element {
    let counter = use_state(cx, || 0);

    // This will be printed every time the state changes
    println!("{counter}");

    let onclick = |_| {
        // You can use the normal operators to update the state
        counter += 1;

        // Or you can use the set and get functions
        counter.set(counter.get() + 1);

        // You can also use the modify function
        counter.modify(|counter| counter + 1);

        // Or the with_mut function
        counter.modify(|counter| *counter += 1);
    };

    render!(
        button {
            onclick: onclick,
            "{counter}"
        }
    )
}

🔗use_ref

The use_ref hook is used to store data that will change over time. It takes an initial value and returns a UseRef object. The difference with use_state is that use_ref can be modified without re-running the component, which can be useful when you need to hold a value across renders that is used asynchronously.

fn Component(cx: Scope) -> Element {
    let counter = use_ref(cx, || 0);

    let onclick = |_| {
        // Silently increment the counter, this will not make the component re-run
        counter.write_silent() = counter.read() + 1;
    };

    let double_and_update = |_| {
        // Double the counter, this will make the component re-run
        counter.write() = counter.read() * 2;
    };

    render!(
        button {
            onclick: increment_silently,
            "Increment Silently"
        }
        p {
            "{counter}"
        }
        button {
            onclick: double_and_update,
            "Double and update"
        }
    )
}

🔗Shared State

Shared state is used to store data that is shared across components. This can be archived by using the use_shared_state and use_shared_state_provider hooks.

🔗use_shared_state_provider

You can use use_shared_state_provider to share a certain value all the way down to it's children.


#[derive(Debug, Clone)]
enum Theme {
    Light,
    Dark
}

fn App(cx: Scope) -> Element {
    // All the children of this component will have access to this shared state
    // See `use_shared_state` on how to actually use it
    use_shared_state_provider(cx, || Theme::Dark);

    render!(
         CoolChild {}
    )
}

🔗use_shared_state

You can use use_shared_state to access a value shared by one of the component ancestors.


#[derive(Debug, Clone)]
enum Theme {
    Light,
    Dark
}

fn CoolChild(cx: Scope) -> Element {
    // Access the shared state
    let theme = use_shared_state::<Theme>(cx);

    // You can read it with the read function
    let is_light = theme.read() == Theme::Light;

    let onclick = |_| {
        // Or modify it with the write function
        theme.write() = match theme.read() {
            Theme::Light => Theme::Dark,
            Theme::Dark => Theme::Light
        };
    };

    render!(
         button {
            onclick: onclick,
            "Click to toggle the theme: {theme:?}"
         }
    )
}

🔗Global State

🔗Providers

Providers are usually Hooks/Components that provide a certain value down to all it's children

Example:


fn App(cx: Scope) -> Element {
    render!(
        // Only Components that are desdendants of ThemeProvider will have access to the state
        ThemeProvider {
            CoolContainer { // ✅ Will have access
                CoolButton { // ✅ Will have access
                    "Hello World"
                }
            }
        }
        NotCoolText { // ❌ Will not have access
            "This text is not cool"
        }
    )
}

🔗Props

Props are parameters for Components, but they are different to normal function arguments:

  • Can be optional
  • Can have default types
  • Can be memoized

Props as struct:

#[derive(Props)]
struct CoolProps {
    value: i32
}

fn CoolValue(cx: Scope<CoolProps>) -> Element {
    let value = cx.props.value;
    render!(
        p {
            "Value: {value}"
        }
    )
}

fn Component(cx: Scope) -> Element {
    let value = use_state(cx, || 0);
    render!(
        button {
            onclick: |evt| {
                value += 1;
            },
            CoolValue {
                value: *value.get()
            }
        }
    )
}

Inline props:

#[inline_props]
fn CoolValue(cx: Scope, value: i32) -> Element {
    render!(
        p {
            "Value: {value}"
        }
    )
}

fn Component(cx: Scope) -> Element {
    let value = use_state(cx, || 0);
    render!(
        button {
            onclick: |evt| {
                value += 1;
            },
            CoolValue {
                value: *value.get()
            }
        }
    )
}

Props with event handlers:

struct CoolEvent {
    value: i32
}

#[derive(Props)]
struct CoolProps<'a> {
    value: i32,
    oncoolevent: EventHandler<'a, CoolEvent>
}

fn CoolValue(cx: Scope<CoolProps>) -> Element {
    let value = cx.props.value;
    render!(
        button {
            onclick: |_| cx.props.oncoolevent.call(CoolEvent { value: value + 1}),
            "Value: {value}"
        }
    )
}

fn Component(cx: Scope) -> Element {
    let value = use_state(cx, || 0);
    render!(
        CoolValue {
            oncoolevent: |evt| {
                value.set(evt.value)
            },
            value: *value.get()
        }
    )
}

Optional props:


#[derive(Props)]
struct CoolProps {
    #[props(optional)]
    value: Option<i32>,
}

fn CoolValue(cx: Scope<CoolProps>) -> Element {
    let value = cx.props.value.unwrap_or(123);
    render!(
        p {
            "Value: {value}"
        }
    )
}

fn Component(cx: Scope) -> Element {
    render!(
        CoolValue { }
    )
}

Optional props with default values:

#[derive(Props)]
struct CoolProps<'a> {
    #[props(optional, default = 123)]
    value: i32,
}

fn CoolValue(cx: Scope<CoolProps>) -> Element {
    let value = cx.props.value;
    render!(
        p {
            "Value: {value}"
        }
    )
}

fn Component(cx: Scope) -> Element {
    render!(
        CoolValue { }
    )
}

Auto convert prop values:

#[derive(Props)]
struct CoolProps<'a> {
    #[props(into)]
    text: String,
}

fn CoolValue(cx: Scope<CoolProps>) -> Element {
    let text = cx.props.text;
    render!(
        p {
            "text: {text}"
        }
    )
}

fn Component(cx: Scope) -> Element {
    render!(
        CoolValue {
            text: "Hello World"
        }
    )
}

🔗Hooks

Hooks are functions that allow you to do certain things in your components. They are usually prefixed with use_. Because they are simple functions you can create your own hooks:

#[derive(Clone)]
struct UseCounter {
    state: UseState<i32>
}

impl UseCounter {
    fn increment(&self) {
        self.state.set(self.state.get() + 1);
    }

    fn value(&self) -> i32 {
        *self.state.get()
    }
}

fn use_counter(cx: ScopeState) -> UseCounter {
    let state = use_state(cx, || 0);
    UseCounter { state }
}

fn Component(cx: Scope) -> Element {
    let counter = use_counter();

    render!(
        button {
            onclick: |_| counter.increment(),
            "{counter.value()}"
        }
    )
}

🔗Composition

Composition is the ability to combine multiple components to form a connected UI flow. Because you use components directly it becomes easier to create more complex scenarios.

Without composition 🥲:

struct CoolOption(String, bool);

fn app() -> Element {
    render!(
        CoolDropdown {
            options: vec![
                CoolOption("Option 1".to_string(), false),
                CoolOption("Option 2".to_string(), true),
                CoolOption("Option 3".to_string())
            ]
        }
    )
}

With composition 😎:

fn app() -> Element {
    render!(
        CoolDropdown {
            CoolDropdownOption {
                big: true,
                "Option 1"
            }
            CoolDropdownOption {
                "Option 2"
            }
            CoolDropdownOption {
                "Option 3"
            }
        }
    )
}

🔗Memoization

Memoization (or caching) is used to avoid re-running components unnecessarily.

Without memoization 🥲:


#[inline_props]
fn CoolComponent<'a>(cx: Scope<'a>, name: &'a str) -> Element<'a> {
    render!(
        rect {
            "Hello, {name}!"
        }
    )
}

fn app(cx: Scope) -> Element {

    // `CoolComponent` will re-run whenever the component `app` re-runs,
    // which is always as it's not an owned value.

    render!(
        CoolComponent {
            name: "World"
        }
    )
}

With memoization 😎:


#[inline_props]
fn CoolComponent(cx: Scope, name: String) -> Element {
    render!(
        rect {
            "Hello, {name}!"
        }
    )
}

fn app(cx: Scope) -> Element {

    // Even when this component `app` re-runs multiple times,
    // the `CoolComponent` will only run once and re-run when the `name` prop changes,
    // which is essentially never as it's harcoded to "World" and is an owned value.

    render!(
        CoolComponent {
            name: "World".to_string()
        }
    )
}