Concepts

Context

Edit this page

Context provides a way to pass data through the component tree without having to pass props down manually at every level.


When to use context

When you have a large component tree that requires state to be shared, context can be used. Context can be employed to avoid prop drilling, which is the practice of passing props through intermediate elements without using them directly.

If you want to avoid passing some props through a few layers, when applicable, adjusting your component hierarchy may be an easier solution. Signals are often the simplest solution since they can be imported directly into the components that need them.

Context, however, is designed to share data that is global to an application or for information that is regularly accessed by multiple components in an application's component tree. This offers a way to access state across an application without passing props through intermediate layers or importing them directly into components.


Creating context

Context is created using the createContext function. This function has a Provider property that wraps the component tree you want to provide context to.


Providing context to children

To pass a value to the Provider, you use the value prop which can take in any value, including signals. Once a value is passed to the Provider, it is available to all components that are descendants of the Provider.

When passing a single value, it can be directly passed to the value prop:

/context/component.jsx
import { createContext, useContext } from "solid-js";
import { MyContext } from "./create";
const Provider = (props) => (
<MyContext.Provider value="new value">{props.children}</MyContext.Provider>
);

Consuming context

Once the values are available to all the components in the context's component tree, they can be accessed using the useContext utility. This utility takes in the context object and returns the value(s) passed to the Provider:

/context/component.jsx
import { createContext, useContext } from "solid-js";
import { MyContext } from "./create";
const Provider = (props) => (
<MyContext.Provider value="new value">
{props.children}
</MyContext.Provider>
);
const Child = () => {
const value = useContext(MyContext);
return (
<span>
{value}
</span>
);
};
export const App = () => (
<Provider>
<Child />
</Provider>
);

Customizing Context Utilities

When an application contains multiple context objects, it can be difficult to keep track of which context object is being used. To solve this issue, you can create a custom utilities to create a more readable way to access the context values.

For example, when wrapping a component tree, you may want to create a custom Provider component that can be used to wrap the component tree. This also provides you with the option of re-using the Provider component in other parts of your application, if needed.

import { createSignal, createContext, useContext } from "solid-js";
import { CounterContext } from "~/context/counter";
export function CounterProvider(props) {
let count = 0;
return (
<CounterContext.Provider value={count}>
{props.children}
</CounterContext.Provider>
);
}

Now if you had to access the Provider in different areas of your application, you can simply import the CounterProvider component and wrap the component tree:

import { CounterProvider } from "./counterProvider";
export function App() {
return (
<CounterProvider count={1}>
<h1>Welcome to Counter</h1>
<NestedComponents />
</CounterProvider>
);
}

Similarly, you can create a custom utility to access the context values. Instead of importing useContext and passing in the context object on each component that you're using it in, creating a customized utility can make it easier to access the values you need:

export function useCounter() {
return useContext(CounterContext);
}

The useCounter() utility in this example can now be imported into any component that needs to access the context values:

import { useCounter } from "./counter";
export function CounterProvider(props) {
const count = useCounter();
return (
<>
<div>{count()}</div>
</>
);
}

Updating Context Values

Signals offer a way to synchronize and manage data shared across your components using context. You can pass a signal directly to the value prop of the Provider component, and any changes to the signal will be reflected in all components that consume the context.

This offers a way to manage state across your components without having to pass props through intermediate elements.


Debugging with context

createContext takes in an optional default value and it is possible it can return undefined if not provided. When working with TypeScript, this can introduce type issues that make it difficult to determine why your component is not rendering as expected.

To solve this issue, a default value can be specified when creating a context object, or errors can be handled manually through the use of a custom useMyContext utility:

/context/counter-component.tsx
import { useContext } from "solid-js";
function useMyContext() {
const value = useContext(MyContext);
if (!value) {
throw new Error("Missing context Provider");
}
return value;
}
function Child() {
const value = useMyContext();
return <div>{value}</div>;
}

Common issues with createContext and useContext

If no default value is passed to createContext, it is possible for useContext to return undefined.

Because of this, if an initial value was not passed to createContext, the TS type signature of useContext will indicate that the value returned might be undefined (as mentioned above). This can be quite annoying when you want to use the context inside a component, and particularly when immediately destructuring the context. Additionally, if you use useContext and it returns undefined (which is often, but not always, the result of a bug), the error message thrown at runtime can be confusing.

The most common solution for it is to wrap all uses of useContext in a function that will explicitly throw a helpful error if the context is undefined. This also serves to narrow the type returned, so TS doesn't complain. As an example:

/context/counter-component.tsx
function useCounterContext() {
const context = useContext(CounterContext)
if (!context) {
throw new Error("can't find CounterContext")
}
return context
}
Report an issue with this page