Suggest an editImprove this articleRefine the answer for “How do you type context?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)The safest approach is to type the context as `T | undefined` and expose access to it through a custom hook that throws a clear error if there is no provider: `createContext<T | undefined>(undefined)` + `useXxx()`. **Key point:** only give the context a valid default when it is genuinely correct at runtime, otherwise it hides a bug.Shown above the full answer for quick recall.Answer (EN)Image## 1) The basic, safe pattern (with `undefined` + a custom hook) When the context is required inside a subtree and **must not** be `null`/`undefined` when it is used. ```javascript import React, { createContext, useContext, ReactNode } from "react"; type Theme = "light" | "dark"; type ThemeCtx = { theme: Theme; setTheme: (t: Theme) => void; }; // 1) The context allows undefined before the Provider const ThemeContext = createContext<ThemeCtx | undefined>(undefined); // 2) A safe hook: throws a clear error if there is no provider export function useTheme() { const ctx = useContext(ThemeContext); if (!ctx) throw new Error("useTheme must be used within <ThemeProvider>"); return ctx; } // 3) A provider with typed props export function ThemeProvider({ value, children, }: { value: ThemeCtx; children: ReactNode; }) { return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>; } ``` **Why this is better:** - there is no need to write `!` or cast with `as ThemeCtx`; - you get an early, clear error if you forgot to wrap things in the provider. --- ## 2) Context with a valid default (if one genuinely exists) If the value **makes sense** outside the provider (for example, read-only settings). ```javascript type Config = { apiBase: string; locale: string }; const defaultConfig: Config = { apiBase: "/api", locale: "en" }; const ConfigContext = createContext<Config>(defaultConfig); export const useConfig = () => useContext(ConfigContext); ``` **Important:** only give it a default **when it is genuinely correct** at runtime, otherwise you will hide a bug. --- ## 3) Context that can be `null` by design If "no value" is a **normal** case for the domain. ```javascript type User = { id: string; name: string } | null; const CurrentUserContext = createContext<User>(null); export const useCurrentUser = () => useContext(CurrentUserContext); ``` --- ## 4) A "state + dispatch" pair (handy for reducers) ```javascript type Action = | { type: "set"; payload: number } | { type: "reset" }; type CounterState = { count: number }; type CounterCtx = { state: CounterState; dispatch: React.Dispatch<Action>; }; const CounterContext = createContext<CounterCtx | undefined>(undefined); export function useCounter() { const ctx = useContext(CounterContext); if (!ctx) throw new Error("useCounter must be used within <CounterProvider>"); return ctx; } ``` --- ## 5) A tuple/multi-value (sometimes more convenient) ```javascript type Filters = { q: string; tags: string[] }; type SetFilters = (next: Filters) => void; const FiltersContext = createContext<[Filters, SetFilters] | undefined>(undefined); export function useFilters() { const ctx = useContext(FiltersContext); if (!ctx) throw new Error("useFilters must be used within <FiltersProvider>"); return ctx; // returns [filters, setFilters] } ``` --- ## 6) Typing the provider (the `value` and `children` props) ```javascript type AuthCtx = { token: string | null; login: (t: string) => void }; const AuthContext = createContext<AuthCtx | undefined>(undefined); type AuthProviderProps = { value: AuthCtx; // strictly type value children: ReactNode; }; export function AuthProvider({ value, children }: AuthProviderProps) { return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; } ``` --- ## 7) Avoid anti-patterns - `createContext<Ctx>(null as unknown as Ctx)` or `null!` - it hides errors. - Giving a "fake" default when the app genuinely **cannot** work without the provider. - Storing frequently changing primitives in context without a reason (it causes unnecessary re-renders). --- ## 8) Nuances and tips - For large values, memoize `value` in the provider: ```javascript const value = useMemo(() => ({ theme, setTheme }), [theme]); ``` - For SSR/tests, it is convenient to have a **valid default** (pattern #2), if it is genuinely safe. - If the context is used across different packages, export **both the context and the hook**, but hand consumers the hook specifically - that way you enforce safe usage. --- ### A short cheat sheet - **Provider is required** -> `createContext<T | undefined>(undefined)` + `useXxx()` that throws. - **A meaningful default exists** -> `createContext<T>(defaultValue)`. - **Absence is allowed** -> `createContext<T | null>(null)`. - **Reducer style** -> `createContext<{ state: S; dispatch: Dispatch<A> } | undefined>(undefined)`.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.