solid-js-best-practices

📁 richardcarls/solid-js-best-practices 📅 Jan 25, 2026
14
总安装量
8
周安装量
#23849
全站排名
安装命令
npx skills add https://github.com/richardcarls/solid-js-best-practices --skill solid-js-best-practices

Agent 安装分布

opencode 7
claude-code 7
codex 7
github-copilot 6
amp 5

Skill 文档

Solid.js Best Practices

Comprehensive best practices for building Solid.js applications and components, optimized for AI-assisted code generation, review, and refactoring.

Quick Reference

Essential Imports

import {
  createSignal,
  createEffect,
  createMemo,
  createResource,
  onMount,
  onCleanup,
  Show,
  For,
  Switch,
  Match,
  Index,
  Suspense,
  ErrorBoundary,
  lazy,
  batch,
  untrack,
  mergeProps,
  splitProps,
  children,
} from "solid-js";

import { createStore, produce, reconcile } from "solid-js/store";

Component Skeleton

import { Component, JSX, mergeProps, splitProps } from "solid-js";

interface MyComponentProps {
  title: string;
  count?: number;
  onAction?: () => void;
  children?: JSX.Element;
}

const MyComponent: Component<MyComponentProps> = (props) => {
  // Merge default props
  const merged = mergeProps({ count: 0 }, props);

  // Split component props from passed-through props
  const [local, others] = splitProps(merged, ["title", "count", "onAction"]);

  // Local reactive state
  const [value, setValue] = createSignal("");

  // Derived/computed values
  const doubled = createMemo(() => local.count * 2);

  // Side effects
  createEffect(() => {
    console.log("Count changed:", local.count);
  });

  // Lifecycle
  onMount(() => {
    console.log("Component mounted");
  });

  onCleanup(() => {
    console.log("Component cleanup");
  });

  return (
    <div {...others}>
      <h1>{local.title}</h1>
      <p>Count: {local.count}, Doubled: {doubled()}</p>
      <input
        value={value()}
        onInput={(e) => setValue(e.currentTarget.value)}
      />
      <button onClick={local.onAction}>Action</button>
      {props.children}
    </div>
  );
};

export default MyComponent;

Rules by Category

1. Reactivity (6 rules)

# Rule Priority Description
1-1 Use Signals Correctly CRITICAL Always call signals as functions count() not count
1-2 Use Memo for Derived Values HIGH Use createMemo for computed values, not createEffect
1-3 Effects for Side Effects Only HIGH Use createEffect only for side effects, not derivations
1-4 Avoid Setting Signals in Effects MEDIUM Setting signals in effects can cause infinite loops
1-5 Use Untrack When Needed MEDIUM Use untrack() to prevent unwanted reactive subscriptions
1-6 Batch Signal Updates LOW Use batch() for multiple synchronous signal updates

2. Components (8 rules)

# Rule Priority Description
2-1 Never Destructure Props CRITICAL Destructuring props breaks reactivity
2-6 Components Return Once CRITICAL Never use early returns — use <Show>, <Switch>, etc. in JSX
2-2 Use mergeProps HIGH Use mergeProps for default prop values
2-3 Use splitProps HIGH Use splitProps to separate prop groups safely
2-7 No React-Specific Props HIGH Use class not className, for not htmlFor
2-4 Use children Helper MEDIUM Use children() helper for safe children access
2-5 Prefer Composition MEDIUM Prefer composition and context over prop drilling
2-8 Style Prop Conventions MEDIUM Use object syntax with kebab-case properties for style

3. Control Flow (5 rules)

# Rule Priority Description
3-1 Use Show for Conditionals HIGH Use <Show> instead of ternary operators
3-2 Use For for Lists HIGH Use <For> for referentially-keyed list rendering
3-3 Use Index for Primitives MEDIUM Use <Index> when array index matters more than identity
3-4 Use Switch/Match MEDIUM Use <Switch>/<Match> for multiple conditions
3-5 Provide Fallbacks LOW Always provide fallback props for loading states

4. State Management (5 rules)

# Rule Priority Description
4-1 Signals vs Stores HIGH Use signals for primitives, stores for nested objects
4-2 Use Store Path Syntax HIGH Use path syntax for granular, efficient store updates
4-3 Use produce for Mutations MEDIUM Use produce for complex mutable-style store updates
4-4 Use reconcile for Server Data MEDIUM Use reconcile when integrating server/external data
4-5 Use Context for Global State MEDIUM Use Context API for cross-component shared state

5. Refs & DOM (6 rules)

# Rule Priority Description
5-1 Use Refs Correctly HIGH Use callback refs for conditional elements
5-2 Access DOM in onMount HIGH Access DOM elements in onMount, not during render
5-3 Cleanup with onCleanup HIGH Always clean up subscriptions and timers
5-5 Avoid innerHTML HIGH Avoid innerHTML to prevent XSS — use JSX or textContent
5-4 Use Directives MEDIUM Use use: directives for reusable element behaviors
5-6 Event Handler Patterns MEDIUM Use on:/oncapture: namespaces and array handler syntax correctly

6. Performance (5 rules)

# Rule Priority Description
6-1 Avoid Unnecessary Tracking HIGH Don’t access signals outside reactive contexts
6-2 Use Lazy Components MEDIUM Use lazy() for code splitting large components
6-3 Use Suspense MEDIUM Use <Suspense> for async loading boundaries
6-4 Optimize Store Access LOW Access only the store properties you need
6-5 Prefer classList LOW Use classList prop for conditional class toggling

7. Accessibility (3 rules)

