State and Binding¶
State is the foundation of the declarative API. This chapter covers State, Computed, and two-way binding.
Basic state¶
State[T] holds a value and notifies subscribers when it changes:
"""State basics — create, read, modify, subscribe."""
import libui
from libui.declarative import App, Window, VBox, Label, Button, Entry, State
async def main():
app = App()
name = State("World")
greeting = name.map(lambda n: f"Hello, {n}!")
# Subscribe to changes (prints to console)
name.subscribe(lambda: print(f"Name changed to: {name.value}"))
app.build(
window=Window(
"State Binding",
400,
300,
child=VBox(
Label(text=greeting),
Entry(text=name),
Button("Reset", on_clicked=lambda: name.set("World")),
),
)
)
app.show()
await app.wait()
libui.run(main())
Key points:
State("World")— creates a state with an initial valuename.value = "Python"— setting.valuenotifies all subscribersname.set("value")— equivalent to setting.valuename.subscribe(cb)— registers a callback; returns an unsubscribe function
Computed state¶
Computed is a read-only derived state created with .map():
"""Computed state — derived read-only values that auto-update."""
import libui
from libui.declarative import App, Window, VBox, Form, Label, Button, State
async def main():
app = App()
count = State(0)
doubled = count.map(lambda n: n * 2)
label_text = count.map(lambda n: f"Count: {n}")
doubled_text = doubled.map(lambda n: f"Doubled: {n}")
parity = count.map(lambda n: "even" if n % 2 == 0 else "odd")
parity_text = parity.map(lambda p: f"Parity: {p}")
app.build(
window=Window(
"Computed State",
400,
250,
child=VBox(
Form(
("Count:", Label(text=label_text)),
("Doubled:", Label(text=doubled_text)),
("Parity:", Label(text=parity_text)),
),
Button("Increment", on_clicked=lambda: count.update(lambda n: n + 1)),
Button("Reset", on_clicked=lambda: count.set(0)),
),
)
)
app.show()
await app.wait()
libui.run(main())
Key points:
count.map(fn)— creates aComputedthat auto-updates whencountchangesComputed values can be chained:
a.map(f).map(g)Computed values are read-only — you can’t set them directly
Pass
Computedto widget props likeLabel(text=...)for automatic updates
Two-way binding¶
When you pass a State to a widget that supports user input, you get two-way binding — the widget updates the state, and state changes update the widget:
text = State("")
Entry(text=text) # two-way: typing updates state, state updates entry
Compare with one-way binding:
Label(text=text) # one-way: state -> widget (State)
Label(text=text.map(str)) # one-way: state -> widget (Computed)
Label(text="static") # no binding: plain value
Widgets that support two-way binding: Entry, MultilineEntry, Checkbox, Slider, Spinbox, Combobox, EditableCombobox, RadioButtons.