Signals API

Signals API

Signals are the foundation of Luna's reactivity system. The API is compatible with SolidJS.

createSignal

Create a reactive signal that holds a value.

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

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

// Read the value
console.log(count());  // 0

// Set a new value
setCount(5);
console.log(count());  // 5

// Update based on previous value
setCount(c => c + 1);
console.log(count());  // 6

Signature

function createSignal<T>(value: T): [Accessor<T>, Setter<T>];

type Accessor<T> = () => T;
type Setter<T> = (value: T | ((prev: T) => T)) => void;

createEffect

Create a side effect that automatically tracks and re-runs when dependencies change.

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

const [name, setName] = createSignal("Luna");

createEffect(() => {
  console.log(`Hello, ${name()}!`);
});
// Logs: Hello, Luna!

setName("World");
// Logs: Hello, World!

Signature

function createEffect(fn: () => void): () => void;

Returns a dispose function to stop the effect.

createMemo

Create a cached computed value that updates when dependencies change.

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

const [count, setCount] = createSignal(2);
const squared = createMemo(() => count() ** 2);

console.log(squared());  // 4

setCount(3);
console.log(squared());  // 9

Signature

function createMemo<T>(fn: () => T): Accessor<T>;

batch

Batch multiple signal updates to avoid redundant effect runs.

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

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

createEffect(() => {
  console.log(`a=${a()}, b=${b()}`);
});
// Logs: a=0, b=0

batch(() => {
  setA(1);
  setB(2);
  // Effect doesn't run during batch
});
// Logs: a=1, b=2 (only once!)

untrack

Read signals without creating a dependency.

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

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

createEffect(() => {
  // Only tracks 'a', not 'b'
  console.log(a(), untrack(() => b()));
});

setA(1);  // Effect re-runs
setB(1);  // Effect does NOT re-run

onCleanup

Register a cleanup function that runs before an effect re-runs or when disposed.

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

const [active, setActive] = createSignal(true);

createEffect(() => {
  if (active()) {
    const interval = setInterval(() => console.log("tick"), 1000);
    onCleanup(() => clearInterval(interval));
  }
});

onMount

Run code once without tracking dependencies (like a mount lifecycle).

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

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

onMount(() => {
  console.log(count());  // Does NOT create dependency
  console.log("Mounted!");
});

setCount(1);  // onMount does NOT re-run

on

Explicit dependency tracking helper (SolidJS-style).

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

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

// Track single dependency
createEffect(on(count, (value, prev) => {
  console.log(`Changed from ${prev} to ${value}`);
}));

// Track multiple dependencies
const [a, setA] = createSignal(1);
const [b, setB] = createSignal(2);

createEffect(on([a, b], ([aVal, bVal], prev) => {
  console.log(`a=${aVal}, b=${bVal}`);
}));

// Defer option - skip initial run
createEffect(on(count, (value) => {
  console.log(`Count changed to ${value}`);
}, { defer: true }));  // Won't run immediately

Signature

function on<T, U>(
  deps: Accessor<T>,
  fn: (input: T, prevInput: T | undefined) => U,
  options?: { defer?: boolean }
): () => U | undefined;

// Multiple dependencies
function on<T extends readonly Accessor<any>[], U>(
  deps: T,
  fn: (input: [...], prevInput: [...] | undefined) => U,
  options?: { defer?: boolean }
): () => U | undefined;

createRoot

Create a reactive scope that can dispose all nested effects.

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

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

createRoot((dispose) => {
  createEffect(() => {
    console.log(`Count: ${count()}`);
  });

  setCount(1);  // Effect runs
  dispose();    // Disposes all effects
});

setCount(2);  // Effect does NOT run

Owner Utilities

import { createRoot, getOwner, runWithOwner, hasOwner } from '@luna_ui/luna';

// Check if inside an owner scope
console.log(hasOwner());  // false outside createRoot

createRoot(() => {
  console.log(hasOwner());  // true inside

  const owner = getOwner();

  // Later, run code with saved owner context
  setTimeout(() => {
    runWithOwner(owner, () => {
      // Has access to the owner's reactive context
    });
  }, 1000);
});

