Introduction: Effects

Effects

Effects are functions that run automatically when their dependencies change.

Note: Luna's createEffect runs immediately (synchronously), similar to Solid.js's createRenderEffect. Solid.js's createEffect is deferred until after the render phase completes.

Creating Effects

import { createSignal, createEffect } from '@luna_ui/luna';

const [count, setCount] = createSignal(0);

createEffect(() => {
  console.log("Count is:", count());
});

// Logs: "Count is: 0"

setCount(1);
// Logs: "Count is: 1"

setCount(2);
// Logs: "Count is: 2"

Automatic Dependency Tracking

Effects automatically track which signals they read:

const [a, setA] = createSignal(1);
const [b, setB] = createSignal(2);

createEffect(() => {
  console.log("a:", a());  // Only tracks `a`
});

setA(10);  // Effect runs
setB(20);  // Effect does NOT run (b is not tracked)

Conditional Dependencies

Dependencies are tracked dynamically based on execution path:

const [showDetails, setShowDetails] = createSignal(false);
const [name, setName] = createSignal("Luna");
const [details, setDetails] = createSignal("A UI framework");

createEffect(() => {
  console.log("Name:", name());

  if (showDetails()) {
    console.log("Details:", details());  // Only tracked when showDetails is true
  }
});

setDetails("New details");  // Effect does NOT run (not currently tracked)

setShowDetails(true);       // Effect runs, now tracks `details`

setDetails("Updated");      // Effect runs (now tracked)

Side Effects

Effects are perfect for side effects like:

DOM Manipulation

const [title, setTitle] = createSignal("My App");

createEffect(() => {
  document.title = title();  // Updates document title reactively
});

Logging

const [user, setUser] = createSignal(null);

createEffect(() => {
  if (user()) {
    console.log("User logged in:", user().name);
  }
});

API Calls

const [searchTerm, setSearchTerm] = createSignal("");

createEffect(() => {
  const term = searchTerm();
  if (term.length > 2) {
    fetch(`/api/search?q=${term}`)
      .then(res => res.json())
      .then(data => setResults(data));
  }
});

Effect Return Value

Effects can return a dispose function:

const dispose = createEffect(() => {
  console.log("Running");
});

// Later: stop the effect
dispose();

Cleanup (onCleanup)

Register cleanup functions inside effects:

import { createSignal, createEffect, onCleanup } from '@luna_ui/luna';

const [count, setCount] = createSignal(0);

createEffect(() => {
  const interval = setInterval(() => {
    console.log("Count:", count());
  }, 1000);

  // Cleanup runs before effect re-runs or when disposed
  onCleanup(() => {
    clearInterval(interval);
  });
});

Common Patterns

Debounced Effect

const [search, setSearch] = createSignal("");

createEffect(() => {
  const term = search();

  const timeout = setTimeout(() => {
    fetchResults(term);
  }, 300);

  onCleanup(() => clearTimeout(timeout));
});

Event Listeners

const [element, setElement] = createSignal(null);

createEffect(() => {
  const el = element();
  if (!el) return;

  const handler = () => console.log("Clicked!");
  el.addEventListener("click", handler);

  onCleanup(() => {
    el.removeEventListener("click", handler);
  });
});

Try It

Create an effect that:

  1. Tracks a count signal

  2. Logs the count every second while running

  3. Cleans up properly when count changes

Solution
const [count, setCount] = createSignal(0);

createEffect(() => {
  const currentCount = count();

  const interval = setInterval(() => {
    console.log("Current count:", currentCount);
  }, 1000);

  onCleanup(() => {
    console.log("Cleaning up for count:", currentCount);
    clearInterval(interval);
  });
});

Next

Learn about Memos โ†’