Skip to content

effect API

Use effect(...) or @effect to run side effects when the signals read by the effect change:

Editor (session: default) Run
from reaktiv import effect, signal

count = signal(0)

def print_count():
    print(f"count={count()}")

runner = effect(print_count)
count.set(1)
runner.dispose()
Output Clear

Naming

Prefer effect(...) and @effect in application code. Effect is the concrete disposable effect class and remains supported for existing class-style code, type references, and advanced use.

reaktiv.effect

Effect implementation with sync/async support and batched scheduling.

Effect

A reactive effect that automatically tracks signal dependencies and re-runs when they change.

Effect creates a side effect function that runs immediately and re-runs whenever any signal it depends on changes. For synchronous effects outside a batch, every successful dependency update that changes a value triggers one effect execution. Updates inside batch() are intentionally coalesced and run once after the outermost batch completes. It supports optional cleanup logic.

Lifecycle and Memory Management

Effects are weakly referenced to prevent memory leaks. This means:

  • Without a stored reference: The Effect will be garbage collected immediately and stop responding to signal changes. Cleanup functions will run automatically during garbage collection.

  • With a stored reference: The Effect persists and continues reacting to changes. Call dispose() explicitly when done to ensure immediate cleanup.

Best practices: - Store effect references: self.effect = effect(...) - Call dispose() explicitly: self.effect.dispose() - Cleanup functions run automatically on GC, but prefer explicit dispose()

Cleanup Functions

Effects can register cleanup logic to run when: 1. The effect reruns (cleanup from previous run) 2. dispose() is called explicitly 3. The Effect is garbage collected (automatic)

Register cleanup by returning a function or using the on_cleanup parameter.

Note

Async functions are supported but still experimental and may not behave as expected in all scenarios.

Parameters:

Name Type Description Default
func EffectFn

The effect function to run. Can be sync or async (experimental). May optionally accept an on_cleanup callback parameter for registering cleanup logic, or return a cleanup function.

required

Examples:

Basic effect:

from reaktiv import effect, signal

counter = signal(0)

# Effect runs immediately and on every change
# Must retain reference to prevent garbage collection
def print_counter():
    print(f"Counter: {counter()}")

counter_effect = effect(print_counter)
# Prints: "Counter: 0"

counter.set(1)
# Prints: "Counter: 1"

counter.set(2)
# Prints: "Counter: 2"

Effect with cleanup:

from reaktiv import effect, signal

user_id = signal(1)

def subscribe_to_user():
    uid = user_id()
    print(f"Subscribing to user {uid}")

    # Return cleanup function
    def cleanup():
        print(f"Unsubscribing from user {uid}")
    return cleanup

subscription = effect(subscribe_to_user)
# Prints: "Subscribing to user 1"

user_id.set(2)
# Prints: "Unsubscribing from user 1"
# Prints: "Subscribing to user 2"

subscription.dispose()
# Prints: "Unsubscribing from user 2"

Effect with on_cleanup parameter:

from reaktiv import effect, signal

enabled = signal(True)

def my_effect(on_cleanup):
    if enabled():
        print("Starting...")
        on_cleanup(lambda: print("Stopping..."))

runner = effect(my_effect)
# Prints: "Starting..."

enabled.set(False)
# Prints: "Stopping..."

Manual disposal:

from reaktiv import effect, signal

count = signal(0)

def print_count():
    print(count())

count_effect = effect(print_count)
# Prints: 0

count.set(1)
# Prints: 1

count_effect.dispose()

count.set(2)
# No print - effect is disposed

__del__()

Run cleanup function when Effect is garbage collected.

This ensures that cleanup functions (e.g., closing connections, canceling subscriptions) are executed even if dispose() is not called explicitly.

Note: This is called automatically by Python's garbage collector when the Effect has no more references. For deterministic cleanup, prefer calling dispose() explicitly.

dispose()

Stop the effect and prevent it from running again.

This method: - Marks the effect as disposed - Unsubscribes from all signal dependencies - Runs any pending cleanup functions - Cancels any in-progress async tasks

After calling dispose(), the effect will no longer react to signal changes.

Examples:

from reaktiv import effect, signal

counter = signal(0)

def print_count():
    print(f"Count: {counter()}")

count_effect = effect(print_count)
# Prints: "Count: 0"

counter.set(1)
# Prints: "Count: 1"

count_effect.dispose()

counter.set(2)
# No output - effect is disposed

EffectDescriptor

Bases: PerInstanceDescriptor['Effect']

Descriptor that creates one Effect per owner instance.

reaktiv.Effect

A reactive effect that automatically tracks signal dependencies and re-runs when they change.

Effect creates a side effect function that runs immediately and re-runs whenever any signal it depends on changes. For synchronous effects outside a batch, every successful dependency update that changes a value triggers one effect execution. Updates inside batch() are intentionally coalesced and run once after the outermost batch completes. It supports optional cleanup logic.

Lifecycle and Memory Management

Effects are weakly referenced to prevent memory leaks. This means:

  • Without a stored reference: The Effect will be garbage collected immediately and stop responding to signal changes. Cleanup functions will run automatically during garbage collection.

  • With a stored reference: The Effect persists and continues reacting to changes. Call dispose() explicitly when done to ensure immediate cleanup.

Best practices: - Store effect references: self.effect = effect(...) - Call dispose() explicitly: self.effect.dispose() - Cleanup functions run automatically on GC, but prefer explicit dispose()

Cleanup Functions

Effects can register cleanup logic to run when: 1. The effect reruns (cleanup from previous run) 2. dispose() is called explicitly 3. The Effect is garbage collected (automatic)

Register cleanup by returning a function or using the on_cleanup parameter.

Note

Async functions are supported but still experimental and may not behave as expected in all scenarios.

Parameters:

Name Type Description Default
func EffectFn

The effect function to run. Can be sync or async (experimental). May optionally accept an on_cleanup callback parameter for registering cleanup logic, or return a cleanup function.

required

Examples:

Basic effect:

from reaktiv import effect, signal

counter = signal(0)

# Effect runs immediately and on every change
# Must retain reference to prevent garbage collection
def print_counter():
    print(f"Counter: {counter()}")

counter_effect = effect(print_counter)
# Prints: "Counter: 0"

counter.set(1)
# Prints: "Counter: 1"

counter.set(2)
# Prints: "Counter: 2"

Effect with cleanup:

from reaktiv import effect, signal

user_id = signal(1)

def subscribe_to_user():
    uid = user_id()
    print(f"Subscribing to user {uid}")

    # Return cleanup function
    def cleanup():
        print(f"Unsubscribing from user {uid}")
    return cleanup

subscription = effect(subscribe_to_user)
# Prints: "Subscribing to user 1"

user_id.set(2)
# Prints: "Unsubscribing from user 1"
# Prints: "Subscribing to user 2"

subscription.dispose()
# Prints: "Unsubscribing from user 2"

Effect with on_cleanup parameter:

from reaktiv import effect, signal

enabled = signal(True)

def my_effect(on_cleanup):
    if enabled():
        print("Starting...")
        on_cleanup(lambda: print("Stopping..."))

runner = effect(my_effect)
# Prints: "Starting..."

enabled.set(False)
# Prints: "Stopping..."

Manual disposal:

from reaktiv import effect, signal

count = signal(0)

def print_count():
    print(count())

count_effect = effect(print_count)
# Prints: 0

count.set(1)
# Prints: 1

count_effect.dispose()

count.set(2)
# No print - effect is disposed

dispose()

Stop the effect and prevent it from running again.

This method: - Marks the effect as disposed - Unsubscribes from all signal dependencies - Runs any pending cleanup functions - Cancels any in-progress async tasks

After calling dispose(), the effect will no longer react to signal changes.

Examples:

from reaktiv import effect, signal

counter = signal(0)

def print_count():
    print(f"Count: {counter()}")

count_effect = effect(print_count)
# Prints: "Count: 0"

counter.set(1)
# Prints: "Count: 1"

count_effect.dispose()

counter.set(2)
# No output - effect is disposed

__del__()

Run cleanup function when Effect is garbage collected.

This ensures that cleanup functions (e.g., closing connections, canceling subscriptions) are executed even if dispose() is not called explicitly.

Note: This is called automatically by Python's garbage collector when the Effect has no more references. For deterministic cleanup, prefer calling dispose() explicitly.