Signals
Signals are the core unit of reactivity in Signalis. A Signal is simply a box around a value that notifies other parts of the system when its value has changed.
Key Characteristics
- Writable: Can be both read and updated
- Lazy notifications: Changing a signal doesn't trigger computations immediately
- Equality checking: Uses
===by default to determine if the value actually changed - Customizable: Can provide custom equality functions
Creating Signals
Basic Signal
import { createSignal } from '@signalis/core';
const count = createSignal(0);
const list = createSignal(['foo', 'bar', 'baz']);
const user = createSignal({ firstName: 'Jane', lastName: 'Doe' });Reading Values
const count = createSignal(0);
console.log(count.value); // 0Updating Values
const count = createSignal(0);
count.value = 1;
count.value++; // Also worksAPI Reference
createSignal<T>(value: T, isEqual?: false | (old: T, new: T) => boolean): Signal<T>
Creates a signal with an initial value and optional equality function.
Parameters:
value: The initial valueisEqual(optional): Equality function orfalseto always notify
Returns: A Signal<T> with a value property
Example:
const count = createSignal(0);createSignal(): Signal<unknown>
Creates a notifier signal that always notifies on updates, regardless of value.
Returns: A Signal<unknown> that notifies on every update
Example:
const notifier = createSignal();
notifier.value = null; // Notifies dependents
notifier.value = null; // Notifies again (even though value didn't change)Working with Objects and Arrays
Since signals use === equality by default, object and array updates need to be immutable:
const user = createSignal({ firstName: 'Jane', lastName: 'Doe' });
// ✅ Correct: Create a new object
user.value = {
...user.value,
firstName: 'Janet',
};
// ❌ Wrong: Mutating the object won't trigger notifications
user.value.firstName = 'Janet'; // Doesn't work!Custom Equality Functions
You can provide a custom equality function to change how signals determine when they've changed:
import { isEqual } from 'lodash';
const user = createSignal(
{
firstName: 'Jane',
lastName: 'Doe',
},
isEqual,
);
// Now you can mutate and it will detect deep equality
const current = user.value;
current.firstName = 'Janet';
user.value = current; // Triggers update because deep equality check failsAlways Notify
Set the equality function to false to make a signal always notify, regardless of value:
const alwaysNotify = createSignal(0, false);
alwaysNotify.value = 1; // Notifies
alwaysNotify.value = 1; // Notifies again
alwaysNotify.value = 1; // Keeps notifyingCommon Patterns
Counter
const count = createSignal(0);
const increment = () => count.value++;
const decrement = () => count.value--;
const reset = () => (count.value = 0);Toggle
const isOpen = createSignal(false);
const toggle = () => (isOpen.value = !isOpen.value);
const open = () => (isOpen.value = true);
const close = () => (isOpen.value = false);List Management
const items = createSignal<Array<string>>([]);
const addItem = (item: string) => {
items.value = [...items.value, item];
};
const removeItem = (index: number) => {
items.value = items.value.filter((_, i) => i !== index);
};
const clearItems = () => {
items.value = [];
};Notifier Pattern
For cases where you only care about the change event, not the value:
const refreshTrigger = createSignal();
createEffect(() => {
refreshTrigger.value; // Just read it to create dependency
fetchData(); // This runs whenever refreshTrigger changes
});
// Trigger a refresh
const refresh = () => {
refreshTrigger.value = null; // Value doesn't matter
};Type Safety
Signals are fully type-safe in TypeScript:
const count = createSignal(0); // Signal<number>
const name = createSignal('Jane'); // Signal<string>
const user = createSignal({ name: 'Jane' }); // Signal<{ name: string }>
count.value = 'hello'; // ❌ Type error
name.value = 42; // ❌ Type errorGotchas
Don't Mutate Objects
const state = createSignal({ count: 0 });
// ❌ This won't trigger updates
state.value.count++;
// ✅ Do this instead
state.value = { ...state.value, count: state.value.count + 1 };
// Or consider using a Store for nested stateReading in Loops
Each read of .value tracks a dependency:
const count = createSignal(0);
createEffect(() => {
// Reading multiple times is fine, but unnecessary
console.log(count.value);
console.log(count.value);
console.log(count.value);
// Better to read once
const value = count.value;
console.log(value);
console.log(value);
console.log(value);
});Type Guard
Use isSignal() to check if a value is a signal:
import { isSignal } from '@signalis/core';
const count = createSignal(0);
const value = 42;
isSignal(count); // true
isSignal(value); // false