Function: useSignalEffect()
API / @xmachines/play-react / useSignalEffect
function useSignalEffect(callback, deps?): void;Defined in: packages/play-react/src/useSignalEffect.ts:80
React hook that subscribes to signal changes and runs effect callback
Wraps the callback in a Signal.Computed to automatically track signal
dependencies accessed inside it, then delegates the watcher lifecycle
(microtask batching, re-arming, disposal) to watchSignal from
@xmachines/play-signals — the single canonical implementation shared
across framework renderers.
Architecture:
- Uses Signal.Computed to wrap callback for automatic dependency tracking
- watchSignal owns the Signal.subtle.Watcher lifecycle (Phase 29 memory safety)
- Microtask batching coalesces rapid signal updates
- Re-rendering is driven by the callback’s own setState (see remarks)
- Handles cleanup on unmount to prevent memory leaks
Invariant: Signal-Only Reactivity - Signals accessed in callback are watched. Invariant: Passive Infrastructure - React observes signals and does not control them.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
callback | () => void | (() => void) | undefined | Effect function that accesses signals. Can return cleanup function. |
deps | DependencyList | [] | Optional dependency list (like useEffect). When any dependency changes, the watcher and Computed are torn down and recreated, re-tracking signals from scratch. Defaults to [] (subscribe once per mount). Pass this when the identity of the object whose signals you read can change over the component’s lifetime (e.g. an actor prop) — otherwise the watcher keeps tracking the OLD object’s signals forever. |
Returns
void
Example
const MyComponent = ({ actor }) => { const [view, setView] = useState(null);
// Subscribe to actor.currentView signal; re-subscribe if the actor swaps useSignalEffect(() => { const currentView = actor.currentView.get(); setView(currentView); }, [actor]);
return <div>{view?.component}</div>;};Remarks
CRITICAL: Signals must be accessed unconditionally (no if statements). Conditional signal access breaks automatic dependency tracking.
Performance: Microtask batching (queueMicrotask, inside watchSignal) prevents React thrashing when multiple signals update rapidly.
Re-rendering: the hook itself does NOT force a React render on signal change — the callback drives re-rendering by calling setState with the new signal value (as in the example above). This lets React’s setState bailout skip renders when the derived value is unchanged; an unconditional force-update here previously caused a wasted render per notification even when the callback’s setState bailed. If you need to re-render on every notification while reading signals during render, mirror the signal value into React state inside the callback.
Implementation note: We wrap the callback in Signal.Computed because Signal.subtle.Watcher cannot automatically track arbitrary function calls. The Computed handles dependency tracking; watchSignal evaluates it (which runs the callback) whenever any tracked signal changes.