Documentation / Fundamentals

Views & state

Compose typed views and connect them to a graph that knows what changed.

Describe the interface

A view describes part of your interface. Compose primitives such as text, button, h_stack, and v_stack into a tree. Use #[view] for ergonomic declarations, implement View::body directly, or use view_fn for the same runtime without macros.

counter.rs
use refract::prelude::*;

#[view]
fn counter(cx: &mut ViewContext<'_>) -> impl View {
    #[state]
    let count: u32 = 0;

    #[action(count)]
    let increment = |cx| {
        count.update(cx.runtime(), |n| *n += 1);
    };

    button(
        text(format!("Count: {}", cx.read(&count))),
        increment,
    )
    .padding(16.0)
}

Read and update bindings

Mounted state is stored by name and type. Keep state declarations unconditional and their types stable. Bindings support set, update, set_if_changed, and field projections through map.

Read a binding through cx.read inside a view to establish a dependency. The runtime invalidates consumers of changed values. Derived::new tracks its own reads; Derived::distinct suppresses downstream updates when the derived result stays equal.

Inside a #[view] function
#[state]
let name: String = String::from("World");

let greeting = format!("Hello, {}", cx.read(&name));
text(greeting)

Keep identity stable

Use .key(item.id) on reorderable children. Keys are sibling-local strings obtained through ToString. Moving a keyed child preserves its mounted identity; changing that identity resets local state.

Use .memo(props) to preserve a subtree across new parent descriptions when its explicit props and provider scope match. Reactive dependencies still update. Include every nonreactive input and callback capture that affects the output in those props.

Keyed child
text("A stable item")
    .key("item-42")