untrack()
Read signals without creating reactive subscriptions.
const value = untrack(fn)
Parameters
| Parameter | Type | Description |
|---|---|---|
fn |
() => T |
A function in which signal reads do not create subscriptions. The function is called synchronously. |
Returns
The return value of fn.
| Return | Type | Description |
|---|---|---|
value |
T |
Whatever fn returns. Signals read inside fn return their current values, but the surrounding effect or computed does not subscribe to them. |
Usage
Reading Without Subscribing
import { signal, effect, untrack } from 'what-framework';
const count = signal(0);
const multiplier = signal(2);
effect(() => {
// Re-runs when count changes
// Does NOT re-run when multiplier changes
const result = count() * untrack(() => multiplier());
console.log(result);
});
Preventing Infinite Loops
const items = signal([]);
const log = signal([]);
effect(() => {
const currentItems = items();
// Read log without subscribing to avoid cycle
const currentLog = untrack(() => log());
log.set([...currentLog, `Items changed: ${currentItems.length}`]);
});
Event Handlers
A handler fired by the browser runs with no tracking context, so signal reads inside it
create no subscription. That holds on both render paths, but for different reasons, and the
two diverge if you invoke a handler yourself from inside an effect or a computed. Handlers
attached through h() are wrapped in untrack() by the DOM layer;
compiled JSX routes handlers through event delegation, which does not wrap them, so a
handler called from inside a tracking context does subscribe there.
function Counter() {
const count = signal(0, 'count');
const step = signal(1, 'step');
return (
<button onClick={() => {
// peek() reads step without subscribing, so calling this handler
// from inside an effect or computed adds no dependency on step.
count.set(c => c + step.peek());
}}>
{() => `Count: ${count()}`}
</button>
);
}
Comparison With peek()
const count = signal(0);
effect(() => {
// These are equivalent:
const a = untrack(() => count());
const b = count.peek();
// Both read the value without subscribing
});
How It Works
untrack() temporarily sets the current effect tracking context to null while executing fn. Signal reads check this context to decide whether to add a subscriber. With the context cleared, no subscriptions are created. After fn completes, the original context is restored.
Notes
untrack()is functionally equivalent tosignal.peek()for a single signal, but works with any number of signal reads inside the callback.- Use
peek()when reading a single known signal. Useuntrack()when calling a function that may read multiple signals internally. - Handlers attached through
h()are wrapped inuntrack()by What's DOM layer. Compiled JSX uses event delegation and does not wrap them, so usepeek()oruntrack()rather than relying on the wrapper. - The callback is executed synchronously. It does not defer or schedule anything.
- Useful for breaking read-write cycles where an effect needs to read a signal that it also writes to.