Skip to content

reaktiv: Reactive Signals for Python

reaktiv logo

Reactive declarative state management for Python — automatic dependency tracking and reactive updates for your application state.

Python Version PyPI Version PyPI Downloads Documentation Status License Checked with pyright

Change One Value. Let The Graph Handle The Rest.

A form field changes. Validation, normalized values, totals, availability, and the UI must follow. A query changes. Results, loading state, selection, and background work must stay consistent.

Without a reactive graph, every mutation needs to remember what else to update. With reaktiv, each value declares what it depends on:

query = Signal("")
results = Computed(lambda: search(query()))
summary = Computed(lambda: f"{len(results())} matches")
display = Effect(lambda: render(summary(), results()))

query.set("python")

reaktiv tracks the relationships, invalidates the affected graph, recomputes derived values lazily, and runs the relevant effects.

Try It In Your Browser

Edit the code and press Run. The example runs entirely in your browser with Pyodide.

Editor (session: default) Run
from reaktiv import Computed, Effect, Signal

unit_price = Signal(12.50)
quantity = Signal(1)

total = Computed(lambda: unit_price() * quantity())
receipt = Effect(
    lambda: print(f"{quantity()} item(s): ${total():.2f}")
)

quantity.set(3)
unit_price.set(10.00)

receipt.dispose()
Output Clear

The first run downloads Pyodide and installs reaktiv; later runs are cached by the browser.

Three Building Blocks

  1. Signal stores changing state. Read it by calling it and update it with set() or update().
  2. Computed describes derived state. Dependencies are discovered automatically; results are lazy and cached.
  3. Effect reacts to changes. It reruns when a signal or computed value read by the effect changes.

That small vocabulary scales from a total in a form to branching dependency graphs, async resources, and independently updating threaded workloads.

Why reaktiv?

  • Relationships stay next to the values they define. You do not have to trace every setter and callback to understand what updates what.
  • Derived state stays current. Dependencies are tracked automatically.
  • Updates stay focused. Only affected parts of the graph are invalidated.
  • Computations are lazy and memoized. Work happens when a value is needed.
  • Lifetimes are explicit. Effects and resources can be disposed cleanly.
  • Thread safety is enabled by default. Independent graphs do not share a global execution lock.

See when a reactive graph helps

Organize Application State With ReactiveModel

After learning the primitives, use ReactiveModel to keep a related graph and its lifecycle in one object:

Editor (session: default) Run
from reaktiv import ReactiveModel, computed, effect, field


class ShoppingCart(ReactiveModel):
    unit_price = field(12.50)
    quantity = field(1)
    discount = field(0.0)

    @computed
    def total(self):
        return self.unit_price() * self.quantity() * (1 - self.discount())

    @effect
    def show_total(self):
        print(f"{self.quantity()} item(s): ${self.total():.2f}")


cart = ShoppingCart()
cart.quantity.set(3)
cart.discount.set(0.10)
cart.dispose()
Output Clear

Every model instance owns independent fields, computed values, effects, linked state, and resources. Model-owned work is retained automatically and cleaned up by dispose().

Explore ReactiveModel

Where To Go Next