.claude/skills/solidjs/solid-syntax/solid-syntax-context/SKILL.md
Use when sharing state between components, creating providers, or implementing dependency injection in SolidJS. Prevents non-reactive context values and missing Provider errors common when porting React context patterns. Covers createContext, useContext, Provider pattern, reactive context with signals and stores, typed context, custom hooks, and nested overrides. Keywords: createContext, useContext, Provider, SolidJS context, dependency injection, shared state, typed context.
npx skillsauth add OpenAEC-Foundation/OpenAEC-Workspace-Composer solid-syntax-contextInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Function | Import | Purpose |
|----------|--------|---------|
| createContext<T>(default?) | solid-js | Create a context object with optional default value |
| useContext(ctx) | solid-js | Read the nearest Provider's value for a context |
| <Ctx.Provider value={v}> | (from context object) | Supply a value to all descendants |
// createContext
interface Context<T> {
id: symbol;
Provider: (props: { value: T; children: any }) => any;
defaultValue: T;
}
function createContext<T>(): Context<T | undefined>;
function createContext<T>(defaultValue: T): Context<T>;
// useContext
function useContext<T>(context: Context<T>): T;
ALWAYS provide a typed generic to createContext<T>() — omitting the generic results in Context<undefined> which provides no type safety for consumers.
ALWAYS wrap useContext in a custom hook with an error check — calling useContext directly gives no feedback when a Provider is missing.
NEVER destructure or access signals/stores when passing them to value — pass the signal accessor or store object directly to preserve reactivity.
NEVER rely on the default value as a functional substitute for a Provider — defaults exist only as a type-narrowing fallback, not as working application state.
ALWAYS define context objects in separate modules — this prevents Hot Module Replacement (HMR) issues where context identity is lost on file reload.
Need to share state between components?
├── Only parent → child (1-2 levels)?
│ └── USE PROPS — simplest, most explicit
├── Deep tree (3+ levels) but single subtree?
│ └── USE CONTEXT — avoids prop drilling, scoped to Provider
├── Multiple unrelated trees need same state?
│ ├── Read-only singleton?
│ │ └── USE MODULE-LEVEL SIGNAL — simplest global state
│ └── Needs encapsulation + methods?
│ └── USE CONTEXT + STORE — full reactive state container
└── Server-provided data needed in deep components?
└── USE CONTEXT — Provider wraps at route level
This is the recommended pattern for all context usage:
// counter-context.tsx — ALWAYS define in a separate module
import { createContext, useContext, type ParentProps } from "solid-js";
import { createSignal } from "solid-js";
interface CounterState {
count: () => number;
increment: () => void;
decrement: () => void;
}
const CounterContext = createContext<CounterState>();
// ALWAYS export a custom hook — NEVER export the raw context
export function useCounter(): CounterState {
const context = useContext(CounterContext);
if (!context) {
throw new Error("useCounter: must be used within a <CounterProvider>");
}
return context;
}
export function CounterProvider(props: ParentProps) {
const [count, setCount] = createSignal(0);
const value: CounterState = {
count,
increment: () => setCount((c) => c + 1),
decrement: () => setCount((c) => c - 1),
};
return (
<CounterContext.Provider value={value}>
{props.children}
</CounterContext.Provider>
);
}
// app.tsx — usage
import { CounterProvider, useCounter } from "./counter-context";
function Display() {
const { count } = useCounter();
return <p>Count: {count()}</p>;
}
function Controls() {
const { increment, decrement } = useCounter();
return (
<div>
<button onClick={decrement}>-</button>
<button onClick={increment}>+</button>
</div>
);
}
export default function App() {
return (
<CounterProvider>
<Display />
<Controls />
</CounterProvider>
);
}
import { createContext, useContext, type ParentProps } from "solid-js";
import { createStore, type SetStoreFunction } from "solid-js/store";
interface Todo {
id: number;
text: string;
done: boolean;
}
interface TodoState {
todos: Todo[];
}
type TodoContextValue = [
state: TodoState,
actions: {
addTodo: (text: string) => void;
toggleTodo: (id: number) => void;
},
];
const TodoContext = createContext<TodoContextValue>();
export function useTodos(): TodoContextValue {
const context = useContext(TodoContext);
if (!context) {
throw new Error("useTodos: must be used within a <TodoProvider>");
}
return context;
}
export function TodoProvider(props: ParentProps) {
const [state, setState] = createStore<TodoState>({ todos: [] });
const actions = {
addTodo(text: string) {
setState("todos", (prev) => [
...prev,
{ id: Date.now(), text, done: false },
]);
},
toggleTodo(id: number) {
setState("todos", (t) => t.id === id, "done", (d) => !d);
},
};
return (
<TodoContext.Provider value={[state, actions]}>
{props.children}
</TodoContext.Provider>
);
}
Inner Providers override outer Providers for the same context:
import { ThemeProvider, useTheme } from "./theme-context";
function ThemedBox() {
const { theme } = useTheme();
return <div style={{ background: theme().bg }}>{theme().name}</div>;
}
function App() {
return (
<ThemeProvider theme="light">
<ThemedBox /> {/* reads "light" */}
<ThemeProvider theme="dark">
<ThemedBox /> {/* reads "dark" — inner overrides outer */}
</ThemeProvider>
</ThemeProvider>
);
}
| Pattern | Reactive? | Why |
|---------|-----------|-----|
| value={{ count: count() }} | NO | Object is created once at Provider render; count() is read once |
| value={{ count }} (passing accessor) | YES | Accessor function is called in consuming component's render |
| value={store} (passing store proxy) | YES | Store proxy tracks property access in consumers |
| value={[state, actions]} (tuple) | YES | If state is a store or contains accessors |
Rule: ALWAYS pass signal accessors (functions) or store proxies as context values. NEVER call signals inside the value prop object literal — this reads the value once and breaks reactivity.
development
Use when integrating Vite with a backend framework, rendering Vite assets from server-side templates, or setting up dev/production HTML serving. Prevents incorrect manifest.json traversal and missing CSS chunk resolution in production. Covers build.manifest configuration, .vite/manifest.json structure, ManifestChunk properties, dev mode HTML setup, production rendering, CSS/JS chunk resolution, and modulepreload polyfill. Keywords: backend integration, manifest.json, ManifestChunk, Django, Laravel, Rails, modulepreload.
development
Use when encountering dev server startup failures, HMR issues, proxy errors, CORS blocks, or module not found errors during development. Prevents misconfiguring server.hmr behind reverse proxies and forgetting appType: 'custom' in middleware mode. Covers HMR full-reload debugging, proxy configuration, CORS setup, HTTPS certificates, server.fs.strict violations, port conflicts, WebSocket failures, file watcher issues, and middleware mode. Keywords: dev server, HMR, proxy, CORS, HTTPS, WebSocket, port conflict, server.fs.strict, middleware mode, file watcher.
development
Use when encountering pre-bundling errors, dependency resolution failures, stale cache issues, or slow development server startup. Prevents excluding CJS dependencies from pre-bundling (which breaks runtime module resolution) and misconfiguring optimizeDeps. Covers CJS/ESM conversion failures, missing dependency auto-discovery, optimizeDeps configuration, monorepo linked dependencies, cache invalidation, browser cache staleness, and large dependency tree performance. Keywords: pre-bundling, optimizeDeps, CJS, ESM, cache, dependency resolution, monorepo, node_modules/.vite.
development
Use when encountering Vite build failures, chunk size warnings, or version-specific build errors. Prevents the common mistake of using deprecated rollupOptions in v8 or misconfiguring build targets and minifiers. Covers Rolldown/Rollup bundling failures, CSS minification errors, sourcemap problems, library mode build failures, BundleError handling, and asset processing errors. Keywords: build error, Rolldown, chunk size, sourcemap, library mode, minify, BundleError, rollupOptions, build.target.