solidjs Best Practices
This document outlines the definitive best practices for developing with SolidJS 1.x. Adhere to these guidelines to leverage Solid's fine-grained reactivity, ensure optimal performance, and maintain a consistent, scalable codebase.
1. Use SolidJS 1.x (Stable)
Always use the current stable 1.x release. SolidJS 2.0 is experimental and not ready for production. Avoid any features or APIs mentioned for 2.0.
2. Component Architecture & Code Organization
2.1. Component File Naming
Use PascalCase for component filenames. This clearly distinguishes components from other modules.
- ❌ BAD:
mybutton.jsx,user-profile.jsx - ✅ GOOD:
MyButton.jsx,UserProfile.jsx
2.2. Keep Components Lean
Components should be small and focused. Extract complex logic or UI parts into separate components or custom primitives.
2.3. Colocate Styles
Use Vite's CSS Modules for component-specific styling. This provides scoped styles without runtime overhead.
- ❌ BAD: Global CSS files, CSS-in-JS libraries with runtime.
- ✅ GOOD:
// MyComponent.module.css .container { background-color: var(--color-primary); } .text { color: white; } // MyComponent.jsx import styles from './MyComponent.module.css'; function MyComponent() { return <div class={styles.container}><span class={styles.text}>Hello</span></div>; }
3. Props Handling
SolidJS props are reactive getters. Incorrect handling breaks reactivity.
3.1. Never Destructure Props Directly
Do not destructure props at the component's top level. This immediately loses reactivity. Access props via props.propertyName.
- ❌ BAD:
function MyComponent({ name, age }) { // React pattern, breaks Solid reactivity return <div>{name} is {age}</div>; } - ✅ GOOD:
function MyComponent(props) { return <div>{props.name} is {props.age}</div>; // Access directly }
3.2. Apply Default Props with mergeProps
Use mergeProps for default props. This preserves reactivity and merges objects non-destructively.
- ❌ BAD:
function MyComponent(props) { const name = props.name || "Guest"; // Loses reactivity if props.name is a signal return <div>Hello, {name}</div>; } - ✅ GOOD:
import { mergeProps } from 'solid-js'; function MyComponent(props) { const merged = mergeProps({ name: "Guest", greeting: "Hello" }, props); return <div>{merged.greeting}, {merged.name}!</div>; }
3.3. Use splitProps for Spreading
When spreading props, use splitProps to separate known props from rest props. This is crucial for accessibility and avoiding prop conflicts.
- ❌ BAD:
function MyInput(props) { return <input type="text" {...props} />; // Unsafe, can overwrite internal props } - ✅ GOOD:
import { splitProps } from 'solid-js'; function MyInput(props) { const [local, rest] = splitProps(props, ["label", "onChange"]); return ( <label> {local.label} <input type="text" {...rest} /> </label> ); }
4. State Management
4.1. Signals for Local State
Use createSignal for simple, primitive, or local component state. It's the most performant and idiomatic way to manage reactive values.
- ❌ BAD:
let count = 0;(non-reactive) - ✅ GOOD:
import { createSignal } from 'solid-js'; function Counter() { const [count, setCount] = createSignal(0); return <button => setCount(c => c + 1)}>{count()}</button>; }
4.2. Stores for Complex/Global State
Use createStore for nested, mutable objects or global state. Stores provide deep reactivity for objects.
- ❌ BAD:
createSignal({ user: { name: "John" } })(nested updates are not reactive without manual spreading) - ✅ GOOD:
import { createStore } from 'solid-js/store'; const [user, setUser] = createStore({ firstName: "John", lastName: "Doe" }); function UserProfile() { return ( <div> <p>Name: {user.firstName} {user.lastName}</p> <button => setUser("firstName", "Jane")}>Change Name</button> </div> ); }
5. Side Effects & Lifecycle
5.1. createEffect for Side Effects
Encapsulate all side effects in createEffect. Solid automatically tracks dependencies, ensuring effects run only when necessary.
- ❌ BAD:
console.log(mySignal());outsidecreateEffect(runs once) - ✅ GOOD:
import { createSignal, createEffect } from 'solid-js'; function Logger() { const [value, setValue] = createSignal("initial"); createEffect(() => { console.log("Value changed:", value()); // Runs on initial render and whenever value() changes }); return <input => setValue(e.target.value)} value={value()} />; }
5.2. onCleanup for Resource Management
Use onCleanup to dispose of resources (subscriptions, timers, event listeners). It runs when the reactive scope (component, effect) is destroyed.
- ❌ BAD: Global
clearIntervalorremoveEventListenerwithoutonCleanup. - ✅ GOOD:
import { createEffect, onCleanup } from 'solid-js'; function Timer() { createEffect(() => { const interval = setInterval(() => console.log("tick"), 1000); onCleanup(() => clearInterval(interval)); // Cleans up when component/effect unmounts }); return <div>Timer running...</div>; }
6. Performance Considerations
6.1. Avoid Unnecessary Re-renders
Solid's fine-grained reactivity minimizes re-renders, but be mindful of creating new functions or objects inside JSX that aren't memoized.
- ❌ BAD:
<MyComponent data={{ id: 1 }} /> // Creates new object on every parent update - ✅ GOOD:
const data = { id: 1 }; <MyComponent data={data} /> // Object reference remains stable
6.2. Lazy Loading
Use dynamic imports for route-level components. This reduces initial bundle size.
- ✅ GOOD:
import { lazy } from 'solid-js'; import { Routes, Route } from '@solidjs/router'; const HomePage = lazy(() => import('./pages/Home')); const AboutPage = lazy(() => import('./pages/About')); function AppRoutes() { return ( <Routes> <Route path="/" component={HomePage} /> <Route path="/about" component={AboutPage} /> </Routes> ); }
7. Ecosystem Tools
Leverage official and community-vetted tools.
- Bundling:
vite-plugin-solid - Routing:
solid-router(or SolidStart) - Head Management:
solid-meta - Linting:
eslint-plugin-solid - Testing:
solid-testing-library
8. Accessibility
Prioritize semantic HTML and WAI-ARIA attributes. SolidJS does not abstract away the DOM, making direct accessibility implementation straightforward.
- ✅ GOOD:
<button aria-label="Close dialog" <input type="checkbox" id="agree" checked={agreed()} /> <label for="agree">I agree to terms</label>
9. Testing Approaches
Use solid-testing-library for unit and integration tests. It provides a user-centric API similar to React Testing Library.
- ✅ GOOD:
import { render, screen } from 'solid-testing-library'; import { createSignal } from 'solid-js'; import MyComponent from './MyComponent'; test('MyComponent displays the correct count', async () => { const [count, setCount] = createSignal(0); render(() => <MyComponent count={count()} />); expect(screen.getByText(/Count: 0/i)).toBeInTheDocument(); setCount(1); await Promise.resolve(); // Wait for Solid's microtask queue expect(screen.getByText(/Count: 1/i)).toBeInTheDocument(); });