Skip to content

solidjs/signals

@solidjs/signals

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.

Installation

npm install @solidjs/signals
# or
pnpm add @solidjs/signals

Overview

@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"
});

Batched Updates

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 once

Core Primitives

Signals

const [value, setValue] = createSignal(initialValue, options?);

Reactive state with a getter/setter pair. Supports custom equality via options.equals.

Memos

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.

Effects

// 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).

Writable Memos

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);

Async

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 value

Use 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 Updates

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().

Stores

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);
});

Projections

Derived stores that transform data reactively:

import { createProjection } from "@solidjs/signals";

const filtered = createProjection(
  draft => {
    draft.items = store.todos.filter(t => !t.done);
  },
  { items: [] }
);

Boundaries

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()
);

Ownership & Context

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
});

Utilities

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

Development

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 sorting

License

MIT

About

No description, website, or topics provided.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages