| name | solid-core-utilities |
| description | SolidJS utilities: Context API, props manipulation (mergeProps/splitProps), JSX attributes (class/classList, events, refs), lifecycle (onMount/onCleanup), reactive utilities (batch/untrack/startTransition), store utilities. |
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";
const ThemeContext = createContext();
function ThemeProvider(props) {
const [theme, setTheme] = createSignal("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{props.children}
</ThemeContext.Provider>
);
}
function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
return ctx;
}
function Component() {
const { theme, setTheme } = useTheme();
return <div class={theme()}>Content</div>;
}
Props
- Props are reactive - access properties directly
- Use
mergeProps to combine props with defaults
- Use
splitProps to separate props
- Props with
default or initial prefix are common for one-time initialization; the linter allows this pattern without explicit untrack
import { mergeProps, splitProps } from "solid-js";
function Button(props) {
const merged = mergeProps({ type: "button" }, props);
return <button {...merged}>Click</button>;
}
function Component(props) {
const [local, others] = splitProps(props, ["name", "age"]);
return <div {...others}>{local.name}</div>;
}
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) {
const resolved = children(() => props.children);
return (
<div>
{resolved()}
<div>Duplicate: {resolved()}</div>
</div>
);
}
When to use:
- Accessing
props.children multiple times
- Accessing
props.children outside tracking scope
- Resolving/transforming children before use
Important: children helper evaluates immediately - use conditionally for conditional rendering:
const resolved = children(() => props.children);
<Show when={visible()}>{resolved()}</Show>
const resolved = children(() => visible() && props.children);
Array normalization:
const resolved = children(() => props.children);
const array = resolved.toArray();
JSX Attributes
class and classList
<div class="active" />
<div class={isActive() ? "active" : undefined} />
<div
classList={{
active: isActive(),
disabled: isDisabled(),
"large-text": size() === "large"
}}
/>
const [classes, setClasses] = createSignal({});
<div classList={classes()} />
Event Handlers
<button onClick={handleClick}>Click</button>
<input onInput={e => console.log(e.target.value)} />
<button on:click={handler} />
<div on:mousedown={handler} />
Other Attributes
let inputEl;
<input ref={inputEl} />
<div style={{ color: color(), fontSize: "14px" }} />
<div innerHTML={htmlContent()} />
<div use:someDirective={arg} />
Refs and Directives
- Use
ref attribute to access DOM elements directly
ref can 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.ref to 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
import { createSignal, Show } from "solid-js";
let myInput!: HTMLInputElement;
<input ref={myInput} />;
let myDiv;
<div ref={(el) => (myDiv = el)}>Content</div>;
const [show, setShow] = createSignal(false);
let element!: HTMLParagraphElement;
<Show when={show()}>
<p ref={element}>This is the ref element</p>
</Show>;
function ChildComponent(props) {
return <input ref={props.ref} />;
}
function ParentComponent() {
let inputRef;
return <ChildComponent ref={inputRef} />;
}
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);
onMount(async () => {
const fetchedData = await fetchData();
setData(fetchedData);
});
onCleanup(() => {
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(() => {
setUp1(10);
setUp2(10);
setUp3(10);
});
untrack
Access signal without tracking:
import { untrack } from "solid-js";
const value = untrack(() => props.staticValue);
function MyComponent(props: { initialName: string }) {
const [name, setName] = createSignal(props.initialName);
return <div>{name()}</div>;
}
startTransition
Mark non-urgent updates:
import { startTransition } from "solid-js";
const [filter, setFilter] = createSignal("");
const [search, setSearch] = createSignal("");
startTransition(() => {
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";
const mapped = mapArray(items, item => item.id);
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));
unwrap
Get plain object snapshot:
import { unwrap } from "solid-js/store";
const plainData = unwrap(store);
createMutable
Create mutable store (use sparingly):
import { createMutable } from "solid-js/store";
const state = createMutable({ count: 0 });
state.count++;
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
mergeProps for default props
- Use
splitProps to separate local and forwarded props
- Use
batch when making multiple related updates
- Use
untrack to read values without tracking
- Use
produce for complex store mutations
- Use
reconcile when merging fetched data
- Use
unwrap when serializing or sending to API