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>();