On This Page
Creating
Running
Lifecycle
Async Reactions
Properties
Reaction
A reaction is a computation that re-runs whenever the signals it read change. Use it for side effects and computations that respond to reactive state.
Creating
reaction
reaction(callback, options);Creates a reaction and runs it once immediately, tracking the signals the callback reads. It re-runs whenever any of them change. The callback receives the Reaction instance.
Parameters
| Name | Type | Description |
|---|---|---|
| callback | function | Runs reactively, receiving the reaction instance |
| options | object | Optional configuration |
Options
| Name | Type | Default | Description |
|---|---|---|---|
| firstRun | boolean | true |
Run the callback immediately on creation. Set false to create the reaction without an initial run |
| context | object | undefined |
Debugging context surfaced in tracing output. See Debugging Reactivity |
Returns
The created Reaction.
Usage
import { signal, reaction } from '@semantic-ui/reactivity';
const count = signal(0);
const counter = reaction(() => { console.log('count is', count.get());});
count.set(1); // logs: count is 1Pass firstRun: false to defer the initial run until you call run() yourself.
const deferred = reaction(() => { console.log(count.get());}, { firstRun: false });
deferred.run(); // tracks dependencies and logs for the first timeExample
Running
run
reaction.run();Executes the callback, rebuilding the tracked dependency set from scratch. Called automatically on creation and whenever a dependency changes. Call it manually to force a re-run or to perform the first run of a reaction created with firstRun: false.
Lifecycle
onCleanup
reaction.onCleanup(callback);Registers a cleanup callback that fires just before the reaction’s next run and once when it stops. Use it to tear down resources or scope inner reactions to this one.
Parameters
| Name | Type | Description |
|---|---|---|
| callback | function | Runs on the next re-run or on stop |
Cleanups fire in registration order, and the queue is cleared after firing. A cleanup registered during firstRun fires once, before the second run.
Cleanups fire in registration order, and the queue is cleared after firing. A cleanup registered during firstRun fires once, before the second run.
Usage
const channel = signal('updates');
reaction((comp) => { const socket = subscribe(channel.get()); // tears down the old socket before resubscribing, and on stop comp.onCleanup(() => socket.close());});stop
reaction.stop();Permanently stops the reaction. It unsubscribes from every dependency, fires its cleanups, and no longer responds to changes. Idempotent.
Usage
const count = signal(0);
const counter = reaction((comp) => { console.log(count.get()); if (count.get() > 5) { comp.stop(); // detach once the threshold is crossed }});
count.set(6); // logs 6, then stopscount.set(7); // no outputExample
Async Reactions
A reaction callback may be async. Return a promise and the run is treated as asynchronous: the reaction tracks the signals read before the first await, then stays in flight until the promise settles.
import { signal, reaction } from '@semantic-ui/reactivity';
const userId = signal(1);const user = signal(null);
reaction(async (comp) => { const id = userId.get(); // tracked, read before the first await const res = await fetch(`/api/users/${id}`, { signal: comp.abortSignal }); user.set(await res.json()); // writes never need track});Runs never overlap. When a dependency changes while a run is in flight, the reaction aborts that run’s abortSignal and coalesces every change into a single re-run. The re-run starts once the in-flight promise settles, so the latest run wins and intermediate states never launch a run of their own. Rejections report through console.error and never surface as unhandled rejections.
An async re-run starts at the flush drain point, after pending sync reactions and before the afterFlush snapshot. firstRun stays true through the whole first run, including continuations after an await, and flips once the promise settles.
An async re-run starts at the flush drain point, after pending sync reactions and before the afterFlush snapshot. firstRun stays true through the whole first run, including continuations after an await, and flips once the promise settles.
track
reaction.track(callback);Re-enters dependency tracking for a synchronous block after an await. Signals read inside the callback register on the reaction and accumulate into the current run, so an async reaction can depend on values it reads past its first await. Reads after an await without track() register nothing.
Parameters
| Name | Type | Description |
|---|---|---|
| callback | function | Synchronous function whose signal reads should register on the reaction |
Returns
The callback’s return value.
Usage
const query = signal('');const page = signal(1);
reaction(async (comp) => { const q = query.get(); // tracked normally const results = await search(q); const n = comp.track(() => page.get()); // page re-runs the reaction too show(results, n);});abortSignal
reaction.abortSignal;A per-run AbortSignal, created on first access and aborted when the run is superseded by an invalidation, a re-run, or stop(). Pass it to fetch or any abortable work to cancel in-flight IO when the reaction re-runs. Sync reactions get one too, the signal a run reads aborts just before the next run reads a fresh one.
Usage
const query = signal('');const results = signal([]);
reaction(async (comp) => { const q = query.get(); // the previous run's request aborts automatically when query changes const res = await fetch(`/search?q=${q}`, { signal: comp.abortSignal }); results.set(await res.json());});Properties
firstRun
reaction.firstRun;true while the callback is executing for the first time, false on every later run. Useful for one-time initialization.
Read the signals you depend on before any early if (firstRun) return. Bail out first and the reaction registers no dependencies and never re-runs.
Read the signals you depend on before any early if (firstRun) return. Bail out first and the reaction registers no dependencies and never re-runs.
Usage
const data = signal({ value: 0 });
reaction((comp) => { const current = data.get(); // track before branching if (comp.firstRun) { console.log('initial setup'); } else { console.log('value updated:', current.value); }});Example
active
reaction.active;true while the reaction is responding to changes, false once stop() has been called.
Usage
const counter = reaction((comp) => { console.log('active:', comp.active);});
counter.stop();counter.active; // falsecurrent
Reaction.current;The reaction currently executing, or null outside a reactive run. A static mirror of Scheduler.current, kept on the class so a debugger breakpoint can read it without importing currentReaction().
Returns
A Reaction or null.