Reactive State¶
FluxUI's reactive state system makes it easy to build dynamic UIs. When state changes, only the components that depend on that state re-render — nothing more.
ui.state(initial) — ReactiveVar¶
The fundamental building block. Creates a reactive variable scoped to the current page render (i.e., to the current session's page call).
count = ui.state(0) # ReactiveVar[int]
name = ui.state("Alice") # ReactiveVar[str]
items = ui.state([1, 2, 3]) # ReactiveVar[list]
Reading the value¶
Writing the value¶
Peeking without subscription¶
Use peek() inside on_change handlers to avoid a component self-subscribing
to its own input:
name = ui.state("")
def on_change(v):
# BAD: name.value — would subscribe this handler's component to 'name'
# GOOD: name.peek() reads without subscribing, then we set the new value
if v != name.peek():
name.set(v)
ui.TextInput(label="Name", value=name, on_change=on_change)
API summary¶
| Method / Property | Description |
|---|---|
.value |
Read current value (subscribes if in render context) |
.get() |
Identical to .value |
.peek() |
Read without subscribing |
.set(v) |
Replace value; notifies subscribers if changed |
.update(fn) |
Atomic set(fn(current_value)) |
ui.computed(fn) — ComputedVar¶
A derived read-only reactive value that automatically re-evaluates when its dependencies change.
first = ui.state("Alice")
last = ui.state("Smith")
full_name = ui.computed(lambda: f"{first.value} {last.value}")
ui.Text(lambda: full_name.value) # re-renders when first or last changes
- Auto-tracking:
ComputedVarrunsfnonce to discover whichReactiveVarinstances it reads, then subscribes to them all. - Lazy evaluation:
fnis not re-run until.valueis read after a dependency changes. - Read-only: Calling
.set()on aComputedVarraisesAttributeError.
Complex computed values¶
rows = ui.list_state([{"name": "Alice", "active": True}, ...])
query = ui.state("")
visible = ui.computed(lambda: [
r for r in rows.value
if query.value.lower() in r["name"].lower()
])
ui.DataGrid(data=visible, columns=[{"key": "name", "sortable": True}])
ui.effect(fn) — EffectHandle¶
Runs a side-effect whenever its reactive dependencies change. The function is called once immediately to capture initial dependencies.
Stopping an effect¶
Async effects¶
Currently, effect functions must be synchronous. For async side-effects,
use an on_change handler or trigger async work from a button's on_click.
ui.list_state(initial) — ReactiveVar[ReactiveList]¶
Creates a reactive list where in-place mutations (append, insert, remove,
__setitem__, etc.) automatically notify subscribers.
items = ui.list_state(["apple", "banana"])
items.value.append("cherry") # triggers re-render
items.value[0] = "avocado" # triggers re-render
del items.value[1] # triggers re-render
Don't replace the list
Calling items.set([...]) replaces the ReactiveList wrapper, which works
but loses the reactive mutation tracking. Prefer mutating in-place.
Example
todo = ui.list_state([])
text = ui.state("")
with ui.Column():
with ui.Row():
ui.TextInput(value=text, on_change=text.set, placeholder="New item…")
ui.Button("Add", on_click=lambda: (todo.value.append(text.peek()), text.set("")))
for item in todo.value: # NOTE: read inside lambda/render to subscribe
ui.Text(item)
ui.dict_state(initial) — ReactiveVar[ReactiveDict]¶
Creates a reactive dict where key assignment and deletion trigger re-renders.
config = ui.dict_state({"theme": "light", "lang": "en"})
config.value["theme"] = "dark" # triggers re-render
del config.value["lang"] # triggers re-render
config.value.update({"theme": "light", "lang": "fr"}) # triggers re-render
Example
filters = ui.dict_state({"status": "all", "sort": "name"})
ui.Select(
label="Status",
options=["all", "active", "inactive"],
value=lambda: filters.value["status"],
on_change=lambda v: filters.value.__setitem__("status", v),
)
How Auto-Tracking Works¶
FluxUI uses a ContextVar called _current_render_context to track dependencies:
- Before rendering a component, FluxUI sets
_current_render_contextto a freshset(). - Every
ReactiveVar.get()/.valueread checks_current_render_context. If it is notNone, the variable adds itself to the set. - After rendering, FluxUI compares the new dependency set with the old one and adjusts weakref subscriptions accordingly.
- When a
ReactiveVaris mutated, it walks its subscriber set and calls_mark_dirty()on each subscribed component. Dirty components are flushed as a batch and their HTML patches are sent over the WebSocket.
Reading state outside render¶
If you read .value inside an on_click or on_change callback (not during render),
no subscription is created. That is intentional — callbacks are not re-run automatically;
only render functions are.