The reactive core that powers SolidJS 2.0. This is a standalone signals library designed for rendering — it includes first-class support for async, transitions, optimistic updates, and deeply reactive stores that go beyond what general-purpose signals libraries offer.
Status: Beta — this package is the reactive foundation of SolidJS 2.0 Beta. The API is stabilizing but may still have breaking changes before a final release.
npm install @solidjs/signals
# or
pnpm add @solidjs/signals@solidjs/signals is a push-pull hybrid reactive system. Signals hold values, computeds derive from them, and effects run side effects — all connected through an automatic dependency graph. Updates are batched and flushed asynchronously via microtask, giving you consistent state without glitches.
import { createEffect, createMemo, createRoot, createSignal, flush } from "@solidjs/signals";
createRoot(() => {
const [count, setCount] = createSignal(0);
const doubled = createMemo(() => count() * 2);
createEffect(
() => doubled(),
value => {
console.log("Doubled:", value);
}
);
setCount(5);
flush(); // "Doubled: 10"
});Signal writes are batched — reads after a write won't reflect the new value until flush() runs. This prevents glitches and unnecessary recomputation.
const [a, setA] = createSignal(1);
const [b, setB] = createSignal(2);
setA(10);
setB(20);
// Neither has updated yet — both writes are batched
flush(); // Now both update and downstream effects run onceconst [value, setValue] = createSignal(initialValue, options?);Reactive state with a getter/setter pair. Supports custom equality via options.equals.
const derived = createMemo(() => expensive(signal()));Read-only derived values that cache their result and only recompute when dependencies change. Supports async compute functions — return a Promise or AsyncIterable and downstream consumers will wait automatically.
// Two-phase: compute tracks dependencies, effect runs side effects
createEffect(
() => count(),
value => {
console.log(value);
}
);
// Render-phase effect (runs before user effects)
createRenderEffect(
() => count(),
value => {
updateDOM(value);
}
);Effects split tracking from execution. createEffect and createRenderEffect take a compute function (for tracking) and an effect function (for side effects).
Pass a function to createSignal to get a writable derived value — a memo you can also set:
const [value, setValue] = createSignal(prev => transform(source(), prev), initialValue);Computeds can return promises or async iterables. The reactive graph handles this automatically — previous values are held in place until the async work resolves, so downstream consumers never see an inconsistent state.
const data = createMemo(async () => {
const response = await fetch(`/api/items?q=${query()}`);
return response.json();
});
// Check async state
isPending(data); // true while loading
latest(data); // last resolved valueUse action() to coordinate async workflows with the reactive graph:
const save = action(function* (item) {
yield fetch("/api/save", { method: "POST", body: JSON.stringify(item) });
});Optimistic signals show an immediate value while async work is pending, then automatically revert when it settles:
const [optimisticCount, setOptimisticCount] = createOptimistic(0);
// Immediate UI update — reverts when the async work resolves
setOptimisticCount(count + 1);Also available for stores via createOptimisticStore().
Proxy-based deeply reactive objects with per-property tracking:
import { createStore, reconcile } from "@solidjs/signals";
const [store, setStore] = createStore({ todos: [], filter: "all" });
// Setter takes a mutating callback — mutations are intercepted by the proxy
setStore(s => {
s.filter = "active";
});
setStore(s => {
s.todos.push({ text: "New", done: false });
});
setStore(s => {
s.todos[0].done = true;
});
// Reconcile from server data
setStore(s => {
reconcile(serverTodos, "id")(s.todos);
});Derived stores that transform data reactively:
import { createProjection } from "@solidjs/signals";
const filtered = createProjection(
draft => {
draft.items = store.todos.filter(t => !t.done);
},
{ items: [] }
);Intercept async loading and error states in the reactive graph:
import { createErrorBoundary, createLoadingBoundary } from "@solidjs/signals";
createErrorBoundary(
() => riskyComputation(),
(error, reset) => handleError(error)
);
createLoadingBoundary(
() => asyncContent(),
() => showFallback()
);All reactive nodes exist within an owner tree that handles disposal and context propagation:
import { createContext, createRoot, getContext, onCleanup, setContext } from "@solidjs/signals";
const ThemeContext = createContext("light");
createRoot(dispose => {
setContext(ThemeContext, "dark");
createEffect(
() => getContext(ThemeContext),
theme => {
console.log("Theme:", theme);
}
);
onCleanup(() => console.log("Disposed"));
// Call dispose() to tear down the tree
});| Function | Description |
|---|---|
flush() |
Process all pending updates |
untrack(fn) |
Run fn without tracking dependencies |
isPending(accessor) |
Check if an async accessor is loading |
latest(accessor) |
Get the last resolved value of an async accessor |
refresh(accessor) |
Re-trigger an async computation |
isRefreshing(accessor) |
Check if an async accessor is refreshing |
resolve(fn) |
Returns a promise that resolves when a reactive expression settles |
mapArray(list, mapFn) |
Reactive array mapping with keyed reconciliation |
repeat(count, mapFn) |
Reactive repeat based on a reactive count |
onSettled(fn) |
Run a callback after the current flush cycle completes |
snapshot(store) |
Returns a non-reactive copy of a store, preserving unmodified references |
reconcile(value, key) |
Returns a diffing function for updating stores from new data |
merge(...sources) |
Reactively merges multiple objects/stores, last source wins |
omit(props, ...keys) |
Creates a reactive view of an object with specified keys removed |
deep(store) |
Tracks all nested changes on a store |
storePath(...path) |
Path-based setter for stores as an alternative to mutating callbacks |
pnpm install
pnpm build # Rollup build (dev/prod/node outputs)
pnpm test # Run tests
pnpm test:watch # Watch mode
pnpm test:gc # Tests with GC exposed
pnpm bench # Benchmarks
pnpm format # Prettier + import sortingMIT