Reconciliation: How Components Render
We've covered extensively how components and their properties are defined, but we haven't yet covered how they actually work. Dioxus components are not normal Rust functions. While technically possible to call them like regular Rust functions, components rely on an active Dioxus runtime to function properly.
To use components properly, it's important to understand the fundamentals of how state flows, how elements are created, and how state is stored. We are going to outline how state works here, but state can be complex so we've given it its own chapter.
Components Render
In Dioxus, the term "rendering" refers to the process that Dioxus uses to call your component functions and draw elements to the screen. When you call dioxus::launch
, Dioxus sets up the app's runtime and then calls the provided initial component to create the initial Element
. This element declares styles, layouts, children, and event listeners. Dioxus converts your elements into draw calls and converts your event listeners into native event handlers.
Because Dioxus uses a "virtual" tree, the elements in your RSX tree are not actual handles to "real" nodes in the renderer. For example, the Dioxus Element
type is not a full HTMLElement object. When Dioxus receives your initial Element, it converts your virtual elements into real elements and draw calls using a platform-specific renderer.
Components Rerender
Components will be rerun when the state they depend on changes. After the initial Element has been drawn with the platform-specific renderer, Dioxus listens for events on your elements. When an event is received, the corresponding event listeners are called, and your code has an opportunity to mutate state. Mutating state is the primary mechanism by which Dioxus knows to run your component functions again and look for changes in the tree.
Dioxus considers state to have been changed in two situations:
- The component's properties change, as determined its
PartialEq
implementation - Internal state the component depends on changes (e.g.
signal.write()
) and an "update" is scheduled
// When the name property changes, the component will rerender #[component] fn Button(name: String) -> Element { let mut count = use_signal(|| 0); log!("Component rerendered with name: {name} count: {count}"); rsx! { h1 { "Hello, {name}!" } // MyComponent reads the `count` signal, so it will rerender // whenever `count` changes. "Count: {count}" button { // When the button is clicked, it increments the count signal onclick: move |_| count += 1, "Increment" } } }
Hello, World!
Count: 0After a component runs again, Dioxus will compare the old Element
and the new Element
to look for changes. The Dioxus runtime will identify the least amount of draw calls required to change the old UI to match your desired UI. This comparison process is called "diffing". Dioxus optimizes diffing by only comparing dynamic parts of the RSX, so static elements are not checked for changes (see this blog post for details). This entire loop - Render, Display, Listen, Mutate - is called "reconciliation" and Dioxus has one of the most performant implementations of any UI framework.
Components are Functions of State
Components are a pure function of your current application state in the form fn(State) -> Element
. They read state from various sources like props, hooks, or context and return a view of the current UI as an Element
.
We have already seen how components map the props state into UI, but state can also come from the component itself in the form of hooks. For example, we can use a signal to keep track of a count in our component. The component defines the mapping from the current state of the signal to the UI that should be rendered:
#[component] pub fn MyStatefulComponent() -> Element { let mut count = use_signal(|| 0); rsx! { div { h1 { "Count: {count}" } button { onclick: move |_| count += 1, "Increment" } } } }
Count: 0
When building Dioxus apps, it's important to understand that your UI code is a declaration of what you want your UI to be - it does not contain any logic on how to update the UI to get there. Dioxus itself is responsible for making the UI match your desired input.
Components are Pure Functions
The body of a component must be pure. Pure functions always return the same output for the same input and do not have side effects. For example, this double function is pure:
fn double(x: i32) -> i32 { x * 2 }
If you call double(2)
, it will always return 4
.
However, this function is not pure because it modifies external state:
static GLOBAL_COUNT: AtomicI32 = AtomicI32::new(0); fn increment_global_count() -> i32 { GLOBAL_COUNT.fetch_add(1, Ordering::SeqCst) }
When you call increment_global_count()
the first time, it will return 0
, but the next time you call it, it will return 1
. This function has side effects because it modifies the global state.
In addition to global variables, context and hooks are also external state in components. Components shouldn't modify state from context or hooks while rendering:
#[component] fn MyImpureComponent() -> Element { let mut count = use_signal(|| 0); // ❌ Modifying the count signal from a hook is a side effect. // Dioxus may try to rerender the component with the new value, // which can lead to unexpected behavior or infinite loops. count += 1; rsx! { div { h1 { "Count: {count}" } } } }
Side effects that modify state should be placed in event handlers or effects which run after the component has rendered. This ensures that the component's output is stable and predictable.
#[component] fn MyPureComponent() -> Element { let mut count = use_signal(|| 0); rsx! { div { h1 { "Count: {count}" } button { // ✅ Event handlers can modify state and have side effects. onclick: move |_| count += 1, "Increment" } } } }
If you find yourself writing components that are not pure, then you are likely misusing or misunderstanding the reactive paradigm. Mutations should be placed either in event handlers as a response to user input, or in long running async tasks as a response to background processing.
Similar to React
If you're familiar with libraries like ReactJS, then this paradigm is familar to you. Dioxus borrows many ideas from React and your existing knowledge will be extremely helpful. If anything here is confusing to you, check out the React docs or do some extra research on React's reactivity system.