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.
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.
#[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.
text("A stable item")
.key("item-42")