effect
Automatically runs side effects when reactive dependencies change.
API
function effect(fn: () => void): () => void
fn
: The function to run as a side effect. It will be re-executed whenever a signal read inside it changes.- Returns: A
cleanup
function that can be called to stop the effect and prevent further executions.
TypeScript
The effect
function takes a function returning void
and returns a cleanup function.
type EffectFunction = () => void;
type CleanupFunction = () => void;
const cleanup: CleanupFunction = effect(() => {
// ... side effects
});
// Later...
cleanup();
Basic Usage
An effect runs immediately upon creation and then again whenever any of its dependencies change. It’s crucial to call the returned cleanup
function to stop the effect when it’s no longer needed, for example, when a component unmounts.
import { signal, effect } from '@hellajs/core';
const count = signal(0);
// This effect runs immediately, then whenever `count` changes.
const cleanup = effect(() => {
console.log(`The count is: ${count()}`);
});
count(1); // Logs: "The count is: 1"
count(2); // Logs: "The count is: 2"
// Stop the effect. Future changes to `count` will not be logged.
cleanup();
count(3); // (nothing is logged)
Key Concepts
Eager Execution
Unlike computed values that run lazily, effects execute immediately when dependencies change, making them perfect for side effects like DOM updates and API calls.
const data = signal('initial');
effect(() => {
console.log('Effect runs immediately:', data());
}); // Logs immediately
data('updated'); // Effect runs again immediately
Dependency Tracking
Effects automatically track all signals read during execution, creating dynamic dependency graphs that adapt to conditional logic.
const showData = signal(true);
const data = signal('value');
effect(() => {
if (showData()) {
console.log(data()); // Only tracks 'data' when showData is true
}
});
Cleanup Functions
The effect
function returns a cleanup function that you should call to dispose of the effect and prevent memory leaks.
const cleanup = effect(() => {
const interval = setInterval(() => console.log('tick'), 1000);
// Note: Effect functions cannot return cleanup - manage resources manually
});
// Call cleanup when no longer needed
cleanup();
Async Effect Patterns
Handle asynchronous operations safely by implementing cancellation tokens and proper cleanup to avoid race conditions.
let cancelled = false;
const cleanup = effect(() => {
const id = Math.random(); // Unique ID for this effect run
fetch('/api/data').then(result => {
if (!cancelled) updateData(result);
});
});
// Clean up when needed
const dispose = () => {
cancelled = true;
cleanup();
};
Important Considerations
Async Functions
Do not pass an async
function directly to effect
. Effects should be synchronous functions that may contain asynchronous operations.
// ❌ Async function directly
effect(async () => { /* ... */ });
// ✅ Handle async operations inside
effect(() => {
fetch('/api/data').then(data => result(data));
});
Infinite Loops
Avoid writing to signals that the effect reads from, as this creates infinite loops.
// ❌ Effect triggers itself
effect(() => {
count(count() + 1);
});
// ✅ Use untracked for non-reactive reads
effect(() => {
const current = untracked(() => count());
console.log(`Count: ${current}`);
});
Cleanup Management
Always call the returned cleanup function to prevent memory leaks and unwanted side effects.
// ✅ Proper cleanup
const cleanup = effect(() => {
// side effect logic
});
// Later when component unmounts
cleanup();