Skip to content
Back to skills

Solid Core Reactivity Model

ASecurity

Use when reasoning about SolidJS reactivity, debugging tracking issues, or understanding why components run once. Prevents React mental model contamination such as expecting re-renders, virtual DOM diffing, or stale closure patterns. Covers reactive dependency graph, tracking contexts, ownership tree, synchronous execution model, and direct DOM updates. Keywords: SolidJS reactivity, fine-grained, signals, tracking scope, ownership, createEffect, createMemo, no virtual DOM.

  • 5 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 10, 2026
developmentgoreactnodeexpressrailsdebugginggitapi

Works with

  • claude code
  • cli
  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned September 10, 2026

npx -y skills add OpenAEC-Foundation/OpenAEC-Workspace-Composer --skill solid-core-reactivity-model --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Solid Core Reactivity Model?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Solid Core Reactivity Model
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/openaec-foundation-solid-core-reactivity-model/badge)](https://www.skillsdirectory.com/skills/openaec-foundation-solid-core-reactivity-model)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: solid-core-reactivity-model
description: >
  Use when reasoning about SolidJS reactivity, debugging tracking issues, or understanding why components run once.
  Prevents React mental model contamination such as expecting re-renders, virtual DOM diffing, or stale closure patterns.
  Covers reactive dependency graph, tracking contexts, ownership tree, synchronous execution model, and direct DOM updates.
  Keywords: SolidJS reactivity, fine-grained, signals, tracking scope, ownership, createEffect, createMemo, no virtual DOM.
license: MIT
compatibility: "Designed for Claude Code. Requires SolidJS 1.x/2.x."
metadata:
  author: OpenAEC-Foundation
  version: "1.0"
---

# solid-core-reactivity-model

## Quick Reference

### The Fundamental Mental Model

SolidJS has NO virtual DOM. Components run **exactly once** to set up a reactive dependency graph. Only reactive expressions (effects, memos, JSX bindings) re-execute when their dependencies change. The DOM is updated **directly** through reactive bindings — no diffing, no reconciliation, no re-rendering.

### Tracking Scope Matrix

| Context | Tracking? | Re-executes on change? | Purpose |
|---------|-----------|----------------------|---------|
| `createEffect(() => ...)` | YES | YES | Side effects |
| `createMemo(() => ...)` | YES | YES | Cached derived values |
| `createRenderEffect(() => ...)` | YES | YES | Synchronous render-phase effects |
| `createComputed(() => ...)` | YES | YES | Pre-render state sync |
| JSX expression `{count()}` | YES | YES | DOM text/attribute bindings |
| Component function body | NO | NO — runs once | Setup only |
| Event handlers `onClick` | NO | NO — runs on demand | User interaction |
| `onMount(() => ...)` | NO | NO — runs once after mount | DOM initialization |
| `untrack(() => ...)` | NO | NO — explicitly suppressed | Read without subscribing |

### Reactive Dependency Graph

```
Signal (source)          Memo (derived)         Effect (sink)
  [count]  ──────────►  [double]  ──────────►  [DOM update]
     │                                              ▲
     └──────────────────────────────────────────────┘
                    (direct subscription)
```

- **Signals** are sources — they store values and notify subscribers
- **Memos** are derived nodes — they cache computed values and propagate changes
- **Effects** are sinks — they perform side effects, NEVER produce reactive values
- Dependencies are tracked **automatically** when a signal/memo getter is called inside a tracking scope

### Execution Models

| Aspect | SolidJS 1.x | SolidJS 2.x |
|--------|-------------|-------------|
| Signal propagation | Synchronous — updates propagate immediately | Microtask-batched — reads reflect after flush |
| Forcing immediate propagation | N/A (already synchronous) | `flush()` |
| Effect timing | After render, before paint | Compute-then-apply split |
| Lifecycle hook | `onMount` | `onSettled` (can return cleanup) |
| Async support | Manual via `createResource` | First-class — computations can return Promises |

### Critical Warnings

**NEVER** assume components re-render — SolidJS component functions run ONCE. Only reactive expressions inside tracking scopes re-execute. Placing logic in the component body expecting it to re-run is the #1 cause of broken SolidJS code.

**NEVER** destructure props or signal results outside a tracking scope — this captures a static snapshot and permanently breaks reactivity.

**NEVER** use dependency arrays — SolidJS tracks dependencies automatically. There is no equivalent to React's `useEffect([deps])` or `useMemo([deps])`.

**NEVER** return cleanup functions from effects — use `onCleanup()` as a separate call instead. Returning a function from `createEffect` does NOT register cleanup.

**NEVER** access signals conditionally or after early returns inside effects — signals accessed after an early `return` or inside an `if` branch are only tracked when that code path executes, causing intermittent tracking failures.

**ALWAYS** call signal getters as functions (`count()`, not `count`) — the getter function call is what registers the dependency in the tracking scope.

**ALWAYS** access props directly (`props.name`) or use `splitProps`/`mergeProps` — NEVER destructure props at the function parameter level.

---

## How JSX Compilation Works

SolidJS compiles JSX into **direct DOM creation** calls with reactive bindings. There is no virtual DOM tree, no diff algorithm, no reconciliation.

```tsx
// What you write:
const el = <div class={className()}>{count()}</div>;

// What the compiler produces (conceptual):
const el = document.createElement("div");
createRenderEffect(() => el.className = className());
createRenderEffect(() => el.textContent = count());
```

Each `{expression}` in JSX becomes a fine-grained reactive binding. When `count()` changes, ONLY that specific text node updates — the `div` element, its attributes, and all sibling nodes remain untouched.

---

## Ownership Tree and Disposal

SolidJS organizes reactive computations into an **ownership tree**. Every computation (effect, memo) is owned by the scope that created it. When a scope disposes, all child computations are automatically cleaned up.

### Key Primitives

| Primitive | Purpose |
|-----------|---------|
| `createRoot(fn)` | Creates a new ownership root — computations inside are NOT auto-disposed |
| `getOwner()` | Returns the current tracking owner (or `null` outside any scope) |
| `runWithOwner(owner, fn)` | Executes `fn` under a specific owner's scope |
| `onCleanup(fn)` | Registers cleanup for when the current scope disposes or re-executes |

### Ownership Hierarchy

```
createRoot (application root)
  └── Component A (runs once)
        ├── createEffect (owned by A)
        │     └── onCleanup (runs on re-execute or dispose)
        ├── createMemo (owned by A)
        └── Child Component B (owned by A)
              └── createEffect (owned by B)
```

When Component A unmounts, ALL its owned computations (effects, memos, child components) are disposed automatically.

### When to Use createRoot

**ALWAYS** use `createRoot` when creating reactive computations outside of a component tree — for example, in a standalone reactive system, a test harness, or a module-level subscription.

```tsx
// WRONG: Effect created outside ownership — memory leak, no cleanup
createEffect(() => console.log(globalSignal()));

// CORRECT: createRoot provides ownership and disposal
const dispose = createRoot((dispose) => {
  createEffect(() => console.log(globalSignal()));
  return dispose;
});
// Later: dispose() to clean up
```

### runWithOwner for Async Contexts

Async callbacks lose their tracking owner. Use `runWithOwner` to restore it:

```tsx
function AsyncComponent() {
  const owner = getOwner();

  onMount(async () => {
    const data = await fetchData();
    // WRONG: createEffect here has no owner (async broke the scope)
    // CORRECT: restore the owner explicitly
    runWithOwner(owner!, () => {
      createEffect(() => console.log(data, someSignal()));
    });
  });
}
```

---

## Synchronous vs Batched Execution

### SolidJS 1.x: Synchronous by Default

Signal updates propagate **immediately**. Each `setSignal()` call triggers all dependent computations synchronously before the next line of code executes.

```tsx
const [a, setA] = createSignal(1);
const [b, setB] = createSignal(2);

createEffect(() => console.log(a() + b()));

setA(10); // Effect runs immediately: logs 12
setB(20); // Effect runs immediately: logs 30
```

Use `batch()` to defer propagation:

```tsx
batch(() => {
  setA(10);
  setB(20);
}); // Effect runs ONCE: logs 30
```

### SolidJS 2.x: Microtask-Batched by Default

All signal updates are batched automatically. Reads do NOT reflect updates until the microtask batch flushes. Use `flush()` when immediate propagation is needed.

```tsx
// 2.x behavior:
setA(10);
setB(20);
// Effects have NOT run yet — batched until microtask flush

flush(); // Force immediate propagation
```

---

## Version-Specific Changes

### Key 2.x Reactivity Changes

- **Microtask batching**: Default behavior — no manual `batch()` needed for most cases
- **`flush()`**: Forces immediate propagation when synchronous behavior is required
- **Async first-class**: Computations can return Promises; the graph handles suspension automatically
- **`onMount` renamed to `onSettled`**: Can return a cleanup function
- **Dev guardrails**: Warnings for accidental top-level reads and reactive scope writes
- **`createSignal(fn)`**: Derived-but-writable signals (function argument creates derived initial value)

---

## Decision Trees

### "Should this code be in a tracking scope?"

1. Does the code read a signal/memo value? → If NO → tracking scope not needed
2. Should the code re-run when that value changes? → If NO → use `untrack()` or read in component body
3. Does the code produce a derived value? → If YES → use `createMemo`
4. Does the code perform a side effect? → If YES → use `createEffect`
5. Does the code update the DOM directly? → If YES → use `createRenderEffect`

### "Why is my value not updating?"

1. Are you calling the signal getter as a function? → `count()` not `count`
2. Is the access inside a tracking scope? → Component body is NOT tracked
3. Did you destructure props or store values? → Destructuring breaks tracking
4. Is there an early return BEFORE the signal access? → Move signal reads before returns
5. Is the access inside `untrack()`? → This explicitly suppresses tracking
6. Is the signal inside a conditional branch? → Access unconditionally, use value conditionally

---

## Reference Links

- [references/methods.md](references/methods.md) — API signatures for createRoot, getOwner, runWithOwner, tracking scope primitives
- [references/examples.md](references/examples.md) — Reactive graph examples, tracking demos, ownership patterns
- [references/anti-patterns.md](references/anti-patterns.md) — React mental model violations: re-render assumption, VDOM thinking, dependency arrays

### Official Sources

- https://docs.solidjs.com/concepts/intro-to-reactivity
- https://docs.solidjs.com/concepts/signals
- https://docs.solidjs.com/concepts/effects
- https://docs.solidjs.com/reference/basic-reactivity/create-signal
- https://docs.solidjs.com/reference/basic-reactivity/create-effect
- https://docs.solidjs.com/reference/basic-reactivity/create-memo
- https://docs.solidjs.com/reference/reactive-utilities/batch
- https://docs.solidjs.com/reference/reactive-utilities/untrack
- https://github.com/solidjs/solid/releases

Files in this skill

  • SKILL.md10.6 KB
  • references/anti-patterns.md11.6 KB
  • references/examples.md10.1 KB
  • references/methods.md8.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.