react/selector-scheduling
Tune ReactStore signal/.useValue coalescing with throttledSelectorFrequency. Keep scheduling Store-owned: no private scheduler imports, debounce wrappers, or audit-log use.
SKILL.mdskills/react/selector-scheduling/SKILL.md
GitHubReact selector scheduling — internal only
Selector scheduler helpers are package-private implementation details. React consumers must not import scheduler internals, wrap selector outputs in their own debounce layer, or treat selector emissions as event/audit logs.
Public facade: @themislib/themis/react-store. Create selectors through the
configured ReactStore instance and tune selector coalescing only through
ReactStore options such as throttledSelectorFrequency.
Store-first scheduling rule
Scheduling applies to signal-backed render consumers. Choose the consumer API using Call-mode map; this leaf owns cadence, not component/hook/handler/saga boundary rules.
- Create production selectors with the configured
ReactStoreinstance:reactStore.createSelector(...). - Direct
ReadonlySignaloutputs and.useValue(...args)use the owningReactStore's cadenced state stream. The state tick rate is capped bythrottledSelectorFrequency; signal-argument changes can emit changed results immediately and are not capped by that option. The initial snapshot is prompt; later Store writes coalesce at the next scheduled tick. - Same-Store/selector/args outputs share the retained cache entry, including never-observed outputs. Final-consumer cleanup evicts it; follow Selector caching for identity boundaries.
- Tune coalescing only with the final constructor options argument, for example
new ReactStore(reducers, middleware, { throttledSelectorFrequency }). - Omit
throttledSelectorFrequencyfor the default64FPS. Explicit values must be finite numbers in the inclusive1..256range; fractional values are supported. - Selector trace output is disabled by default; pass
{ traceSelectors: true }in the same final options object only for temporary diagnostics. - Snapshot and saga reads do not use React render scheduling; follow React signal consumption guardrails.
- There is no separately named public fast-selector API.
.select(store.state, ...plainArgs)and saga.effect(...plainArgs)are one-shot uncadenced reads, not alternate render subscriptions; selector-channel sagas subscribe to Redux, not this cadence.
Do not
- Do not import selector scheduler helpers from package internals or source-shaped paths.
- Do not wrap selector callbacks, selector signals, or
.useValue(...args)results with ad hocmemoize,cache, debounce,setTimeout,requestAnimationFrame, streams, or proxy state just to reduce React renders. - Do not manually subscribe to selector outputs from React components; use the consumer integration in Call-mode map.
- Do not rely on selector outputs as audit/event streams; they represent the latest derived state and may coalesce intermediate writes.
- Do not replace this React signal scheduling model with unrelated selector scheduling rules in the same React app path.
Examples
Configure coalescing at the ReactStore owner
import { ReactStore } from "@themislib/themis/react-store";
import { pointerReducer } from "./pointer-slice";
export const reactStore = new ReactStore(
{ pointer: pointerReducer },
undefined,
{ throttledSelectorFrequency: 120, traceSelectors: true }
);
Define selectors through the configured Store
import { reactStore } from "./react-store";
type Pointer = { x: number; y: number };
export const selectPointer = reactStore.createSelector((state): Pointer => {
return state.pointer.current;
});
export const selectPointerLabel = reactStore.createSelector((state) => {
const pointer = selectPointer.select(state);
return `${pointer.x},${pointer.y}`;
});
Consumer integration
Direct signal outputs already schedule Store-state changes, not argument changes. Component/hook, handler/test, and saga examples belong to Examples, including the distinction between render subscriptions and one-shot reads.
❌ Bad: manual debounce wrapper around selector output
// BAD: this adds stale local state on top of Store-owned selector coalescing.
import * as React from "react";
import { selectPointer } from "./pointer-selectors";
export function useManuallyDebouncedPointer() {
const pointer = selectPointer();
const [debounced, setDebounced] = React.useState(pointer.value);
React.useEffect(() => {
const id = window.setTimeout(() => setDebounced(pointer.value), 100);
return () => window.clearTimeout(id);
}, [pointer]);
return debounced;
}
❌ Bad: treating selector outputs as event logs
// BAD: selector values are latest-state projections and can coalesce writes.
import * as React from "react";
import { selectPointer } from "./pointer-selectors";
export function PointerAuditPanel({ audit }: { audit: Array<{ x: number; y: number }> }) {
const pointer = selectPointer();
React.useEffect(() => {
audit.push(pointer.value);
}, [audit, pointer]);
return null;
}
Prefer actions or sagas when every event matters
import { call, takeEvery } from "typed-redux-saga";
import { pointerMoved } from "./pointer-slice";
declare const auditLog: { write(event: string, payload: unknown): Promise<void> };
export function* pointerAuditSaga() {
yield* takeEvery(pointerMoved, function* auditPointerMove(action) {
yield* call(auditLog.write, "pointer-moved", action.payload[0]);
});
}
Verification cues
- Cadence is configured at the Store, not with consumer wrappers.
- Selector outputs are latest-state projections, not event logs.
- Consumer examples follow Verification cues.
See also
../selectors/SKILL.md— buildingReactStoreselectors.../selector-lifecycle/SKILL.md— choosing direct signal,.useValue,.select,.effect, and.withStorecall modes.@themislib/themis/docs/SELECTORS.md— selector memoization and lifecycle rules.