| name | solid-errors-react-contamination |
| description | Use when reviewing SolidJS code for React anti-patterns or converting React components to SolidJS. Prevents destructured props, useState/useEffect habits, Array.map rendering, and all other React patterns that silently break SolidJS reactivity. Covers all 12+ contamination patterns including props destructuring, signal misuse, effect cleanup, list rendering, conditional rendering, refs, children, and navigation. Keywords: React to SolidJS, destructure props, createSignal, createEffect, For component, Show component, anti-pattern, migration.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires SolidJS 1.x/2.x with TypeScript. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
solid-errors-react-contamination
Critical Warnings
NEVER destructure props in SolidJS components. Destructuring severs the reactive proxy connection. The component function runs ONCE — destructured values are frozen snapshots that NEVER update. This is the #1 source of broken SolidJS code generated by AI assistants trained on React.
NEVER assume components re-render. SolidJS component functions execute exactly ONCE to set up the reactive graph. Code in the component body that depends on state changes (derived values, conditionals, logging) MUST be wrapped in reactive primitives (createMemo, createEffect) or inline JSX expressions.
NEVER use dependency arrays. SolidJS tracks dependencies automatically. There is no [deps] argument to createEffect or createMemo. Adding one is a syntax error or passes it as the initial value parameter.
NEVER return cleanup functions from effects. SolidJS uses onCleanup() as a separate call inside the effect, NOT a return value.
NEVER use Array.map() for list rendering. It recreates ALL DOM nodes on every array change. ALWAYS use <For> or <Index> components.
Quick Reference — All Anti-Patterns
| ID | Pattern | Severity | Detection |
|---|
| AP-001 | Destructuring props | CRITICAL | function X({ prop } or const { x } = props |
| AP-002 | Destructuring signal value | CRITICAL | const val = signal() outside JSX/effect |
| AP-003 | useState instead of createSignal | CRITICAL | useState import or usage |
| AP-004 | useEffect instead of createEffect | CRITICAL | useEffect import or [deps] array |
| AP-005 | useMemo instead of createMemo | HIGH | useMemo import or dependency array |
| AP-006 | Re-render assumption | CRITICAL | Derived values as plain variables in component body |
| AP-007 | Conditional signal access | HIGH | Signal read inside if in effect |
| AP-008 | Early return before signal access | HIGH | return before signal call in effect |
| AP-009 | Storing signal in variable | HIGH | const x = signal() in component body |
| AP-010 | Spreading props unsafely | MEDIUM | {...props} without splitProps |
| AP-011 | Array.map for lists | HIGH | .map() in JSX return |
| AP-012 | Ternary instead of Show | MEDIUM | {cond ? <A/> : <B/>} |
| AP-013 | switch/case in component body | HIGH | switch statement in component return |
| AP-014 | key prop on list items | LOW | key={...} prop in For callback |
| AP-015 | useRef instead of let ref | MEDIUM | useRef import or .current access |
| AP-016 | React.createElement assumption | LOW | Manual element creation calls |
| AP-017 | children as static value | HIGH | props.children without children() helper |
| AP-018 | useEffect cleanup return | CRITICAL | return () => cleanup in effect |
| AP-019 | useRouter / next/router | HIGH | useRouter import |
| AP-020 | useEffect for data fetching | HIGH | fetch inside useEffect/createEffect |
| AP-021 | element prop on Route | HIGH | element={<Component/>} on Route |
| AP-022 | getServerSideProps pattern | HIGH | Separate data-fetching exports |
| AP-023 | Form onSubmit with preventDefault | MEDIUM | e.preventDefault() in form handler |
Top 10 Priority Patterns (Detailed)
AP-001: Destructuring Props (CRITICAL)
function Greeting({ name }: { name: string }) {
return <h1>Hello {name}</h1>;
}
function Greeting(props: { name: string }) {
const { name } = props;
return <h1>Hello {name}</h1>;
}
function Greeting(props: { name: string }) {
return <h1>Hello {props.name}</h1>;
}
function Greeting(props: { name: string; class?: string }) {
const [local, rest] = splitProps(props, ["name"]);
return <h1 {...rest}>Hello {local.name}</h1>;
}
Why it breaks: Props are reactive getters on a proxy. Destructuring reads the value once and discards the proxy connection.
AP-002: Destructuring Signal Value (CRITICAL)
const [count, setCount] = createSignal(0);
const value = count();
return <div>{value}</div>;
return <div>{count()}</div>;
Why it breaks: Calling count() outside a tracking scope captures a static value. Inside JSX, the compiler wraps it in a reactive effect.
AP-003: useState vs createSignal (CRITICAL)
const [count, setCount] = useState(0);
return <div>{count}</div>;
const [count, setCount] = createSignal(0);
return <div>{count()}</div>;
Why it breaks: createSignal returns [getter, setter] where getter is a function. Forgetting () renders the function object, not the value.
AP-004: useEffect vs createEffect (CRITICAL)
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
createEffect(() => {
document.title = `Count: ${count()}`;
});
Why it breaks: SolidJS has NO dependency array concept. The second argument to createEffect is an initial value for the previous-value parameter, NOT a dependency list.
AP-005: useMemo vs createMemo (HIGH)
const double = useMemo(() => count * 2, [count]);
const double = createMemo(() => count() * 2);
Why it breaks: React's useMemo is NOT a reactive source. SolidJS's createMemo IS -- other computations can track it.
AP-006: Component Re-Render Assumption (CRITICAL)
function Counter() {
const [count, setCount] = createSignal(0);
const doubled = count() * 2;
console.log("render");
return <p>{doubled}</p>;
}
function Counter() {
const [count, setCount] = createSignal(0);
const doubled = createMemo(() => count() * 2);
return <p>{doubled()}</p>;
}
Why it breaks: The component function is a setup function. It runs once. Only reactive expressions (effects, memos, JSX bindings) re-execute.
AP-007: Conditional Signal Access (HIGH)
createEffect(() => {
if (isEnabled()) {
console.log(data());
}
});
createEffect(() => {
const enabled = isEnabled();
const currentData = data();
if (enabled) {
console.log(currentData);
}
});
Why it breaks: SolidJS tracks signals at read time. If a branch prevents reading a signal, that signal is not registered as a dependency for that execution.
AP-008: Early Return Before Signal Access (HIGH)
createEffect(() => {
if (loading()) return;
console.log(name());
});
createEffect(() => {
const isLoading = loading();
const currentName = name();
if (isLoading) return;
console.log(currentName);
});
AP-009: Storing Signal in Variable (HIGH)
function Timer() {
const [count, setCount] = createSignal(0);
const current = count();
setInterval(() => {
console.log(current);
}, 1000);
return <div>{current}</div>;
}
function Timer() {
const [count, setCount] = createSignal(0);
return <div>{count()}</div>;
}
AP-010: Spreading Props Unsafely (MEDIUM)
function Button(props: ButtonProps) {
return <button {...props}>{props.children}</button>;
}
function Button(props: ButtonProps) {
const [local, rest] = splitProps(props, ["children", "onClick"]);
return <button onClick={local.onClick} {...rest}>{local.children}</button>;
}
Decision Tree: Is This a React Anti-Pattern?
Code contains signal/props access?
├── Props destructured in function signature? --> AP-001 (CRITICAL)
├── Props destructured with const { } = props? --> AP-001 (CRITICAL)
├── Signal value stored in const outside JSX? --> AP-002/AP-009 (CRITICAL/HIGH)
├── useState / useEffect / useMemo import? --> AP-003/004/005 (CRITICAL)
├── Dependency array [deps] on effect/memo? --> AP-004/005 (CRITICAL/HIGH)
├── Component body has derived non-reactive values? --> AP-006 (CRITICAL)
├── Effect has early return before signal reads? --> AP-008 (HIGH)
├── Effect has conditional signal access? --> AP-007 (HIGH)
├── Return value from effect for cleanup? --> AP-018 (CRITICAL)
├── Array.map() in JSX? --> AP-011 (HIGH)
├── key={} prop on elements? --> AP-014 (LOW)
├── useRef or .current access? --> AP-015 (MEDIUM)
├── props.children used without children() helper? --> AP-017 (HIGH)
└── useRouter import? --> AP-019 (HIGH)
Reference Links
Official Sources