| name | solidjs-reactivity |
| description | Implements SolidJS reactive patterns using signals, effects, and memos. Use when writing or reviewing SolidJS components, state management, or reactive code. Covers the core mental model: components run once, signals track in reactive scopes, and props must not be destructured. |
SolidJS Reactivity
Core Mental Model
SolidJS is NOT React. The fundamental differences:
- Components run once — A component function executes a single time to set up the reactive graph. It never re-runs.
- Reactivity is automatic — Signal reads inside tracking scopes create subscriptions. No dependency arrays.
- Fine-grained updates — Only the specific DOM nodes or computations that depend on a signal update when it changes. No virtual DOM diffing.
function Counter() {
const [count, setCount] = createSignal(0);
console.log("This logs once, not on every update");
return <button onClick={() => setCount(c => c + 1)}>{count()}</button>;
}
createSignal
Creates a reactive value with a getter (accessor) and setter.
import { createSignal } from "solid-js";
const [count, setCount] = createSignal(0);
count();
setCount(5);
setCount(prev => prev + 1);
Options:
const [data, setData] = createSignal(initialData, {
equals: (prev, next) => prev.id === next.id,
});
const [trigger, setTrigger] = createSignal(undefined, { equals: false });
Key rule: Signal getters must be called (with ()) to read the value. count is a function, count() is the value.
Tracking Scopes
Signals are only tracked when read inside a tracking scope. These are:
- JSX expressions —
<div>{count()}</div>
- createEffect —
createEffect(() => console.log(count()))
- createMemo —
createMemo(() => count() * 2)
Reading a signal outside these scopes does NOT create a subscription:
function MyComponent() {
const [count, setCount] = createSignal(0);
console.log(count());
createEffect(() => {
console.log(count());
});
return <div>{count()}</div>;
}
createEffect
Runs side effects when dependencies change. Tracks signals automatically.
import { createSignal, createEffect, onCleanup } from "solid-js";
function Timer() {
const [count, setCount] = createSignal(0);
createEffect(() => {
const id = setInterval(() => setCount(c => c + 1), 1000);
onCleanup(() => clearInterval(id));
});
return <span>{count()}</span>;
}
Execution timing:
- Initial run: after synchronous component code, before browser paint
- Subsequent runs: after tracked dependencies change
- Effects run AFTER all pure computations (memos) in the same update cycle
- Effects never run during SSR
Cleanup: Use onCleanup inside an effect to clean up before re-execution or disposal.
createMemo
Creates a cached derived value. Only recalculates when dependencies change.
import { createSignal, createMemo } from "solid-js";
function FilteredList() {
const [query, setQuery] = createSignal("");
const [items] = createSignal(["apple", "banana", "cherry"]);
const filtered = createMemo(() =>
items().filter(item => item.includes(query()))
);
return <ul><For each={filtered()}>{item => <li>{item}</li>}</For></ul>;
}
Memo vs derived signal (plain function):
const doubled = () => count() * 2;
const doubled = createMemo(() => count() * 2);
Use createMemo when:
- The computation is expensive
- The result is read in multiple places (prevents duplicate computation)
- You need to suppress downstream updates when the result hasn't changed
Use a plain derived signal when:
- The computation is cheap
- It's only read in one place
batch
Groups multiple signal updates so downstream computations run once.
import { batch } from "solid-js";
batch(() => {
setFirstName("John");
setLastName("Doe");
setAge(30);
});
Automatic batching: Updates inside createEffect, onMount, and store setters are already batched. You rarely need explicit batch().
Important: If batch callback is async, only updates before the first await are batched.
untrack
Reads a signal without creating a tracking dependency.
import { untrack } from "solid-js";
createEffect(() => {
console.log(count(), untrack(() => name()));
});
Use untrack when you need a signal's current value without subscribing to changes.
Anti-Patterns — See PITFALLS.md
The most common mistakes when writing SolidJS code are documented in detail in the PITFALLS.md companion file. The top issues:
- Destructuring props (breaks reactivity)
- Conditional access outside JSX (breaks reactivity)
- Setting signals inside derived computations
- Forgetting
() on signal getters
- Accessing signals outside tracking scopes
- Event handler reactivity assumptions
stopPropagation with delegated events
- Async breaking tracking context
- Creating signals/effects outside component scope
- Assuming effect execution order