A better way to create React Context providers
Oct 20, 2025
If you use React Context, you'll be familiar with the boilerplate involved. It typically looks something like this:
tsx
import React, { createContext, useContext, useState } from "react";
interface SearchContextValue {
query: string;
setQuery: (query: string) => void;
}
const SearchContext = createContext<SearchContextValue | undefined>(undefined);
export const SearchProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [query, setQuery] = useState<SearchContextValue["query"]>("");
const ctx: SearchContextValue = {
query,
setQuery,
};
return (
<SearchContext.Provider value={ctx}>{children}</SearchContext.Provider>
);
};
export function useSearch() {
const context = useContext(SearchContext);
if (!context) {
throw new Error("useSearch must be used within a SearchProvider");
}
return context;
}
This can be abstracted away into a simple utility. I can't remember where I first came across it, but have found myself copying into virtually every React project I work on.
tsx
"use client";
import {
createContext as _createContext,
useContext as _useContext,
} from "react";
export interface CreateContextOptions {
strict?: boolean;
errorMessage?: string;
name?: string;
}
type CreateContextReturn<T> = [React.Provider<T>, () => T, React.Context<T>];
export function createContext<ContextType>(options: CreateContextOptions = {}) {
const {
strict = true,
errorMessage = "useContext: `context` is undefined. Seems you forgot to wrap component within the Provider",
name,
} = options;
const Context = _createContext<ContextType | undefined>(undefined);
Context.displayName = name;
function useContext() {
const context = _useContext(Context);
if (!context && strict) {
throw new Error(errorMessage);
}
return context;
}
return [
Context.Provider,
useContext,
Context,
] as CreateContextReturn<ContextType>;
}
It can be used like this. As a bonus, if you forget to wrap your component tree properly, it will throw an error.
tsx
interface SearchContextValue {
query: string;
setQuery: (query: string) => void;
}
export const [SearchContext, useSearch] = createContext<
SearchContextValue | undefined
>(undefined);
export const SearchProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [query, setQuery] = useState<SearchContextValue["query"]>("");
const ctx: SearchContextValue = {
query,
setQuery,
};
return <SearchContext value={ctx}>{children}</SearchContext>;
};