Documentation Index
Fetch the complete documentation index at: https://mintlify.com/DioxusLabs/dioxus/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The VirtualDom is the core engine that manages component state, tracks updates, and generates mutations for renderers. It implements a concurrent, async-aware virtual DOM with support for suspense, error boundaries, and efficient diffing.
VirtualDom Structure
pub struct VirtualDom {
scopes: Slab<ScopeState>,
dirty_scopes: BTreeSet<ScopeOrder>,
runtime: Rc<Runtime>,
resolved_scopes: Vec<ScopeId>,
rx: UnboundedReceiver<SchedulerMsg>,
}
Creating a VirtualDom
Basic Creation
use dioxus::prelude::*;
fn app() -> Element {
rsx! { div { "Hello, world!" } }
}
let mut dom = VirtualDom::new(app);
With Props
use dioxus::prelude::*;
#[derive(PartialEq, Props, Clone)]
struct AppProps {
title: String,
}
fn app(props: AppProps) -> Element {
rsx! { h1 { "{props.title}" } }
}
let mut dom = VirtualDom::new_with_props(
app,
AppProps { title: "My App".to_string() },
);
Prebuilt VirtualDom
// Create and immediately build the initial tree
let mut dom = VirtualDom::prebuilt(app);
With Root Context
#[derive(Clone)]
struct AppConfig {
api_url: String,
}
let dom = VirtualDom::new(app)
.with_root_context(AppConfig {
api_url: "https://api.example.com".to_string(),
});
Rendering
Initial Render
use dioxus::prelude::*;
let mut dom = VirtualDom::new(app);
let mut mutations = Mutations::default();
// Generate the initial UI
dom.rebuild(&mut mutations);
// Apply mutations to your renderer
for edit in mutations.edits {
// Handle edit
}
Render to Vec
// For testing or simple cases
let mutations = dom.rebuild_to_vec();
In-place Rebuild
// Rebuild without collecting mutations
dom.rebuild_in_place();
Update Cycle
use dioxus::prelude::*;
let mut dom = VirtualDom::new(app);
let mut mutations = Mutations::default();
dom.rebuild(&mut mutations);
loop {
// Wait for work
dom.wait_for_work().await;
// Render all dirty components
mutations.edits.clear();
dom.render_immediate(&mut mutations);
// Apply mutations
for edit in &mutations.edits {
// Handle edit
}
}
Render to Vec
// For testing
let mutations = dom.render_immediate_to_vec();
Event Loop
A complete event loop integrating VirtualDom with a renderer:
use dioxus::prelude::*;
#[component]
fn app() -> Element {
let mut count = use_signal(|| 0);
rsx! {
button {
onclick: move |_| count += 1,
"Count: {count}"
}
}
}
async fn run() {
let mut dom = VirtualDom::new(app);
let mut mutations = Mutations::default();
// Initial render
dom.rebuild(&mut mutations);
apply_mutations(&mutations);
loop {
tokio::select! {
// Wait for component updates
_ = dom.wait_for_work() => {
mutations.edits.clear();
dom.render_immediate(&mut mutations);
apply_mutations(&mutations);
}
// Handle user events
event = wait_for_event() => {
let evt = Event::new(event.data, true);
dom.runtime().handle_event(
&event.name,
evt,
event.element_id,
);
}
}
}
}
Suspense
Wait for Suspense
use dioxus::prelude::*;
let mut dom = VirtualDom::new(app);
// Build initial tree
dom.rebuild_in_place();
// Wait for all suspense boundaries to resolve
dom.wait_for_suspense().await;
// Now the tree is fully rendered
let mutations = dom.rebuild_to_vec();
Check Suspended Tasks
if dom.suspended_tasks_remaining() {
println!("Still waiting for suspended tasks");
}
Render During Suspense
// Render scopes that are ready, even during suspense
let resolved = dom.render_suspense_immediate().await;
for scope_id in resolved {
println!("Resolved scope: {:?}", scope_id);
}
Scope Management
Access Scopes
// Get a specific scope
if let Some(scope) = dom.get_scope(scope_id) {
let node = scope.root_node();
// Work with scope
}
// Get the root scope
let root = dom.base_scope();
Mark Dirty
// Manually mark a scope for re-render
dom.mark_dirty(scope_id);
// Mark all scopes dirty
dom.mark_all_dirty();
Run in Runtime Context
dom.in_runtime(|| {
// Code runs with runtime active
let signal = use_signal(|| 0);
});
Run in Scope
dom.in_scope(scope_id, || {
// Code runs in this scope's context
});
Context Injection
Provide Root Context
#[derive(Clone)]
struct Config {
debug: bool,
}
// During creation
let dom = VirtualDom::new(app)
.with_root_context(Config { debug: true });
// After creation
dom.provide_root_context(Config { debug: false });
// Type-erased context
dom.insert_any_root_context(Box::new(Config { debug: true }));
Runtime Access
let runtime = dom.runtime();
// Handle events
let event = Event::new(event_data, true);
runtime.handle_event("onclick", event, element_id);
// Spawn tasks
runtime.spawn(async {
// Async work
});
Diffing Algorithm
The VirtualDom uses an efficient diffing algorithm that:
- Templates are compared by pointer - Static templates are only created once
- Dynamic nodes are diffed recursively - Only changed parts update
- Keys enable list optimization - Reordering lists is efficient
Example: List Diffing
#[component]
fn TodoList(todos: Vec<Todo>) -> Element {
rsx! {
ul {
// Keys help diff algorithm track items
for todo in todos {
li { key: "{todo.id}",
"{todo.text}"
}
}
}
}
}
Mutations
The Mutations struct contains the list of edits to apply:
pub struct Mutations {
pub edits: Vec<Mutation>,
pub templates: Vec<Template>,
}
pub enum Mutation {
CreateElement { tag: &'static str, id: ElementId },
CreateTextNode { text: String, id: ElementId },
SetAttribute { name: &'static str, value: AttributeValue, id: ElementId },
RemoveAttribute { name: &'static str, id: ElementId },
AppendChildren { id: ElementId, m: usize },
ReplaceWith { id: ElementId, m: usize },
Remove { id: ElementId },
// ... more variants
}
Processing Mutations
use dioxus::prelude::*;
struct MyRenderer;
impl WriteMutations for MyRenderer {
fn append_children(&mut self, id: ElementId, m: usize) {
// Append m children from stack to element
}
fn create_element(&mut self, tag: &str, id: ElementId) {
// Create element and push to stack
}
fn create_text_node(&mut self, text: &str, id: ElementId) {
// Create text node and push to stack
}
// ... implement other methods
}
Advanced Features
Process Events
// Process all queued events synchronously
dom.process_events();
Wait for Work
// Async wait for any work (events, tasks, signals)
dom.wait_for_work().await;
Check for Dirty Scopes
if dom.has_dirty_scopes() {
// Render needed
}
Testing
use dioxus::prelude::*;
#[test]
fn test_component() {
fn app() -> Element {
rsx! { div { "Hello" } }
}
let mut dom = VirtualDom::new(app);
let mutations = dom.rebuild_to_vec();
// Assert mutations are correct
assert_eq!(mutations.edits.len(), 3);
}
Async Testing
#[tokio::test]
async fn test_suspense() {
fn app() -> Element {
let resource = use_resource(|| async {
// Fetch data
});
rsx! {
match resource() {
Some(data) => rsx! { div { "{data}" } },
None => rsx! { div { "Loading..." } },
}
}
}
let mut dom = VirtualDom::new(app);
dom.rebuild_in_place();
dom.wait_for_suspense().await;
let mutations = dom.rebuild_to_vec();
// Assert rendered correctly
}
Memoization
// Components with PartialEq props are memoized
#[derive(Props, PartialEq, Clone)]
struct ExpensiveProps {
data: Vec<i32>,
}
#[component]
fn Expensive(props: ExpensiveProps) -> Element {
// Only re-renders when props.data changes
rsx! { div { "Rendered" } }
}
Keys for Lists
// Always use keys for dynamic lists
rsx! {
for item in items {
div { key: "{item.id}", "{item.name}" }
}
}
See Also