Context in Solid
Sharing reactive state across the component tree with createContext and useContext, without prop drilling.
2 min de lectura
Context in Solid solves the same problem it solves in React: passing data through many layers of components without threading it through every prop list. The API shape is nearly identical, but because Solid's reactivity is fine-grained, context values here stay reactive all the way down without causing any re-render cascade.
Creating and providing a 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>
);
}createContext() creates a context object with a default value (optionally passed as an argument). .Provider is a component that makes a value available to every descendant, however deeply nested, via props.children. Here, the value being provided is an object containing the signal getter and setter themselves — not the current value of the signal, which is an important distinction covered below.
Consuming context with useContext
function ThemeToggle() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme() === "light" ? "dark" : "light")}>
Current theme: {theme()}
</button>
);
}useContext(ThemeContext) retrieves whatever was passed to the nearest ancestor <ThemeContext.Provider>. Destructuring { theme, setTheme } here is safe — unlike destructuring props, which breaks reactivity because you're unwrapping a value early. This destructures the getter and setter functions themselves, which stay just as reactive after being pulled off the object as before, because calling theme() later still reads the live signal.
Provide the signal, not its current value
The mistake to avoid is passing the signal's resolved value into context instead of the reactive accessor:
// Broken — theme() is called once, and the raw string is provided
<ThemeContext.Provider value={{ theme: theme(), setTheme }}>
// Correct — the getter function itself is provided, staying reactive
<ThemeContext.Provider value={{ theme, setTheme }}>This is the context-specific version of the same rule from the props lesson: reactivity survives through function calls, not through values captured once and handed off.
Context works well alongside stores
For anything more structured than a single signal — user data, app-wide settings — it's common to pair context with createStore (covered in the next lesson) so that consumers can read and update specific nested fields without re-fetching the whole object:
const AppContext = createContext();
function AppProvider(props) {
const [state, setState] = createStore({ user: null, settings: {} });
return (
<AppContext.Provider value={[state, setState]}>
{props.children}
</AppContext.Provider>
);
}When to reach for context
Context is for values genuinely needed across many unrelated parts of the tree — theming, authentication state, localization. For state shared between just a parent and a couple of direct children, plain props are simpler and easier to trace; reserve context for the cases where prop drilling would otherwise mean threading a value through components that don't themselves use it.