Agent skill
solid-core-utilities
SolidJS utilities: Context API, props manipulation (mergeProps/splitProps), JSX attributes (class/classList, events, refs), lifecycle (onMount/onCleanup), reactive utilities (batch/untrack/startTransition), store utilities.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/solid-core-utilities
SKILL.md
SolidJS Utilities
Context API
Use createContext to create context. Provider component wraps children and passes value. useContext consumes context.
import { createContext, useContext, createSignal } from "solid-js";
// Create context
const ThemeContext = createContext();
// Provider with signal
function ThemeProvider(props) {
const [theme, setTheme] = createSignal("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{props.children}
</ThemeContext.Provider>
);
}
// Custom hook with error handling
function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
return ctx;
}
// Use in component
function Component() {
const { theme, setTheme } = useTheme();
return <div class={theme()}>Content</div>;
}
Props
- Props are reactive - access properties directly
- Use
mergePropsto combine props with defaults - Use
splitPropsto separate props - Props with
defaultorinitialprefix are common for one-time initialization; the linter allows this pattern without explicituntrack
import { mergeProps, splitProps } from "solid-js";
// Merge props
function Button(props) {
const merged = mergeProps({ type: "button" }, props);
return <button {...merged}>Click</button>;
}
// Split props
function Component(props) {
const [local, others] = splitProps(props, ["name", "age"]);
return <div {...others}>{local.name}</div>;
}
// Default/initial props (no tracking needed)
function Input(props) {
const [value, setValue] = createSignal(props.initialValue);
return <input value={value()} onInput={e => setValue(e.target.value)} />;
}
children Helper
Use children helper for complex child manipulation. Prevents duplicate DOM creation.
import { children } from "solid-js";
function Wrapper(props) {
// Resolve children once (prevents duplicate DOM)
const resolved = children(() => props.children);
// Reuse resolved children multiple times
return (
<div>
{resolved()}
<div>Duplicate: {resolved()}</div>
</div>
);
}
When to use:
- Accessing
props.childrenmultiple times - Accessing
props.childrenoutside tracking scope - Resolving/transforming children before use
Important: children helper evaluates immediately - use conditionally for conditional rendering:
// ❌ Always evaluates
const resolved = children(() => props.children);
<Show when={visible()}>{resolved()}</Show>
// ✅ Conditional evaluation
const resolved = children(() => visible() && props.children);
Array normalization:
const resolved = children(() => props.children);
const array = resolved.toArray(); // Normalize to array
JSX Attributes
class and classList
// Static class
<div class="active" />
// Dynamic class
<div class={isActive() ? "active" : undefined} />
// classList object
<div
classList={{
active: isActive(),
disabled: isDisabled(),
"large-text": size() === "large"
}}
/>
// Signal classList
const [classes, setClasses] = createSignal({});
<div classList={classes()} />
Event Handlers
// onClick, onInput, etc. (lowercase 'on')
<button onClick={handleClick}>Click</button>
<input onInput={e => console.log(e.target.value)} />
// Event delegation with on:eventName
<button on:click={handler} />
<div on:mousedown={handler} />
Other Attributes
// ref callback
let inputEl;
<input ref={inputEl} />
// style object (reactive)
<div style={{ color: color(), fontSize: "14px" }} />
// innerHTML (dangerous, use carefully)
<div innerHTML={htmlContent()} />
// use:* directives
<div use:someDirective={arg} />
Refs and Directives
- Use
refattribute to access DOM elements directly refcan be a variable or a callback function- In TypeScript, use
let myElement!: HTMLDivElement;for definitive assignment - Signal setters or callbacks can be used as refs for dynamic elements
- Forward refs by passing
props.refto child's element - Directives (
use:) attach reusable behaviors to DOM elements- Function signature:
(element: Element, accessor: () => any): void - Can have multiple directives on an element
- Can pass reactive data to accessor
- Function signature:
import { createSignal, Show } from "solid-js";
// Basic ref
let myInput!: HTMLInputElement;
<input ref={myInput} />;
// Callback ref
let myDiv;
<div ref={(el) => (myDiv = el)}>Content</div>;
// Signal as ref
const [show, setShow] = createSignal(false);
let element!: HTMLParagraphElement;
<Show when={show()}>
<p ref={element}>This is the ref element</p>
</Show>;
// Forwarding refs
function ChildComponent(props) {
return <input ref={props.ref} />;
}
function ParentComponent() {
let inputRef;
return <ChildComponent ref={inputRef} />;
}
// Custom directive
function myDirective(element: Element, accessor: () => any) {
createEffect(() => {
element.textContent = accessor();
});
}
<div use:myDirective={mySignal()}></div>;
Lifecycle
import { onMount, onCleanup, createSignal } from "solid-js";
function Component() {
const [data, setData] = createSignal(null);
// Runs once when component mounts
onMount(async () => {
const fetchedData = await fetchData();
setData(fetchedData);
});
// Runs when component unmounts or effect re-runs
onCleanup(() => {
// Cleanup subscriptions, timers, etc.
subscription.unsubscribe();
});
}
Reactive Utilities
batch
Batch multiple updates - effects run once at the end:
import { batch } from "solid-js";
const [up1, setUp1] = createSignal(1);
const [up2, setUp2] = createSignal(2);
const [up3, setUp3] = createSignal(3);
const down = createMemo(() => up1() + up2() + up3());
createEffect(() => console.log("Downstream:", down()));
// Batch multiple updates
batch(() => {
setUp1(10);
setUp2(10);
setUp3(10);
// All updates processed together, 'down' recomputes once
});
untrack
Access signal without tracking:
import { untrack } from "solid-js";
// Access signal without tracking
const value = untrack(() => props.staticValue);
// Props with default/initial prefix are auto-untracked
function MyComponent(props: { initialName: string }) {
const [name, setName] = createSignal(props.initialName); // ✅ OK
return <div>{name()}</div>;
}
startTransition
Mark non-urgent updates:
import { startTransition } from "solid-js";
const [filter, setFilter] = createSignal("");
const [search, setSearch] = createSignal("");
startTransition(() => {
// Non-urgent updates
setFilter(filterValue);
setSearch(searchValue);
});
from
Convert promises/observables to signals:
import { from } from "solid-js";
const data = from(fetch("/api/data").then(r => r.json()));
mapArray / indexArray
Transform arrays with fine-grained reactivity:
import { mapArray, indexArray } from "solid-js";
// Map by item identity
const mapped = mapArray(items, item => item.id);
// Map by index
const indexed = indexArray(items, item => item.id);
on
Execute effect when specific signal changes:
import { on } from "solid-js";
createEffect(on(count, (value) => {
console.log("Count changed to:", value);
}, { defer: true }));
catchError
Catch errors in reactive contexts:
import { catchError } from "solid-js";
const data = catchError(() => {
return riskyOperation();
}, (err) => {
console.error("Error:", err);
return defaultValue;
});
Store Utilities
produce
Mutate store data imperatively:
import { produce } from "solid-js/store";
setStore("user", produce(user => {
user.name = "New Name";
user.age = 31;
}));
reconcile
Merge new data efficiently (only updates changed fields):
import { reconcile } from "solid-js/store";
const newData = fetchNewData();
setStore("items", reconcile(newData)); // Only updates changed items
unwrap
Get plain object snapshot:
import { unwrap } from "solid-js/store";
const plainData = unwrap(store); // Plain JS object
// Use for serialization, API calls, etc.
createMutable
Create mutable store (use sparingly):
import { createMutable } from "solid-js/store";
const state = createMutable({ count: 0 });
// Direct mutation
state.count++;
// No setter needed, but less reactive control
Advanced Patterns
createResource
Manages asynchronous data fetching:
import { createResource, Suspense } from "solid-js";
async function fetchUser(id: string) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
function DataComponent(props: { userId: string }) {
const [data, { mutate, refetch }] = createResource(
() => props.userId,
fetchUser,
{
initialValue: null,
name: "user"
}
);
return (
<Suspense fallback={<div>Loading user...</div>}>
<div>{data()?.name}</div>
<button onClick={() => refetch()}>Refetch</button>
</Suspense>
);
}
Portals
Renders children into a different DOM node:
import { Portal } from "solid-js/web";
<Portal mount={document.body}>
<Modal>Content</Modal>
</Portal>
Lazy Components
import { lazy, Suspense } from "solid-js";
const LazyComponent = lazy(() => import("./Component"));
<Suspense fallback={<Loading />}>
<LazyComponent />
</Suspense>
Best Practices
- Create custom context hooks for type safety and error handling
- Use
mergePropsfor default props - Use
splitPropsto separate local and forwarded props - Use
batchwhen making multiple related updates - Use
untrackto read values without tracking - Use
producefor complex store mutations - Use
reconcilewhen merging fetched data - Use
unwrapwhen serializing or sending to API
Didn't find tool you were looking for?