untrack() & batch(): Breaking Dependencies and Grouping Mutations
- Last updated
- Prerequisites:
- SolidJS createSignal
- createEffect tracking scopes
- solidjs
- untrack
- batch
- reactivity
- dependencies
- performance
Read at your depth
The practical view
`untrack(() => signal())` lets you read a signal's value inside an effect or memo without subscribing to it. If the untracked signal changes later, the effect will *not* re-run. `batch(() => { setA(1); setB(2); })` bundles multiple signal updates together so that downstream effects and memos execute only once after the batch completes, rather than firing sequentially.
The same idea in other frameworks
`ReactDOM.unstable_batchedUpdates` or modern automatic batching; reading from a `useRef` to avoid dependency tracking.Angular `untracked(() => signal())` in `@angular/core`.Vue `@vue/reactivity` `toRaw()` or custom non-tracking getters.Svelte 5 `untrack(() => state)` inside `$effect` blocks.Legacy vs modern
Accidental Infinite Effect Loop vs untrack() Isolation
Reading and writing signals inside an effect causes infinite loops; `untrack()` breaks the tracking edge to allow safe reads.
createEffect(() => { if (count() > 0) setLogs([...logs(), 'changed']); }); // ❌ Infinite loop!setLogs([...untrack(logs), `Count is ${count()}`]); // ✅ NEW: Safe one-way dependencyInterview gotchas
Context
Interviewers test your knowledge of Solid's automatic event batching heuristics.
The mechanical answer
No, `batch()` is usually unnecessary in native DOM event handlers (`onClick`, `onInput`) because SolidJS wraps all synthetic/delegated event dispatches in an automatic top-level `batch()` call. However, `batch()` **is** necessary when updating multiple signals inside asynchronous callbacks, `setTimeout`, `fetch.then()`, or custom WebSocket listeners.
Trap
Failing to use `batch()` across multiple signal updates inside `fetch().then()` can cause intermediate computations to run multiple times with inconsistent state.
Context
Checks understanding of dynamic synchronous tracking context.
The mechanical answer
Yes. `untrack(fn)` suppresses dependency tracking for *all* synchronous signal reads that occur during the execution of `fn`, including nested function calls and deep getter access inside `fn`.
Trap
If `fn` is an `async` function, any signal read after an `await` was already not tracked (because async resumptions lose the tracking context), so `untrack` only affects synchronous execution.