# Rule Priority Description
7-1 Use Semantic HTML HIGH Use appropriate semantic HTML elements
7-2 Use ARIA Attributes MEDIUM Apply appropriate ARIA attributes for custom controls
7-3 Support Keyboard Navigation MEDIUM Ensure all interactive elements are keyboard accessible

8. Testing (6 rules)

# Rule Priority Description
8-1 Configure Vitest for Solid CRITICAL Configure Vitest with Solid-specific resolve conditions and plugin
8-2 Wrap Render in Arrow Functions CRITICAL Always use render(() => <C />) not render(<C />)
8-3 Test Primitives in a Root HIGH Wrap signal/effect/memo tests in createRoot or renderHook
8-4 Handle Async in Tests HIGH Use findBy queries and proper timer config for async behavior
8-5 Use Accessible Queries MEDIUM Prefer role and label queries over test IDs
8-6 Separate Logic from UI Tests MEDIUM Test primitives/hooks independently from component rendering

Task-Based Rule Selection

Writing New Components

Load these rules when creating new Solid.js components:

Rule Why
1-1 Ensure signals are called as functions
2-1 Prevent reactivity breakage
2-6 No early returns — use control flow in JSX
2-2 Handle default props correctly
2-3 Separate local and forwarded props
3-1 Proper conditional rendering
3-2 Efficient list rendering
5-3 Prevent memory leaks

Code Review

Focus on these rules during code review:

Priority Rules
CRITICAL 1-1, 2-1, 2-6
HIGH 1-2, 1-3, 2-7, 5-2, 5-3, 5-5

Performance Optimization

Load these rules when optimizing performance:

Rule Focus
1-2 Prevent unnecessary recomputation
1-6 Reduce update cycles
4-2 Granular store updates
6-1 Prevent unwanted subscriptions
6-2 Code splitting
6-4 Efficient store access

State Management

Load these rules when working with application state:

Rule Focus
4-1 Choose the right primitive
4-2 Efficient updates
4-3 Complex mutations
4-4 External data integration
4-5 Cross-component state

Accessibility Audit

Load these rules when auditing accessibility:

Rule Focus
7-1 Semantic structure
7-2 Screen reader support
7-3 Keyboard users

Writing Tests

Load these rules when writing or reviewing tests:

Rule Focus
8-1 Correct Vitest configuration
8-2 Reactive render scope
8-3 Reactive ownership for primitives
8-4 Async queries and timers
8-5 Accessible query selection
8-6 Test architecture

Common Mistakes to Catch

Mistake Rule Solution
Forgetting () on signal access 1-1 Always call signals: count()
Destructuring props 2-1 Access via props.name
Using ternaries for conditionals 3-1 Use <Show> component
.map() for lists 3-2 Use <For> component
Deriving values in effects 1-2 Use createMemo
Setting signals in effects 1-4 Use createMemo or external triggers
Accessing DOM during render 5-2 Use onMount
Forgetting cleanup 5-3 Use onCleanup
Early returns in components 2-6 Use <Show>, <Switch> in JSX instead
Using className or htmlFor 2-7 Use class and for (standard HTML)
style="color: red" or camelCase styles 2-8 Use style={{ color: "red" }} with kebab-case
Using innerHTML with user data 5-5 Use JSX or sanitize with DOMPurify
Spreading whole store 6-4 Access specific properties
String concatenation for class toggling 6-5 Use classList={{ active: isActive() }}
render(<Comp />) without arrow 8-2 Use render(() => <Comp />)
Effects in tests without owner 8-3 Wrap in createRoot or use renderHook
getBy for async content 8-4 Use findBy queries

Solid.js vs React Mental Model

When helping users familiar with React, keep these differences in mind:

React Solid.js
Components re-render on state change Components run once, signals update DOM directly
useState returns [value, setter] createSignal returns [getter, setter]
useMemo with deps array createMemo with automatic tracking
useEffect(fn, [deps]) createEffect(fn) (no deps array — automatic tracking)
Destructure props freely Never destructure props
Early returns (if (!x) return null) <Show> / <Switch> in JSX (components return once)
{condition && <Component />} <Show when={condition}><Component /></Show>
{items.map(item => ...)} <For each={items}>{item => ...}</For>
className class
htmlFor for
style={{ fontSize: 14 }} style={{ "font-size": "14px" }}
Context requires useContext hook Context works with useContext or direct access

Priority Levels

  • CRITICAL: Fix immediately. Causes bugs, broken reactivity, or runtime errors.
  • HIGH: Address in code reviews. Important for correctness and maintainability.
  • MEDIUM: Apply when relevant. Improves code quality and performance.
  • LOW: Consider during refactoring. Nice-to-have optimizations.

Key Solid.js Concepts

Fine-Grained Reactivity

Solid.js updates only the specific DOM elements that depend on changed data, not entire component trees. This is achieved through:

  • Signals: Reactive primitives that track dependencies
  • Effects: Side effects that automatically re-run when dependencies change
  • Memos: Cached derived values that only recompute when dependencies change

Components Render Once

Unlike React, Solid components are functions that run once during initial render. Reactivity happens at the signal level, not the component level. This is why:

  • Props must not be destructured (would capture static values)
  • Signals must be called as functions (to maintain reactive tracking)
  • Control flow uses special components (<Show>, <For>) instead of JS expressions

Stores for Complex State

For nested objects and arrays, Solid provides stores with:

  • Fine-grained updates via path syntax
  • Automatic proxy wrapping for nested reactivity
  • Utilities like produce and reconcile for common patterns

Tooling

For automated linting alongside these best practices, use eslint-plugin-solid. The plugin catches many of the same issues this skill covers (destructured props, early returns, React-specific props, innerHTML usage, style prop format, etc.) and provides auto-fixable rules.

Resources