What is SolidJS?
SolidJS is a declarative UI library that looks like React but works very differently. It uses JSX and components, yet it has no virtual DOM and does not re-render. Instead, it uses fine-grained reactivity: signals track who reads them, and when a value changes, only the computations and DOM nodes that depend on it update.
The practical effect is that a Solid component function runs exactly once. Everything reactive inside it is wired up during that run, then updated surgically. Once that clicks, Solid’s performance and its rules both make sense.
Signals
A signal is a reactive value created with createSignal. It returns a getter and a setter.
// Counter.jsx
import { createSignal } from "solid-js";
export function Counter() {
const [count, setCount] = createSignal(0);
return (
<button onClick={() => setCount((c) => c + 1)}>
Count: {count()}
</button>
);
}
You read a signal by calling it: count(). That call is what registers a dependency. Passing a function to the setter (setCount((c) => c + 1)) is the safe way to update based on the previous value.
Derived values with createMemo
createMemo computes a value from other signals and caches it. It recomputes only when a dependency changes, and the result is shared between all readers.
// cart.jsx
import { createSignal, createMemo } from "solid-js";
const [items, setItems] = createSignal([{ price: 10 }, { price: 20 }]);
const total = createMemo(() =>
items().reduce((sum, item) => sum + item.price, 0),
);
For simple inline expressions you can also use a plain function, but createMemo avoids repeating expensive work. Reach for it when the computation is costly or read in several places.
Effects
createEffect runs a side effect whenever the signals it reads change. It is for synchronising with the outside world, not for computing values.
// title.jsx
import { createEffect, createSignal } from "solid-js";
const [name, setName] = createSignal("Ada");
createEffect(() => {
document.title = `Hello, ${name()}`;
});
Effects run after rendering and track their dependencies automatically. Use onCleanup to tear down subscriptions, timers and requests.
Components run once
This is the rule that trips up React developers. A Solid component executes one time. It is a setup function that creates reactive bindings, not a function that re-runs on state changes.
function Profile(props) {
// Do not destructure props — that breaks reactivity.
// Read them where you use them instead.
return <h1>{props.user.name}</h1>;
}
Because props are reactive getters, read them where you use them (props.user.name) rather than destructuring. The same applies to signals: call them inside the JSX or inside a memo, not once at the top of the component.
Control flow components
Solid uses components instead of template directives for conditionals and lists, which keeps everything in JavaScript and lets the compiler optimise updates.
// list.jsx
import { Show, For } from "solid-js";
<Show when={users().length > 0} fallback={<p>No users yet.</p>}>
<ul>
<For each={users()}>{(user) => <li>{user.name}</li>}</For>
</ul>
</Show>
Show renders its fallback when the condition is falsy, and For keys items by reference so it can move DOM nodes rather than recreate them. Index is available when you want to key by index instead.
Stores
For nested reactive objects, createStore provides a proxy with path-based updates.
// store.jsx
import { createStore } from "solid-js/store";
const [user, setUser] = createStore({
name: "Ada",
address: { city: "London" },
});
setUser("address", "city", "Paris");
Only the parts of the tree that changed notify their dependents, so updating a deep property does not invalidate unrelated reads. Stores are the recommended way to manage structured state.
Resources and async data
createResource wraps an async function in a signal, giving you loading and error states without manual bookkeeping.
// user.jsx
import { createResource, Show } from "solid-js";
const [user] = createResource(() => fetch("/api/user").then((r) => r.json()));
<Show when={user()} fallback={<p>Loading…</p>}>
<h1>{user().name}</h1>
</Show>;
The resource re-runs when its source signal changes and cancels outdated work, which makes it a natural fit for data that depends on route parameters or user input.
Routing and SolidStart
Solid Router handles client-side navigation, and SolidStart is the meta-framework that adds file-based routing, server rendering and data loading — the equivalent of Next.js for React or SvelteKit for Svelte. For anything beyond a widget, start with SolidStart.
Best practices
- Read signals and props where you use them; never destructure them once.
- Use
createMemofor expensive or widely shared derived values. - Use
createEffectonly for side effects, and clean up withonCleanup. - Prefer
createStorefor nested state. - Use
ShowandForinstead of manual conditionals and.map. - Keep components as setup functions and avoid relying on re-execution.
- Reach for
createResourcefor async data tied to reactive sources.
Common mistakes
- Destructuring props or signal values and breaking reactivity.
- Expecting the component body to run again after a state change.
- Using an effect to compute a value instead of a memo.
- Forgetting
onCleanupand leaking subscriptions or timers. - Using
.mapin JSX instead ofForand losing keyed updates. - Assuming React’s rules apply and reaching for hooks that do not exist.
Where to go next
Solid rewards understanding over memorisation. Compare its model with React, Vue and Svelte, strengthen your JavaScript and TypeScript, and lean on Vite for the build. Then build a small reactive app and watch how little of it re-runs.