Context API

Provide and consume values through the component tree.

import { createContext, useContext, provide, Provider } from '@luna_ui/luna';

// Create context with default value
const ThemeContext = createContext('light');

// Use context value
const theme = useContext(ThemeContext);  // 'light'

// Provide value with function scope
const result = provide(ThemeContext, 'dark', () => {
  return useContext(ThemeContext);  // 'dark'
});

// Or use Provider component
<Provider context={ThemeContext} value="dark">
  <App />
</Provider>

Resource API

Handle async operations with loading/error states.

import { createResource, createDeferred } from '@luna_ui/luna';

// Create resource with fetcher
const [data, { refetch }] = createResource((resolve, reject) => {
  fetch('/api/data')
    .then(r => r.json())
    .then(resolve)
    .catch(e => reject(e.message));
});

// Access resource
data();         // value or undefined
data.loading;   // boolean
data.error;     // string or undefined
data.state;     // 'unresolved' | 'pending' | 'ready' | 'errored'
data.latest;    // last successful value

// Refetch
refetch();

// Manual control with deferred
const [resource, resolve, reject] = createDeferred<number>();
// Later...
resolve(42);
// Or...
reject("Failed!");

Store API

Create reactive stores with nested property tracking.

import { createStore, produce, reconcile } from '@luna_ui/luna';

const [state, setState] = createStore({
  count: 0,
  user: { name: "John", age: 30 }
});

// Read (reactive)
state.count;
state.user.name;

// Update by path
setState("count", 1);
setState("user", "name", "Jane");

// Functional update
setState("count", c => c + 1);

// Object merge at path
setState("user", { name: "Jane", age: 25 });

// Immer-style mutations with produce
setState("user", produce(user => {
  user.name = "Alice";
  user.age = 28;
}));

// Replace entire value with reconcile
setState("items", reconcile(newItems));

Utility Functions

mergeProps

Merge multiple props objects.

import { mergeProps } from '@luna_ui/luna';

const defaults = { color: 'blue', size: 'medium' };
const props = { color: 'red' };

const merged = mergeProps(defaults, props);
// { color: 'red', size: 'medium' }

// Event handlers are merged (both run)
const a = { onClick: () => console.log('a') };
const b = { onClick: () => console.log('b') };
mergeProps(a, b).onClick();  // Logs: 'a', 'b'

// Classes are concatenated
mergeProps({ class: 'foo' }, { class: 'bar' });
// { class: 'foo bar' }

splitProps

Split props into groups.

import { splitProps } from '@luna_ui/luna';

const props = { a: 1, b: 2, c: 3, d: 4 };

const [local, others] = splitProps(props, ['a', 'b']);
// local = { a: 1, b: 2 }
// others = { c: 3, d: 4 }

// Multiple groups
const [group1, group2, rest] = splitProps(props, ['a'], ['b', 'c']);
// group1 = { a: 1 }
// group2 = { b: 2, c: 3 }
// rest = { d: 4 }

API Summary

Core Reactivity

FunctionDescription
createSignal(value)Create a reactive signal
createEffect(fn)Create a side effect
createMemo(fn)Create a cached computed value
batch(fn)Batch multiple updates
untrack(fn)Run without tracking dependencies
onCleanup(fn)Register cleanup in effect
onMount(fn)Run once without tracking
on(deps, fn, opts)Explicit dependency tracking

Scope Management

FunctionDescription
createRoot(fn)Create reactive scope
getOwner()Get current owner
runWithOwner(owner, fn)Run with owner context
hasOwner()Check if has owner

Context

FunctionDescription
createContext(default)Create context
useContext(ctx)Use context value
provide(ctx, value, fn)Provide context in scope

Async

FunctionDescription
createResource(fetcher)Create async resource
createDeferred()Create manual resource

Store

FunctionDescription
createStore(value)Create reactive store
produce(fn)Immer-style mutations
reconcile(value)Replace value

Utilities

FunctionDescription
mergeProps(...sources)Merge props objects
splitProps(props, keys...)Split props into groups