Source Code 🐙
Components
State
Hooks
Patterns
Dioxus Standard Library
Renderers
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 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
.
render!()
or cx.render(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
}
}
)
}
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 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:?}"
}
)
}
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 are parameters for Components, but they are different to normal function arguments:
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 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 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 (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()
}
)
}