Skip to content

Reactivity

A store's state is an observ reactive proxy, which means the store plugs into everything observ offers: watch, watch_effect, computed, and UI bindings built on them. Reliev adds no reactivity of its own — it routes all changes (mutations, undos, redos) through the same proxies.

Watching state

from observ import watch

from reliev import Store, mutation

class CounterStore(Store):
    @mutation
    def bump(self):
        self.state["count"] += 1

store = CounterStore({"count": 0})

watcher = watch(
    lambda: store.state["count"],
    lambda value: print(f"Count is now: {value}"),
    sync=True,
    immediate=True,
)

store.bump()   # prints "Count is now: 1"
store.undo()   # prints "Count is now: 0"

Fine-grained notifications

Undo and redo apply patches that touch exactly the locations the original mutation changed — not the whole state. Watchers of unrelated parts of the state therefore stay quiet:

from unittest.mock import Mock

store = CounterStore({"count": 0, "foo": {}})
foo_watcher = watch(lambda: store.state["foo"], Mock(), sync=True)

store.bump()
store.undo()

foo_watcher.callback.assert_not_called()  # "foo" was never touched

Reactive history

The undo/redo stacks themselves are (shallow) reactive, so everything derived from them can be watched too: can_undo, can_redo, undo_context and redo_context. A typical desktop app binds its Edit-menu entries to these four properties and never updates them manually again.

Readonly outside mutations

Because store.state is a readonly proxy outside of mutations, accidental writes from UI code raise immediately instead of silently bypassing the history. Every change is forced through a mutation — which is exactly what keeps undo/redo trustworthy.