| name | solidjs |
| description | Best practices and idiomatic patterns for SolidJS development. Use when writing, reviewing, or refactoring SolidJS components, signals, stores, effects, or reactive primitives. Triggers on: SolidJS, Solid.js, solid-js, createSignal, createEffect, createMemo, createStore, createResource, or when the project uses SolidJS as its UI framework.
|
SolidJS Best Practices
Reactivity Model
SolidJS components run once as setup code. The component body is not reactive. Reactivity only
occurs inside reactive scopes: JSX expressions, createEffect, createMemo, and createResource.
All guidance below follows from this core principle.
Rules
1. Call signal accessors when passing to JSX props
Pass id={id()} not id={id}. Components should not need to know whether a prop came from a
signal or a static value. Calling the accessor at the call site keeps prop types uniform.
<User id={id()} name="Brenley" />
<User id={id} name="Brenley" />
2. Never destructure props
Props are reactive via getters. Destructuring extracts the value and breaks tracking.
function User(props: { name: string }) {
const { name } = props;
return <h1>{name}</h1>;
}
function User(props: { name: string }) {
return <h1>{props.name}</h1>;
}
Use splitProps when prop splitting is genuinely needed.
3. Wrap derived values in functions or createMemo
A signal read in the component body runs once and never updates.
const doubled = count() * 2;
const doubled = () => count() * 2;
const doubled = createMemo(() => count() * 2);
Use a plain function wrapper when the computation is trivial. Use createMemo when the derived
value is read in multiple places or the computation is expensive.
4. Use <Show> and <For> instead of JS conditionals/map
Solid provides control-flow components that integrate with its reactive system.
<Show when={open()} fallback={<EmptyState />}>
<SidebarMenu />
</Show>
{open() && <SidebarMenu />}
<For each={items()}>{item => <Item item={item} />}</For>
{items().map(item => <Item item={item} />)}
<For> preserves item identity and applies minimal DOM updates. <Show> gives explicit
conditional rendering with fallback support.
5. Use createEffect sparingly
createEffect is for side effects that interact with the outside world (DOM APIs, third-party
libraries). It is not for derived state or data fetching.
Anti-pattern — derived state via effect:
const [count, setCount] = createSignal(0);
const [double, setDouble] = createSignal(0);
createEffect(() => setDouble(count() * 2));
const double = createMemo(() => count() * 2);
Anti-pattern — data fetching via effect:
const [posts, setPosts] = createSignal([]);
createEffect(async () => {
const data = await fetch("/api/posts").then(r => r.json());
setPosts(data);
});
const [posts] = createResource(() => fetch("/api/posts").then(r => r.json()));
createResource integrates with Suspense and handles loading, error states, and race conditions.
6. Derive state instead of syncing it
When one value is a pure function of another, express that relationship directly instead of syncing
via effects.
const [firstName, setFirstName] = createSignal("John");
const [lastName, setLastName] = createSignal("Doe");
const [fullName, setFullName] = createSignal("");
createEffect(() => setFullName(`${firstName()} ${lastName()}`));
const fullName = () => `${firstName()} ${lastName()}`;
Derived values keep the dependency graph explicit. Effects hide relationships and add unnecessary
indirection.
7. Use stores for complex/nested objects
Signals replace the entire value on update. Stores provide fine-grained reactivity at the property
level.
const [board, setBoard] = createStore({
boards: ["Board 1", "Board 2"],
notes: ["Note 1", "Note 2"],
});
setBoard("notes", notes => [...notes, "Note 3"]);
const [board, setBoard] = createSignal({
boards: ["Board 1", "Board 2"],
notes: ["Note 1", "Note 2"],
});
setBoard({ ...board(), notes: [...board().notes, "Note 3"] });
Stores are deeply reactive. Accessing board.settings.theme in JSX tracks only that path.
Summary Checklist
When writing or reviewing SolidJS code, verify: