Effect API
The Effect class creates side effects that automatically run when their dependencies change. Effects are useful for updating UI elements, logging, API calls, and other operations that respond to state changes.
Basic Usage
Section titled “Basic Usage”from reaktiv import Signal, Effect
# Create a signalcounter = Signal(0)
# Create an effect that runs when counter changescounter_effect = Effect(lambda: print(f"Counter: {counter()}"))# Immediately prints: "Counter: 0"
# When the signal changes, the effect runs automaticallycounter.set(1)# Prints: "Counter: 1"
# Clean up when donecounter_effect.dispose()Creation
Section titled “Creation”Effect(func: Callable[..., Union[None, Coroutine[None, None, None]]]) -> EffectCreates a new effect that automatically runs when its dependencies change.
Parameters
Section titled “Parameters”func: A function or coroutine function to run when dependencies change. Dependencies are automatically tracked when this function accesses signals by calling them. If the function accepts a parameter, it receives anon_cleanupfunction.
Returns
Section titled “Returns”An effect object that manages the execution of the function when dependencies change.
Methods
Section titled “Methods”dispose
Section titled “dispose”dispose() -> NoneDisposes of the effect, removing all dependencies and preventing it from running again.
Note: You should call dispose() when an effect is no longer needed to prevent memory leaks.
Async usage (optional)
Section titled “Async usage (optional)”Recommendation: Prefer synchronous effects for predictable behavior. If you need to integrate with asyncio, spawn background tasks from a sync effect, or (advanced) use an async effect.
import asynciofrom reaktiv import Signal, Effect
counter = Signal(0)
# Recommended: synchronous effect that kicks off async workdef log_counter(): value = counter() async def background(): await asyncio.sleep(0) print(f"Counter value: {value}") asyncio.create_task(background())
logger = Effect(log_counter) # Prints immediately for initial run
counter.set(1)
# Advanced: direct async effect (use with care)# async def log_counter_async():# await asyncio.sleep(0)# print(f"Counter value: {counter()}")# logger = Effect(log_counter_async)Cleanup Functions
Section titled “Cleanup Functions”Effects can register cleanup functions that will be executed before the effect runs again or when it’s disposed:
from reaktiv import Signal, Effect
counter = Signal(0)
def counter_effect(on_cleanup): value = counter() print(f"Counter value: {value}")
# Register a cleanup function def cleanup(): print(f"Cleaning up for value: {value}")
on_cleanup(cleanup)
# Create and schedule the effect with cleanuplogger = Effect(counter_effect)# Prints: "Counter value: 0"
# Update the signalcounter.set(1)# Prints: "Cleaning up for value: 0"# Prints: "Counter value: 1"
# Dispose the effectlogger.dispose()# Prints: "Cleaning up for value: 1"Memory Management
Section titled “Memory Management”Effects are not automatically garbage collected as long as they’re actively tracking dependencies. To prevent memory leaks:
- Keep a reference to your effect as long as you need it
- Call
dispose()when you’re done with the effect - Avoid creating effects inside loops or frequently-called functions without disposing of them
from reaktiv import Signal, Effect
def create_temporary_effect(s): # This effect will only exist while the function runs temp_effect = Effect(lambda: print(f"Value: {s()}")) # ... do something ... temp_effect.dispose() # Clean up properly
# Better pattern for component lifecycleclass MyComponent: def __init__(self, s): self.s = s self.effect_instance = Effect(self._render)
def _render(self): print(f"Rendering: {self.s()}")
def destroy(self): self.effect_instance.dispose()Notification Batching
Section titled “Notification Batching”When multiple signals change, their effects are batched to avoid unnecessary executions:
from reaktiv import Signal, Effect, batch
x = Signal(1)y = Signal(2)
def log_values(): print(f"x: {x()}, y: {y()}")
logger = Effect(log_values) # Prints: "x: 1, y: 2"
# Without batching, the effect would run twice:# x.set(10) # Effect runs# y.set(20) # Effect runs again
# With batching, the effect runs only once after all changes:with batch(): x.set(10) # No effect execution yet y.set(20) # No effect execution yet# After batch completes: Effect runs once with new values# Prints: "x: 10, y: 20"