TypeScript React Context — Complete Guide
In this tutorial, you will learn about TypeScript React Context. We cover key concepts, practical examples, and best practices to help you master this topic.
React Context with TypeScript provides type-safe global state management without prop drilling — typed context values, providers, and context consumers ensure that every component accessing shared state does so with full type safety.
What You'll Learn
- Typed createContext and useContext
- Custom provider components
- Context with useReducer
- Context composition patterns
Why It Matters
Without TypeScript, context values are any by default — you lose all autocompletion and safety. Typed context guarantees that every component consuming the context accesses valid properties with correct types.
Real-World Use
DodaTech's Durga Antivirus Pro dashboard uses typed React Context for the current user session, theme preferences, and scan state. The typed ScanContext ensures every component accessing scan status uses the correct action types and state shape.
Learning Path
flowchart LR A[React Events] --> B[React Context] B --> C[State Management] B --> D[You Are Here] C --> E[React Advanced] E --> F[Node Setup]
Basic Typed Context
import { createContext, useContext, ReactNode } from 'react';
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
function useTheme(): ThemeContextType {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
// Usage
function ThemedButton() {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}
>
Toggle Theme
</button>
);
}
Context with useReducer
interface ScanState {
status: 'idle' | 'scanning' | 'complete' | 'error';
threats: string[];
progress: number;
}
type ScanAction =
| { type: 'START' }
| { type: 'PROGRESS'; progress: number }
| { type: 'THREAT'; name: string }
| { type: 'COMPLETE' }
| { type: 'ERROR'; message: string };
function scanReducer(state: ScanState, action: ScanAction): ScanState {
switch (action.type) {
case 'START': return { status: 'scanning', threats: [], progress: 0 };
case 'PROGRESS': return { ...state, progress: action.progress };
case 'THREAT': return { ...state, threats: [...state.threats, action.name] };
case 'COMPLETE': return { ...state, status: 'complete', progress: 100 };
case 'ERROR': return { ...state, status: 'error' };
}
}
interface ScanContextType {
state: ScanState;
dispatch: React.Dispatch<ScanAction>;
}
const ScanContext = createContext<ScanContextType | undefined>(undefined);
function ScanProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(scanReducer, {
status: 'idle', threats: [], progress: 0,
});
return (
<ScanContext.Provider value={{ state, dispatch }}>
{children}
</ScanContext.Provider>
);
}
function useScan(): ScanContextType {
const context = useContext(ScanContext);
if (!context) throw new Error('useScan must be used within ScanProvider');
return context;
}
// Components
function ScanButton() {
const { dispatch } = useScan();
return <button onClick={() => dispatch({ type: 'START' })}>Start Scan</button>;
}
function ProgressBar() {
const { state } = useScan();
if (state.status === 'idle') return <p>Ready</p>;
return <progress value={state.progress} max={100} />;
}
Composing Multiple Contexts
function AppProviders({ children }: { children: ReactNode }) {
return (
<ThemeProvider>
<ScanProvider>
<AuthProvider>
{children}
</AuthProvider>
</ScanProvider>
</ThemeProvider>
);
}
// Custom hook that combines contexts
function useAppState() {
const theme = useTheme();
const scan = useScan();
const auth = useAuth();
return { theme, scan, auth };
}
Context Factory Patternory" >}} Pattern
function createCtx<T>(name: string) {
const ctx = createContext<T | undefined>(undefined);
function useCtx(): T {
const c = useContext(ctx);
if (!c) throw new Error(`use${name} must be inside ${name}Provider`);
return c;
}
return [ctx, useCtx] as const;
}
// Usage
interface UserContextType {
user: { id: string; name: string } | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
const [UserContext, useUser] = createCtx<UserContextType>('User');
// UserContext — the raw context object
// useUser — typed hook with error handling
Avoiding Unnecessary Re-renders
function SplitContexts() {
// Split reading and writing contexts to avoid re-renders
const ThemeValueContext = createContext<ThemeState>({ theme: 'light' });
const ThemeActionsContext = createContext<ThemeActions>({ toggleTheme: () => {} });
// Components that only read don't re-render when dispatch changes
// Components that only dispatch don't re-render when state changes
}
Common Mistakes
1. Not Handling Undefined Context
function useTheme() {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be used within ThemeProvider');
return context;
}
2. Default Value at createContext That Makes Sense
// Bad: default value is nonsense
createContext<ThemeContextType>({} as ThemeContextType);
// Good: undefined with runtime check
createContext<ThemeContextType | undefined>(undefined);
3. Putting Everything in One Context
Split contexts by concern — auth state, theme, scan status — to prevent unnecessary re-renders.
4. Not Memoizing Context Values
// Bad: new object every render
<MyContext.Provider value={{ user, login }}>
{children}
</MyContext.Provider>
// Good: memoized
const value = useMemo(() => ({ user, login }), [user, login]);
<MyContext.Provider value={value}>{children}</MyContext.Provider>
5. Forgetting to Export the Hook
Always export the typed consumer hook from your context module.
Practice Questions
Why should context value include
undefinedin the type? So the hook can throw a meaningful error when used outside the provider.How do you prevent unnecessary re-renders with context? Split into value/actions contexts, memoize the value, or use context selectors.
What is the context factory pattern? A function that creates both the context and a typed consumer hook, reducing boilerplate.
How do you combine multiple providers? Nest them, create an AppProviders wrapper, and optionally create a combined hook.
Challenge: Create a typed NotificationContext that manages a list of toast notifications with addNotification(type, message) and removeNotification(id). Provide type-safe hooks.
FAQ
Mini Project: Auth Context
interface AuthState { user: User | null; token: string | null; }
interface AuthActions {
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
type AuthContextType = AuthState & AuthActions;
const AuthContext = createContext<AuthContextType | undefined>(undefined);
function AuthProvider({ children }: { children: ReactNode }) {
const [state, setState] = useState<AuthState>({ user: null, token: null });
const login = async (email: string, password: string) => {
const res = await fetch('/api/login', { method: 'POST', body: JSON.stringify({ email, password }) });
const data = await res.json();
setState({ user: data.user, token: data.token });
};
const logout = () => setState({ user: null, token: null });
const value = useMemo(() => ({ ...state, login, logout }), [state]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
function useAuth(): AuthContextType {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
return ctx;
}
What's Next
Now explore state management libraries:
| Lesson | Description |
|---|---|
| {{< ref "/programming-languages/typescript/39-react-events" >}} | Review events |
| {{< ref "/programming-languages/typescript/41-react-state-management" >}} | Zustand, Redux Toolkit, Jotai |
| {{< ref "/programming-languages/typescript/42-react-advanced" >}} | forwardRef, HOCs, render props |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro