React Alien Signals is a TypeScript library that provides hooks built on top of Alien Signals. It offers a seamless integration with React, ensuring concurrency-safe re-renders without tearing.
Click to expand
- Basic Signals: Create and manage reactive state with
createSignal - Computed Signals: Derive reactive values based on other signals using
createComputed - Effects & Effect Scopes: Run side effects in response to state changes with
createEffectand manage multiple effects withcreateSignalScope - Tear-free React integration: Shares stable
useSyncExternalStoreadapters between subscribers - Selective subscriptions: Avoid unrelated renders with
useSignalSelector - Concurrent UI tools: Defer signal-driven UI with
useDeferredSignalValue - Lifecycle effects: Run explicit signal dependencies in React's insertion, layout, or passive phase
- Batching: Coalesce multiple signal writes with
batch - React Compiler compatible: Every exported hook is checked by React Compiler in CI
- TypeScript Support: Fully typed APIs for type safety and IntelliSense
Install react-alien-signals and its peer dependency alien-signals via npm:
npm install react-alien-signals alien-signalsThe current release line targets React 19.2 or newer and Alien Signals 3.2.
Create a writable signal and use it within your components:
import { createSignal, useSignal } from "react-alien-signals";
const count = createSignal(0);
function Counter() {
const [value, setValue] = useSignal(count);
return (
<button onClick={() => setValue(value + 1)}>
Count: {value}
</button>
);
}Create derived state that automatically updates:
import { createSignal, createComputed, useSignalValue } from "react-alien-signals";
const count = createSignal(1);
const double = createComputed(() => count() * 2);
function Display() {
const doubleValue = useSignalValue(double);
return <div>Double: {doubleValue}</div>;
}Run side effects in response to signal changes:
import { createSignal, createEffect, useSignalScope } from "react-alien-signals";
const count = createSignal(0);
function Logger() {
useSignalScope(() => {
createEffect(() => {
console.log('Count changed:', count());
});
});
return null;
}React Alien Signals provides several hooks to interact with signals:
useSignal(signal): Returns[value, setValue]tuple for reading and writinguseSignalValue(signal): Returns the current value (read-only)useDeferredSignalValue(signal): Returns a deferred signal snapshotuseSignalSelector(signal, selector): Subscribes only to the selected valueuseSetSignal(signal): Returns a setter function (write-only)useSignalEffect(effectFn): Runs a side effect based on signal changesuseSignalPassiveEffect(signals, effectFn, deps?): Runs explicit signal dependencies in React's passive phaseuseSignalLayoutEffect(signals, effectFn, deps?): Runs explicit signal dependencies in React's layout phaseuseSignalInsertionEffect(signals, effectFn, deps?): Runs explicit signal dependencies in React's insertion phaseuseSignalScope(callback): Manages effect scopes within a componentuseComputed(getter, deps): Creates and subscribes to a computed signal
The dependency array follows the same rules as
useMemo.
function Component({ a }) {
useComputed(
() => {
return a + mySignal();
},
[a, mySignal],
);
}import { batch } from "react-alien-signals";
batch(() => {
firstName("Ada");
lastName("Lovelace");
});Signal snapshots remain consistent across concurrent React renders through
useSyncExternalStore. External-store writes are synchronous by React's design,
including when they happen inside startTransition; use
useDeferredSignalValue when a signal-driven subtree may render later.
React automatically batches component renders caused by multiple signal writes in
the same event or task. Most application code therefore needs no batching API.
Use batch only when you also need to prevent intermediate Alien Signals
computed values and effects from propagating between a group of writes.
useOptimistic is best kept for Action-owned optimistic state rather than used as
a second signal store. Effect scopes start only after a component commits, so
server rendering and abandoned renders do not leak effects. Lifecycle-specific
signal effects require an explicit, referentially stable signal list so React can
run them in the requested phase.
Run the local comparison against raw React state, TanStack Store, Jotai, and Zustand:
bun run benchmarkRepresentative local results on Apple Silicon with Bun 1.3.14 and React 19.2.8:
| Scenario | React Alien Signals | Raw React | Zustand | TanStack | Jotai |
|---|---|---|---|---|---|
| One commit per write | 200–214k/s | 193–242k/s | 183–213k/s | 133–213k/s | 114–140k/s |
| 10k writes, React auto-batched | 12.4–13.0M/s | 14.0–15.7M/s | 9.5–12.2M/s | 6.9–8.1M/s | 0.68–0.74M/s |
| 10k writes, signal graph batched | 46.4–57.8M/s | — | — | — | — |
Each range covers three runs. Every batched case validates the final value and
exactly one update render. batch also collapses intermediate computed and
effect propagation; React's automatic batching only collapses renders.
See CONTRIBUTING.md for local setup and pull request checks.
- Alien Signals by StackBlitz for the foundational signal implementation