Nested Effects
Understanding how effects compose and clean up.
Effects Can Contain Effects
using @luna { signal, effect, on_cleanup }
let show = signal(true)
let count = signal(0)
effect(fn() {
if show.get() {
println("Outer effect: showing")
// Inner effect
effect(fn() {
println("Inner effect: count = " + count.get().to_string())
})
}
})
Lifecycle of Nested Effects
show = true
โโโ Outer runs
โ โโโ Inner created, runs with count=0
โ
count = 1
โ โโโ Inner re-runs with count=1
โ
show = false
โโโ Outer re-runs
โ โโโ Inner is disposed (cleanup runs)
Automatic Cleanup
Inner effects are automatically disposed when the outer effect re-runs:
let category = signal("books")
let search = signal("")
effect(fn() {
let cat = category.get()
println("Category: " + cat)
// This inner effect is disposed when category changes
effect(fn() {
let query = search.get()
println("Searching " + cat + " for: " + query)
})
})
Cleanup Order
Cleanup runs in reverse order (LIFO):
effect(fn() {
println("Outer setup")
on_cleanup(fn() { println("Outer cleanup") })
effect(fn() {
println("Inner setup")
on_cleanup(fn() { println("Inner cleanup") })
})
})
// Output:
// Outer setup
// Inner setup
// When re-running:
// Inner cleanup
// Outer cleanup
// Outer setup
// Inner setup
Common Patterns
Conditional Subscriptions
let enabled = signal(true)
let data = signal(0)
effect(fn() {
if enabled.get() {
effect(fn() {
println("Data: " + data.get().to_string())
})
}
})
// Data changes are only tracked when enabled=true
Dynamic Effect Count
let items = signal([1, 2, 3])
effect(fn() {
for item in items.get() {
effect(fn() {
println("Item: " + item.to_string())
})
}
})
// Each item gets its own effect
// All are disposed when items changes
Resource Per Item
let connections = signal(["ws://a", "ws://b"])
effect(fn() {
for url in connections.get() {
effect(fn() {
println("Connecting to: " + url)
on_cleanup(fn() {
println("Disconnecting from: " + url)
})
})
}
})
Avoiding Deep Nesting
Deep nesting can be hard to follow. Consider extracting to functions:
// Instead of deep nesting
effect(fn() {
effect(fn() {
effect(fn() {
// ...
})
})
})
// Extract to named effects
fn setup_outer() {
effect(fn() {
setup_inner()
})
}
fn setup_inner() {
effect(fn() {
// ...
})
}
Root Effects
Use create_root for top-level effect management:
using @luna { create_root }
let dispose = create_root(fn(dispose) {
effect(fn() {
// This effect and all nested effects
// can be disposed with dispose()
})
dispose // Return dispose function
})
// Later: dispose all effects
dispose()
Try It
Create a nested effect structure where:
Outer effect tracks a
tabsignalInner effect tracks content specific to that tab
Verify inner effect is disposed on tab change
Next
Learn about Show โ