State Management

Every SableUI component can rebuild its Layout() at any point, so any value that needs to survive that rebuild — and any value that should cause a rebuild when it changes — has to be declared as one of SableUI's state types rather than a plain member variable. There are four of them: State<T>, Ref<T>, Timer, and Interval.

All four are declared as members and constructed with the owning component as their first argument, which registers them so the framework can carry their value across reconciliation:

class MyComponent : public BaseComponent
{
private:
    State<int> count{ this, 0 };
};

A plain member variable (no state wrapper) is still completely valid — it's the right choice for anything recomputed fresh every Layout() call, such as a value handed down from a parent. See the formattedTime member in Your First Application for that pattern. State types exist for the values that don't fit that description.

State<T>

State<T> is the one you'll reach for by default — a value the component owns, that persists across rerenders, and that triggers a rerender when it changes.

State<int> count{ this, 0 };

Read it with .get(), and change it with .set(...):

Text(SableString::Format("Count: %d", count.get()));
// ...
count.set(count.get() + 1);

.set(...) checks the new value against the current one first — if they're equal, nothing happens; if they differ, the value is updated and the owning component is marked dirty for a rerender. This means calling .set() with an unchanged value is cheap and safe to do unconditionally, and it's also why T has to support operator== — State<T> won't compile for a type that can't be compared.

State<T> also supports assignment and implicit conversion, so count = count + 1; and int c = count; both work, though .get()/.set() are the clearer choice in most code.

Ref<T>

Ref<T> looks almost identical to State<T> — same constructor shape, same registration with the owner — but .set(...) does not mark the component dirty. It exists for values that need to survive across rerenders (so they can't just be a plain member) but shouldn't themselves cause a rerender when they change.

The clearest real example is a stored callback. Checkbox keeps the caller's onChange handler in a Ref, not a State:

Ref<std::function<void(bool)>> onChangeCallback{ this, nullptr };

Wrapping that in State<T> instead would be actively wrong here — std::function doesn't have operator==, so it wouldn't compile, and even if it did, reassigning a callback is not something that should trigger a visual rerender on its own.

As a rule: if changing the value should update what's on screen, use State<T>. If it just needs to persist and doesn't affect layout directly, use Ref<T>.

Timer and Interval

Both Timer and Interval hook into the background event scheduler rather than driving a value directly — they're for scheduling when something happens, and you still update a State<T> yourself once it does.

Timer fires once, after a delay:

Timer saveDelay{ this };
// ...
saveDelay.Start(2000); // fires once, 2000ms from now

Interval fires repeatedly, on a fixed period:

Interval tick{ this };
// ...
tick.Start(1000); // fires every 1000ms

Both are checked from OnUpdate(). Interval has a convenience method for this:

void OnUpdate(const UIUpdateContext& ctx) override
{
    if (tick.IsFired(ctx))
    {
        seconds.set(seconds.get() + 1);
    }
}

Timer doesn't expose its own IsFired, so check it against the input context directly using its handle — this form also works for Interval, and is exactly what Interval::IsFired(ctx) does internally:

void OnUpdate(const UIUpdateContext& ctx) override
{
    if (ctx.input.IsFired(saveDelay.GetHandle()))
    {
        DoSave();
    }
}

Both also expose .Stop() and .Reset() — Reset() restarts the countdown/period from now without needing to call .Start() again with the same duration, which is how TextFieldComponent keeps its cursor blinking on a steady rhythm restarted every time a key is pressed, rather than drifting.





Event Handling

Add interactivity with onClick, OnUpdate, and input state

Components

Button, Checkbox, TextField, DatePicker, and Calendar

Examples

See state management in real applications