State and Buttons

Static text is nice, but apps need interactivity. Let's add state.

use telex::prelude::*;

struct App;

impl Component for App {
    fn render(&self, cx: Scope) -> View {
        let count = state!(cx, || 0);

        View::vstack()
            .child(View::text(format!("Count: {}", count.get())))
            .child(
                View::button()
                    .label("Increment")
                    .on_press(with!(count => move || count.update(|n| *n += 1)))
                    .build()
            )
            .build()
    }
}

Run with: cargo run -p telex-tui --example 02_counter

The two macros you need

Telex state uses two macros that work together:

state! - Create state

let count = state!(cx, || 0);

Creates a piece of reactive state with an initial value. Works everywhere - no restrictions on where you call it (unlike React hooks).

with! - Attach callbacks

.on_press(with!(count => move || count.update(|n| *n += 1)))

Captures state for use in callbacks. Handles the cloning automatically.

That's it. Use state! to create, with! to attach.

Reading and writing

count.get()                  // read current value
count.set(5)                 // set new value
count.update(|n| *n += 1)    // modify in place

When state changes, Telex re-renders automatically.

Multiple states

Create as many as you need:

let count = state!(cx, || 0);
let name = state!(cx, || String::new());
let enabled = state!(cx, || true);

Buttons

View::button()
    .label("Click me")
    .on_press(callback)
    .build()

Buttons use the builder pattern. Configure with methods, then .build() to finish.

Example: Multiple buttons

let count = state!(cx, || 0);

View::vstack()
    .child(View::text(format!("Count: {}", count.get())))
    .child(
        View::hstack()
            .child(
                View::button()
                    .label("-")
                    .on_press(with!(count => move || count.update(|n| *n -= 1)))
                    .build()
            )
            .child(
                View::button()
                    .label("+")
                    .on_press(with!(count => move || count.update(|n| *n += 1)))
                    .build()
            )
            .build()
    )
    .build()

Each button gets its own with! callback, but they share the same count state.

Under the hood

state! and with! are just conveniences:

// state! expands to:
let count = cx.use_state_keyed::</* auto key */, _>(|| 0);

// with! expands to:
let count_clone = count.clone();
move || count_clone.update(...)

Footnote: There's also cx.use_state_keyed::<Key, _>(|| init) for when you need shared state across call sites — multiple places in your code that access the same state by using the same key type. For everything else, state!() is the way to go.

Next: Styling