effect

A side effect that runs automatically when reactive dependencies change.

API

function effect(effectFn: () => void): () => void
  • effectFn: 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.

Basic Usage

An effect runs immediately, and then again whenever any of its dependencies change.

Call the returned cleanup function to stop the effect when it’s no longer needed.

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

Conditional Effects

An effect only reacts to the signals that are actually read during the last execution.

import { signal, effect } from '@hellajs/core';

const showData = signal(true);
const data = signal('value');

effect(() => {
  if (showData()) {
    console.log(data()); // Only reacts to 'data' when showData is true
  }
});

Cleanup Functions

The effect function returns a cleanup function that disposes of the effect and prevents memory leaks.

import { effect } from '@hellajs/core';

const cleanup = effect(() => {
 // Side effect logic
});

// Call cleanup when no longer needed
cleanup();

Important Considerations

Async Functions

Do not pass an async function directly to effect. Effects should be synchronous functions that may contain asynchronous operations.

import { effect } from '@hellajs/core';

// ❌ Async function directly
effect(async () => { /* ... */ });

// ✅ Handle async operations inside
effect(() => {
  fetch('/api/data').then(data => {
    // Handle the data here
    console.log(data);
  });
});

Infinite Loops

Avoid writing to signals that the effect reads from, as this creates infinite loops.

import { signal, effect, untracked } from '@hellajs/core';

const count = signal(0);

// ❌ Effect triggers itself
effect(() => {
  count(count() + 1);
});

// ✅ Use untracked for non-reactive reads
effect(() => {
  const current = untracked(() => count());
  console.log(`Count: ${current}`);
});