react/signals
Use Preact signals in ReactStore components/custom hooks: ReadonlySignal, .value, computed, JSX rendering, Babel/useSignals tracking, and component-local signals. Selector call modes belong to selecto
SKILL.mdskills/react/signals/SKILL.md
GitHubReact/Preact signals — tracking and consumption
Use this skill for React apps that chose ReactStore and Preact Signals. It
owns general signal tracking and component-local signals;
Call-mode map owns ReactStore
selector consumption choices, while ../selectors/SKILL.md owns authoring.
This skill covers React signal consumption only. Keep selector outputs and component-local signals within the React tracking mechanisms described below.
Signal model
- A signal is an object whose current value is read through
.value. ReadonlySignal<T>is the correct type for ReactStore selector outputs and computed outputs. Treat it as read-only: read.value, do not assign it.- Mutable
Signal<T>values may assign.value, but app/domain shared state should live in reducers and ReactStore selectors instead of module-level shared signals. computed(fn)returns a read-only computed signal. Dependencies are tracked from signal.valuereads insidefn.- ReactStore direct selector calls already return scheduled
ReadonlySignal<R>values; do not wrap them in extracomputed, debounce, cache, or subscription layers just to make them reactive.
React tracking requirement
React components that read signal.value must be rendered under one of the
official React tracking mechanisms:
- Prefer the
@preact/signals-reactBabel transform for automatic component reactivity when the component statically reads.value. - If the transform is unavailable for the file, call
useSignals()from@preact/signals-react/runtimein the component/custom hook before reading direct signal values.selector.useValue(...args)already uses this runtime path internally and is only a plain-value fallback boundary. - When a component can avoid reading
.value, passing or rendering the signal intentionally is valid. The React adapter supports direct signal rendering in JSX text; for React 18 typing, wrap text signal output in a Fragment when needed.
Static transform limits matter. Signals read in render props, callback bodies,
object getters/setters, or other code the transform cannot see may not be
tracked. Move the read into a small component that statically accesses
.value, pass the ReadonlySignal<T> through, or use the explicit
useSignals() fallback in that component.
Component-local signals
Use component-local signal hooks when the state is genuinely local UI state:
import { useComputed, useSignal, useSignalEffect } from "@preact/signals-react";
export function DraftTitle() {
const draft = useSignal("");
const remaining = useComputed(() => 80 - draft.value.length);
useSignalEffect(() => {
document.title = `${remaining.value} left`;
});
return <input value={draft.value} onInput={(event) => draft.value = event.currentTarget.value} />;
}
Rules:
- Use
useSignal,useComputed, anduseSignalEffectinside React components or custom hooks for component-local signal state/effects. - Do not call
signal()in a component render to create a new signal each render. - Do not move app shared/domain state into module-level
signal(...)just to avoid reducers, selectors, or actions. - Keep business side effects in sagas; reserve
useSignalEffectfor UI-local integration like focus, measurement, document title, or imperative widgets.
ReactStore selector signal consumption
ReactStore selectors produce read-only signals that use the tracking mechanisms above. For component/prop examples and necessary plain-value fallbacks, follow React signal consumption guardrails and Examples; this skill does not define a second selector call-mode policy.
Direct JSX signal rendering
import { useComputed, useSignal } from "@preact/signals-react";
export function DraftLength() {
const draft = useSignal("");
const label = useComputed(() => `${draft.value.length} characters`);
return <><input onChange={(event) => draft.value = event.currentTarget.value} />{label}</>;
}
Use direct JSX signal rendering only when the JSX position intentionally accepts
a signal as text. For props, conditions, array/object operations, or values sent
to non-signal-aware APIs, read .value in a tracked component or use a
documented plain-value fallback.
Selector-specific fallback choices are in Fallback hook/plain-value read.
Do / don't
Do:
- Type selector results and props as
ReadonlySignal<T>when consumers can be signal-aware. - Read
.valuewhere a plain value is needed and make sure the read is tracked. - Pass
ReadonlySignal<T>through props to small presentational components that read it statically. - Use
computed()for local derived signals, not for duplicating Store selector memoization.
Don't:
- For selector results, apply the signal/plain-value boundaries in Pitfalls.
- Do not destructure, map, compare, or serialize a signal object as if it were the selected value.
- Do not replace ReactStore selectors with module-level shared signals for app domain data.
Verification cues
- Component examples that read
.valuemention Babel transform or explicituseSignals()tracking. - Selector-specific examples follow Verification cues.
- React signal guidance remains scoped to ReactStore selector outputs and component-local signal state.