reaktiv: Reactive Signals for Python¶
Reactive declarative state management for Python — automatic dependency tracking and reactive updates for your application state.
Links¶
- Docs: https://reaktiv.bui.app/
- Deep Dive Article: https://bui.app/the-missing-manual-for-signals-state-management-for-python-developers/
- GitHub: https://github.com/buiapp/reaktiv
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.
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()
The first run downloads Pyodide and installs reaktiv; later runs are cached by
the browser.
Three Building Blocks¶
Signalstores changing state. Read it by calling it and update it withset()orupdate().Computeddescribes derived state. Dependencies are discovered automatically; results are lazy and cached.Effectreacts 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:
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()
Every model instance owns independent fields, computed values, effects, linked
state, and resources. Model-owned work is retained automatically and cleaned up
by dispose().
Where To Go Next¶
- New to signals? Start with the Quick Start.
- Want the mental model? Read Core Concepts.
- Building application state? See ReactiveModel.
- Working with async data? Read the Resource Guide.
- Looking for complete programs? Browse the Examples.
- Need a class or method? Open the API Reference.