| name | solid-syntax-components | ||||
|---|---|---|---|---|---|
| description | Use when creating components, handling props, working with children, refs, directives, or events in SolidJS. Prevents props destructuring and children access patterns from React that break SolidJS reactivity. Covers Component/ParentComponent/VoidComponent/FlowComponent types, splitProps, mergeProps, children() helper, ref patterns, use: directives, and event delegation. Keywords: splitProps, mergeProps, children, Component, ParentComponent, VoidComponent, FlowComponent, ref, use directive, SolidJS props, event handling, pass props, access children, click handler, component types. | ||||
| license | MIT | ||||
| compatibility | Designed for Claude Code. Requires SolidJS 1.x/2.x with TypeScript. | ||||
| metadata |
|
| Type | Children | Use When |
|---|---|---|
Component<P> |
Optional (untyped) | General-purpose component |
ParentComponent<P> |
Required (children: JSX.Element) |
Layout wrappers, containers |
VoidComponent<P> |
Forbidden | Icons, inputs, leaf nodes |
FlowComponent<P, C> |
Typed (children: C) |
Control flow, render props |
| Need | Tool | Import |
|---|---|---|
| Access prop values | props.x directly |
-- |
| Derived values outside JSX | () => props.x |
-- |
| Separate prop groups | splitProps(props, [...keys]) |
solid-js |
| Default prop values | mergeProps(defaults, props) |
solid-js |
| Spread remaining props | splitProps + {...rest} |
solid-js |
| Need | Tool | Import |
|---|---|---|
| Resolve and cache children | children(() => props.children) |
solid-js |
| Iterate resolved children | resolved.toArray() |
solid-js |
| Pattern | Syntax | When |
|---|---|---|
| Variable assignment | let ref!: HTMLElement |
Standard ref |
| Callback | ref={(el) => { ... }} |
Side effects on creation |
| Signal ref | const [ref, setRef] = createSignal<HTMLElement>() |
Conditional elements |
| Forwarding | Pass ref as regular prop |
Parent needs child DOM access |
| System | Syntax | Listener Location | Use When |
|---|---|---|---|
| Delegated | onClick={handler} |
document (shared) |
Common UI events (23 events) |
| Native | on:scroll={handler} |
Element (direct) | Custom events, stopPropagation needed |
NEVER destructure props in function parameters or body -- this severs reactivity. Props are reactive getters on a proxy object. ALWAYS access as props.x.
NEVER access props.children multiple times without the children() helper -- each access can re-create child elements. ALWAYS resolve with children(() => props.children).
NEVER use useRef or forwardRef -- these are React APIs. ALWAYS use let ref!: HTMLElement for refs and pass ref as a regular prop for forwarding.
NEVER use event.stopPropagation() with delegated events expecting it to prevent other delegated handlers -- delegated events share a single document listener. ALWAYS use on: prefix when propagation control is needed.
NEVER call a handler signal directly in an event attribute (onClick={handler()}) -- this evaluates once. ALWAYS wrap in an arrow function: onClick={() => handler()}.
Creating a new component?
|
+-- Does it accept children?
| |
| +-- YES: Are children a specific type (render prop, typed slot)?
| | |
| | +-- YES --> FlowComponent<Props, ChildType>
| | +-- NO --> ParentComponent<Props>
| |
| +-- NO: Will it NEVER have children?
| |
| +-- YES --> VoidComponent<Props>
| +-- MAYBE --> Component<Props>
ALWAYS import types from solid-js:
import type { Component, ParentComponent, VoidComponent, FlowComponent } from "solid-js";// WRONG -- breaks reactivity (frozen at initial value):
function Greeting({ name }: { name: string }) {
return <h1>Hello {name}</h1>;
}
// WRONG -- same problem:
function Greeting(props: { name: string }) {
const { name } = props;
return <h1>Hello {name}</h1>;
}
// CORRECT -- reactive access:
const Greeting: Component<{ name: string }> = (props) => {
return <h1>Hello {props.name}</h1>;
};
// CORRECT -- derived accessor for use outside JSX:
const Greeting: Component<{ name: string }> = (props) => {
const upper = () => props.name.toUpperCase();
return <h1>Hello {upper()}</h1>;
};import { splitProps } from "solid-js";
const Button: ParentComponent<ButtonProps> = (props) => {
const [local, styleProps, rest] = splitProps(
props,
["onClick", "disabled"], // Group 1: behavior
["variant", "class"] // Group 2: styling
); // rest: everything else
return (
<button
onClick={local.onClick}
disabled={local.disabled}
class={`btn-${styleProps.variant} ${styleProps.class}`}
{...rest}
>
{props.children}
</button>
);
};import { mergeProps } from "solid-js";
const Button: ParentComponent<ButtonProps> = (props) => {
const merged = mergeProps(
{ variant: "primary", size: "md", disabled: false },
props
);
// Later sources override earlier ones -- props overrides defaults
return <button class={`btn-${merged.variant}`} disabled={merged.disabled}>{props.children}</button>;
};// WRONG -- accessing props.children multiple times re-creates elements:
const Bad: ParentComponent = (props) => {
createEffect(() => { console.log(props.children); }); // Re-creates!
return <div>{props.children}</div>; // Creates again!
};
// CORRECT -- resolve once with children() helper:
import { children } from "solid-js";
const Good: ParentComponent = (props) => {
const resolved = children(() => props.children);
createEffect(() => { console.log(resolved()); }); // Stable reference
return <div>{resolved()}</div>;
};const List: ParentComponent = (props) => {
const resolved = children(() => props.children);
return (
<ul>
<For each={resolved.toArray()}>
{(child) => <li>{child}</li>}
</For>
</ul>
);
};// WRONG (React pattern):
const ref = useRef<HTMLCanvasElement>(null);
useEffect(() => { ref.current?.getContext("2d"); }, []);
return <canvas ref={ref} />;
// CORRECT (SolidJS):
let canvasRef!: HTMLCanvasElement;
onMount(() => { canvasRef.getContext("2d"); });
return <canvas ref={canvasRef} />;| Aspect | React useRef |
SolidJS ref |
|---|---|---|
| Declaration | const ref = useRef(null) |
let ref!: HTMLElement |
| Access | ref.current |
ref (direct) |
| Timing | After mount (useEffect) |
Assigned during render, use in onMount |
| Hook required | Yes (useRef) |
No -- plain variable |
| Forwarding | forwardRef() HOC |
Pass ref as regular prop |
| Directives | No built-in system | use: prefix for reusable behaviors |
// Parent:
function Parent() {
let childRef!: HTMLCanvasElement;
onMount(() => { childRef.getContext("2d"); });
return <ChildCanvas ref={childRef} />;
}
// Child -- ref is ALWAYS received as a callback, regardless of parent declaration:
const ChildCanvas: Component<{ ref: HTMLCanvasElement | ((el: HTMLCanvasElement) => void) }> = (props) => {
return <canvas ref={props.ref} />;
};function directiveName(element: Element, accessor: () => any): void;import { onCleanup } from "solid-js";
function clickOutside(element: Element, accessor: () => () => void): void {
const onClick = (e: Event) => {
if (!element.contains(e.target as Node)) accessor()();
};
document.addEventListener("click", onClick);
onCleanup(() => document.removeEventListener("click", onClick));
}
// TypeScript declaration (required to avoid errors):
declare module "solid-js" {
namespace JSX {
interface Directives {
clickOutside: () => void;
}
}
}
// Usage:
<div use:clickOutside={() => setOpen(false)}>Dropdown content</div>beforeinput, click, dblclick, contextmenu, focusin, focusout, input, keydown, keyup, mousedown, mousemove, mouseout, mouseover, mouseup, pointerdown, pointermove, pointerout, pointerover, pointerup, touchend, touchmove, touchstart
For ALL other events, ALWAYS use the on: prefix.
const handler = (data: string, event: MouseEvent) => {
console.log("Data:", data, "Target:", event.target);
};
// Avoids creating a new closure -- data is first arg, event is second:
<button onClick={[handler, "hello"]}>Click</button>- stopPropagation: Delegated events share a document listener --
stopPropagation()does NOT prevent other delegated handlers. Useon:clickwhen propagation control is needed. - Portals: Events propagate through the component tree, not the DOM tree.
- references/methods.md -- Component types, splitProps, mergeProps, children(), ref patterns, directives, event types
- references/examples.md -- Complete component patterns, props handling, children resolution, ref forwarding, directives, events
- references/anti-patterns.md -- Destructuring props, useRef, forwardRef, React children patterns, event handler mistakes