Creating a React Context using createContext()
requires a default value. If you cannot provide a useful default value, you have to make the context nullable and then, when using the context value, verify that it is not null
.
const context = createContext<User | null>(null);
export function useUser() {
const value = useContext(context);
if (value === null) throw new Error("UserContext not provided");
return value;
}
Instead of repeating this boilerplate code every time, you can move this logic into a small utility function:
// Represents our empty value
const EMPTY = Symbol();
export function createRequiredContext<T>(): [Provider<T>, () => T] {
// Context, initialized with EMPTY
const context = createContext<T | typeof EMPTY>(EMPTY);
// Provider with EMPTY excluded (only T values are allowed)
const Provider = context.Provider as Provider<T>;
// Hook that throws an error if the value is EMPTY
const useStrictContext = () => {
const value = useContext(context);
if (value !== EMPTY) return value;
throw new Error("Missing context provider");
};
return [Provider, useStrictContext];
}
In our approach, we utilize a unique symbol, denoted as EMPTY
, to differentiate between a default value and an actual value. This strategy provides the flexibility to create nullable contexts when necessary. You can then call the createRequiredContext
function to create a new context:
const [UserProvider, useUser] = createRequiredContext<User>();