Diagnosing Silent Reactivity in SolidJS: Signals, Memos, and Effects That Won’t Trigger
When a SolidJS component updates a signal but the UI stays unchanged, the problem often lies in stale closures, missing dependencies, or batching boundaries. This guide walks through the most common causes, step‑by‑step checks, and targeted fixes so you can restore predictable reactivity.
13 Aug 2025, 07:29 UTC

Problem Statement
In a SolidJS application you update a signal (e.g., count()), click a button, but the DOM does not reflect the new value. The component renders once and then appears frozen. This article provides a diagnostic workflow to pinpoint why reactivity is silent, covering signals, memos, effects, batching, and common pitfalls.
Recognizable Condition
Typical symptoms include:
- A
console.loginside anonMountorcreateEffectruns only once. - The UI shows the initial value forever, even after state changes.
- Solid DevTools shows the signal but no effect or memo is subscribed.
- Updating a signal inside an event handler or async callback has no visible impact.
Short Cause & Diagnostic Table
| Cause | Diagnostic Check | Fix |
|---|---|---|
| Signal read outside a tracking scope | Locate count() outside JSX, createEffect, or createMemo | Move the read into a reactive context or call count() inside the handler body. |
| Stale closure in event handler | Handler defined as a component method or arrow that captures old signal value | Read the signal inside the handler or use useCallback‑style binding. |
Missing dependency in createMemo | Memo body references a signal but never reads it at runtime | Ensure the memo reads the signal or expose it via a computed property. |
| Batching boundary mis‑placement | Multiple set calls inside an async function without awaiting | Wrap updates in createRoot or use await flushSync() (v1.8+). |
Non‑reactive property access in createStore | Direct array mutation (e.g., push) on a raw array | Use the store’s mutable API or produce from solid-js/store. |
| SSR hydration mismatch | Server‑rendered HTML differs from client initial state | Serialize initial signal values and use createResource for async data. |
| Third‑party DOM manipulation | jQuery or D3 modifies DOM outside Solid’s render tree | Trigger a signal write after DOM changes or use onMount to re‑hydrate. |
Ordered Checks & Fixes
- Confirm the signal updates. In the component, add:
Run the app and click the button that callsconst [count, setCount] = createSignal(0); createEffect(() => { console.log('Signal value:', count()); });setCount. The console should log a new value each click. If it does not, the signal is not being updated or the effect is not subscribed. - Inspect the reactive graph. Install the Solid DevTools browser extension. Open the “Reactivity” panel, locate the signal, and check if any effects or memos are attached. An empty dependency list indicates a stale read.
- Verify signal reads inside reactive scopes. Search the component for
count()calls. They must reside inside JSX,createEffect,createMemo, or an event handler that runs during a render cycle. If a read occurs in a plainsetTimeoutcallback or afterawait, the read is outside a tracking scope and will not register. - Check for stale closures. If you have an event handler defined as:
and the handler is passed to a component that memoizes props, the closure may capture the oldconst handleClick = () => { setCount(count() + 1); };countvalue. Move the read inside the handler body or bind the handler withuseCallback‑style syntax:const handleClick = () => { setCount(c => c + 1); }; - Audit memo dependencies. A
createMemothat never reads a signal will never recompute. Ensure the memo body includes a call to the signal:
Ifconst double = createMemo(() => count() * 2);count()is omitted, add it or expose the memo via a computed property. - Examine batching boundaries. Solid batches synchronous updates. If you perform multiple
setcalls inside an async function, the batch may flush before subsequent writes. Wrap updates increateRootor callawait flushSync()(v1.8+). Example:import { flushSync } from 'solid-js'; async function updateAll() { setA(1); setB(2); await flushSync(); // forces immediate render } - Validate store mutations. With
createStore, direct array mutations bypass reactivity. Use the store’s mutable API:const [state, setState] = createStore({ users: [] }); setState('users', users => { users.push({ name: 'Alice' }); }); - Check SSR hydration. In a solid-start project, run:
Open the page, view source, and compare the HTML fornpm run devcountwith the client‑side rendered value. Mismatches indicate a hydration issue. - Inspect third‑party DOM changes. If you use jQuery or D3 to modify the DOM, call
setCountafter the manipulation or wrap the code inonMountandonCleanupto ensure re‑render. - Run unit tests with real timers. In
vitestorjest, avoid fake timers when testing reactivity. Useawait flushPromises()after signal writes and then query the DOM.
Practical Verification Checklist
- Signal updates: console log shows new value on each click.
- Effect or memo: Solid DevTools shows a dependency edge from the signal.
- Batching: after a set of updates, the DOM reflects all changes before the next event loop tick.
- Store: UI updates after mutating nested arrays via the store API.
- SSR: Server HTML matches client initial state; no hydration warnings in console.
Limitations & Escalation Criteria
These checks cover the majority of silent reactivity bugs in SolidJS. However:
- Complex third‑party integrations may introduce subtle DOM mutations that are hard to track.
- Version‑specific changes (e.g., batching behavior in Solid 1.8) may require consulting the changelog.
- Asynchronous data loading via
createResourcecan delay updates if the resource resolves after the initial render.
If, after following all steps, the UI still does not update, consider:
- Creating a minimal reproduction on CodeSandbox and sharing it with the Solid community.
- Reviewing the component tree for deeply nested stores or context providers that may shadow signals.
- Checking for custom
createEffectwrappers that might suppress updates.
Conclusion
SolidJS’s fine‑grained reactivity is powerful but requires careful placement of signal reads, awareness of batching boundaries, and correct store mutations. By systematically checking the conditions above, you can quickly restore predictable UI updates and avoid silent bugs that frustrate developers and users alike.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.