Skip to content

signal

The core reactive primitive. A signal is an object that holds a value and notifies any interested consumers when that value changes.

// Create a signal with an initial value
function signal<T>(initialValue: T): Signal<T>
// Create a signal with an initial value of `undefined`
function signal<T>(): Signal<T | undefined>
// A signal is a function that acts as both a getter and a setter
interface Signal<T> {
(): T; // Get the current value
(value: T): void; // Set a new value
}

TypeScript provides excellent type safety for signals. The type is inferred from the initial value, but can also be set explicitly.

// Inferred as Signal<number>
const count = signal(0);
// Inferred as Signal<string>
const name = signal("Hella");
// Explicitly typed signal
type Status = 'loading' | 'success' | 'error';
const status = signal<Status>('loading');

To read a signal’s value, call it with no arguments. To update it, call it with the new value.

import { signal } from '@hellajs/core';
// Create a signal
const count = signal(0);
// Read the value
console.log(count()); // 0
// Set a new value
count(10);
console.log(count()); // 10
// Signals can hold any data type
const user = signal({ name: "John" });
const tags = signal(["a", "b"]);

Signals are the foundation of the reactive system. When a signal is read inside a computed or effect, a dependency is created. When the signal’s value changes, the computation will automatically update.

import { signal, computed, effect } from '@hellajs/core';
const firstName = signal("John");
const lastName = signal("Doe");
// `computed` depends on `firstName` and `lastName`
const fullName = computed(() => `${firstName()} ${lastName()}`);
// `effect` depends on `fullName`
effect(() => {
console.log(`Welcome, ${fullName()}!`);
});
// Logs: "Welcome, John Doe!"
firstName("Jane");
// Logs: "Welcome, Jane Doe!"

Signals detect changes using strict equality (===). When working with objects or arrays, you must pass a new object or array to trigger an update. Modifying the existing object in place will not work.

const user = signal({ name: 'John', age: 30 });
// ❌ Incorrect: This mutates the object but doesn't trigger an update
// because the object reference is the same.
user().age = 31;
user(user()); // Nothing happens
// ✅ Correct: Create a new object
user({ ...user(), age: 31 });
const items = signal(['a', 'b']);
// ❌ Incorrect: `push` mutates the array in place.
items().push('c');
items(items()); // Nothing happens
// ✅ Correct: Create a new array
items([...items(), 'c']);

For managing complex state, consider using multiple signals for independent properties or exploring the @hellajs/store package.