signal API¶
Use signal(...) to create writable reactive state in new code:
from reaktiv import signal count = signal(0) count.update(lambda value: value + 1) print(count()) # 1
Naming
Prefer signal(...) in application code. Signal is the concrete writable
signal class and remains supported for existing class-style code, type
references, and advanced use.
reaktiv.signal
¶
Writable Signal implementation.
Migrated to Edge-based dependency tracking and minimal scheduler.
signal = Signal
module-attribute
¶
Lowercase factory-style spelling for user code that prefers
function-based primitive creation. Equivalent to Signal.
ComputeSignal
¶
Bases: Signal[T]
A computed signal that derives its value from other signals.
ComputeSignal automatically tracks dependencies on other signals and recomputes its value when any dependency changes. Computations are lazy and cached - they only run when accessed and dependencies have changed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
compute_fn
|
Callable[[], T]
|
A function that computes the signal's value from other signals |
required |
equal
|
Optional[Callable[[T, T], bool]]
|
Optional custom equality function for change detection |
None
|
Examples:
Basic computed signal:
from reaktiv import computed, signal
first_name = signal("John")
last_name = signal("Doe")
@computed
def full_name():
return f"{first_name()} {last_name()}"
print(full_name()) # "John Doe"
first_name.set("Jane")
print(full_name()) # "Jane Doe"
Lazy computation:
from reaktiv import computed, signal
x = signal(10)
y = signal(20)
def expensive_computation():
print("Computing...")
return x() * y()
result = computed(expensive_computation)
# Nothing happens yet - computation is lazy
# First access - computation runs
print(result()) # Prints: "Computing..." then "200"
# Second access - no computation (cached)
print(result()) # Just prints "200"
# Change a dependency
x.set(5)
# Next access will recompute
print(result()) # Prints: "Computing..." then "100"
Decorator pattern:
from reaktiv import computed, signal
price = signal(100)
quantity = signal(2)
@computed
def total():
return price() * quantity()
print(total()) # 200
Error handling:
from reaktiv import computed, signal
x = signal(10)
# Computed signal with potential error
@computed
def result():
return 100 / x()
print(result()) # 10.0 (100 / 10)
# Set x to 0, causing division by zero
x.set(0)
# Exception is propagated to caller
try:
print(result())
except ZeroDivisionError as e:
print(f"Error: {e}")
# After fixing, computation works again
x.set(5)
print(result()) # 20.0 (100 / 5)
Note
Computed signals are lazy - they only compute when accessed and cache the result until dependencies change. When a computation raises an exception, it is propagated to the caller for flexible error handling.
ComputedDescriptor
¶
Bases: PerInstanceDescriptor[ComputeSignal[T]], Generic[T]
Descriptor that creates one ComputeSignal per owner instance.
ReadonlySignal
¶
Bases: Generic[T]
A readonly wrapper around a Signal that prevents modification.
ReadonlySignal provides only read access (get() and __call__()) to the
underlying signal, preventing direct modification. Useful for encapsulation
and API design.
Note
You typically don't create ReadonlySignal directly. Use Signal.as_readonly() instead.
Examples:
from reaktiv import signal
_counter = signal(0)
counter = _counter.as_readonly()
# Can read
print(counter()) # 0
# Cannot write
# counter.set(5) # AttributeError
get()
¶
Get the current value of the underlying signal.
Returns:
| Type | Description |
|---|---|
T
|
The current value |
Signal
¶
Bases: Generic[T]
A reactive writable signal that notifies dependents when its value changes.
Signal is the core building block in reaktiv. It creates a container for values that can change over time and automatically notify effects and computed signals that depend on it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
T
|
The initial value of the signal |
required |
equal
|
Optional[Callable[[T, T], bool]]
|
Optional custom equality function to determine if two values should be
considered equal. By default, identity ( |
None
|
Examples:
Basic usage:
from reaktiv import signal
# Create a signal with an initial value
counter = signal(0)
# Get the current value
value = counter() # 0
# Set a new value
counter.set(5)
# Update using a function
counter.update(lambda x: x + 1) # Now 6
Custom equality:
from reaktiv import signal
# Custom equality function for dictionaries
def dict_equal(a, b):
return isinstance(a, dict) and isinstance(b, dict) and a == b
user = signal({"name": "Alice", "age": 30}, equal=dict_equal)
# This won't trigger updates (same key-value pairs)
user.set({"name": "Alice", "age": 30})
# This will trigger updates (different age)
user.set({"name": "Alice", "age": 31})
Readonly wrapper:
from reaktiv import signal
# Internal writable signal
_counter = signal(0)
# Expose readonly view
counter = _counter.as_readonly()
def increment():
_counter.update(lambda x: x + 1)
print(counter()) # 0
increment()
print(counter()) # 1
# counter.set(5) # Error: ReadonlySignal has no 'set' method
__init__(value, *, equal=None)
¶
Initialize a new Signal with an initial value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
T
|
The initial value of the signal |
required |
equal
|
Optional[Callable[[T, T], bool]]
|
Optional custom equality function. If provided, it will be used to
determine if a new value is different from the current value. If not
provided, identity comparison ( |
None
|
as_readonly()
¶
Return a readonly wrapper that exposes only read access to this signal.
Useful for encapsulation when you want to share a value but prevent external code from mutating it.
Returns:
| Type | Description |
|---|---|
'ReadonlySignal[T]'
|
A ReadonlySignal that wraps this signal |
Examples:
get()
¶
Get the current value of the signal.
When called within an active effect or computed signal, it establishes a dependency relationship, so changes to this signal will notify the dependent.
Returns:
| Type | Description |
|---|---|
T
|
The current value of the signal |
Examples:
set(new_value)
¶
Set a new value for the signal and notify subscribers if it changed.
A notification is triggered only if the new value is considered different
from the current value. By default, identity comparison (is) is used
unless a custom equality function was provided.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
new_value
|
T
|
The new value to set |
required |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called from within a ComputeSignal computation |
Examples:
update(update_fn)
¶
Atomically update the signal using a function of its current value.
This method is useful for updates that depend on the current value, such as incrementing a counter or modifying an object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
update_fn
|
Callable[[T], T]
|
A function that takes the current value and returns the new value |
required |
Examples:
reaktiv.Signal
¶
A reactive writable signal that notifies dependents when its value changes.
Signal is the core building block in reaktiv. It creates a container for values that can change over time and automatically notify effects and computed signals that depend on it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
T
|
The initial value of the signal |
required |
equal
|
Optional[Callable[[T, T], bool]]
|
Optional custom equality function to determine if two values should be
considered equal. By default, identity ( |
None
|
Examples:
Basic usage:
from reaktiv import signal
# Create a signal with an initial value
counter = signal(0)
# Get the current value
value = counter() # 0
# Set a new value
counter.set(5)
# Update using a function
counter.update(lambda x: x + 1) # Now 6
Custom equality:
from reaktiv import signal
# Custom equality function for dictionaries
def dict_equal(a, b):
return isinstance(a, dict) and isinstance(b, dict) and a == b
user = signal({"name": "Alice", "age": 30}, equal=dict_equal)
# This won't trigger updates (same key-value pairs)
user.set({"name": "Alice", "age": 30})
# This will trigger updates (different age)
user.set({"name": "Alice", "age": 31})
Readonly wrapper:
from reaktiv import signal
# Internal writable signal
_counter = signal(0)
# Expose readonly view
counter = _counter.as_readonly()
def increment():
_counter.update(lambda x: x + 1)
print(counter()) # 0
increment()
print(counter()) # 1
# counter.set(5) # Error: ReadonlySignal has no 'set' method
__init__(value, *, equal=None)
¶
Initialize a new Signal with an initial value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
T
|
The initial value of the signal |
required |
equal
|
Optional[Callable[[T, T], bool]]
|
Optional custom equality function. If provided, it will be used to
determine if a new value is different from the current value. If not
provided, identity comparison ( |
None
|
get()
¶
Get the current value of the signal.
When called within an active effect or computed signal, it establishes a dependency relationship, so changes to this signal will notify the dependent.
Returns:
| Type | Description |
|---|---|
T
|
The current value of the signal |
Examples:
set(new_value)
¶
Set a new value for the signal and notify subscribers if it changed.
A notification is triggered only if the new value is considered different
from the current value. By default, identity comparison (is) is used
unless a custom equality function was provided.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
new_value
|
T
|
The new value to set |
required |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called from within a ComputeSignal computation |
Examples:
update(update_fn)
¶
Atomically update the signal using a function of its current value.
This method is useful for updates that depend on the current value, such as incrementing a counter or modifying an object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
update_fn
|
Callable[[T], T]
|
A function that takes the current value and returns the new value |
required |
Examples:
as_readonly()
¶
Return a readonly wrapper that exposes only read access to this signal.
Useful for encapsulation when you want to share a value but prevent external code from mutating it.
Returns:
| Type | Description |
|---|---|
'ReadonlySignal[T]'
|
A ReadonlySignal that wraps this signal |
Examples:
reaktiv.ReadonlySignal
¶
A readonly wrapper around a Signal that prevents modification.
ReadonlySignal provides only read access (get() and __call__()) to the
underlying signal, preventing direct modification. Useful for encapsulation
and API design.
Note
You typically don't create ReadonlySignal directly. Use Signal.as_readonly() instead.
Examples:
from reaktiv import signal
_counter = signal(0)
counter = _counter.as_readonly()
# Can read
print(counter()) # 0
# Cannot write
# counter.set(5) # AttributeError
get()
¶
Get the current value of the underlying signal.
Returns:
| Type | Description |
|---|---|
T
|
The current value |