SolidJS Cheat Sheet
A reference for SolidJS signals, effects, memos, and control-flow components for building fine-grained reactive UIs without a virtual DOM.
Signals, Effects & Memos
The core primitives for fine-grained reactivity.
import { createSignal, createEffect, createMemo } from 'solid-js';function Counter() { const [count, setCount] = createSignal(0); const doubled = createMemo(() => count() * 2); createEffect(() => { console.log('count changed to', count()); }); return ( <button onClick={() => setCount(count() + 1)}> {count()} / {doubled()} </button> );}
Control Flow Components
Solid uses components instead of JS operators for conditionals and loops.
import { Show, For } from 'solid-js';function List(props) { return ( <Show when={props.items.length > 0} fallback={<p>No items</p>}> <ul> <For each={props.items}> {(item) => <li>{item.name}</li>} </For> </ul> </Show> );}
Core Concepts
What makes Solid's reactivity model different from React's.
- createSignal- returns a getter/setter pair; calling the getter tracks it as a dependency
- createEffect- re-runs a function whenever any signal it reads changes
- createMemo- caches a derived computation, recalculating only when its dependencies change
- <For>- keyed list-rendering component, more efficient than .map() for reactive arrays
- <Show>- conditional rendering component with a 'when' prop and optional 'fallback'
- createStore- creates a nested reactive store object for more complex state
- No virtual DOM- Solid compiles JSX to real DOM update calls, so component functions run once, not on every render
Stores
Fine-grained reactivity for nested objects.
import { createStore } from 'solid-js/store';const [state, setState] = createStore({ user: { name: 'Ana', age: 30 } });setState('user', 'age', (age) => age + 1); // update a nested fieldconsole.log(state.user.age);
Resources & Suspense
createResource wires an async fetcher to a signal-like value and integrates with Suspense/ErrorBoundary for loading and error states.
import { createResource, createSignal, Suspense, ErrorBoundary } from 'solid-js';async function fetchUser(id) { const res = await fetch(`/api/users/${id}`); if (!res.ok) throw new Error('failed to load user'); return res.json();}function Profile() { const [userId, setUserId] = createSignal(1); const [user, { mutate, refetch }] = createResource(userId, fetchUser); return ( <ErrorBoundary fallback={(err) => <p>Error: {err.message}</p>}> <Suspense fallback={<p>Loading...</p>}> <h1>{user()?.name}</h1> <button onClick={() => setUserId(userId() + 1)}>Next</button> <button onClick={() => refetch()}>Refresh</button> </Suspense> </ErrorBoundary> );}
Untrack, Batch & On
Lower-level primitives for controlling exactly when and what an effect tracks.
import { createSignal, createEffect, untrack, batch, on } from 'solid-js';const [a, setA] = createSignal(1);const [b, setB] = createSignal(2);// only re-runs when `a` changes, but can still read `b` without tracking itcreateEffect(on(a, (aVal) => { console.log(aVal, untrack(() => b()));}));function updateBoth() { batch(() => { setA(a() + 1); // both updates flush together -- setB(b() + 1); // effects run once, not twice });}
Custom Directives
Directives are plain functions registered with use: to attach imperative DOM behavior declaratively.
import { onCleanup } from 'solid-js';function clickOutside(el, accessor) { const handler = (e) => !el.contains(e.target) && accessor()?.(e); document.addEventListener('click', handler, true); onCleanup(() => document.removeEventListener('click', handler, true));}function Dropdown(props) { return ( <div use:clickOutside={() => props.onClose()}> {props.children} </div> );}// register the directive's type so TS doesn't strip the unused import:// declare module 'solid-js' { namespace JSX { interface Directives { clickOutside: () => void } } }
Context API
createContext + useContext share reactive state deep in a tree without prop drilling.
import { createContext, useContext, createSignal } from 'solid-js';const ThemeContext = createContext();export function ThemeProvider(props) { const [theme, setTheme] = createSignal('dark'); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {props.children} </ThemeContext.Provider> );}export function useTheme() { const ctx = useContext(ThemeContext); if (!ctx) throw new Error('useTheme must be used within ThemeProvider'); return ctx;}
Advanced Reactivity APIs
Less common primitives that matter once you're past basic counters.
- createRoot- creates a non-tracked owner scope; required outside components to prevent memory leaks from signals/effects
- onCleanup- registers a disposal callback that runs when the owning scope re-runs or is disposed
- mergeProps- reactively merges default props into a props object without losing reactivity
- splitProps- splits a props object into multiple reactive slices, e.g. to forward some props to a child
- createDeferred- creates a readonly signal that updates using requestIdleCallback-style deferred scheduling
- lazy- wraps a dynamic import() for code-split components, used with Suspense
- produce (store)- Immer-style mutable syntax for updating createStore values while preserving fine-grained diffing
- reconcile (store)- diffs new data into an existing store by key, minimizing DOM churn on data refetches
A Solid component function body runs only once -- reactivity lives inside signals, not re-renders. Never destructure props (const { name } = props), since that reads the value once and breaks reactivity; access them as props.name instead.