π¦ React Type-safe Context & Hooks β Share State with Confidence (2025 Guide)
π§² Introduction β Why Use Type-safe Context & Hooks?
React’s Context API and custom hooks allow you to share data like themes, auth, or settings across components. But without TypeScript, theyβre prone to:
- π΄ Misused values
- β Undefined contexts
- π§ Runtime errors from improper access
Type-safe context and hooks ensure:
- π§ Correct types for context values
- π Compile-time protection
- βοΈ Reusable and scalable global state logic
π― In this guide, youβll learn:
- How to create a type-safe React context
- Write custom hooks with generics
- Handle default values, nulls, and error states safely
- Best practices for shared logic
π§± 1. Creating a Type-safe React Context
β Define Types & Create Context
type ThemeContextType = {
theme: 'light' | 'dark';
toggleTheme: () => void;
};
const ThemeContext = React.createContext<ThemeContextType | undefined>(undefined);
π Always use undefined
and check before accessing
β Provider Component
const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => {
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
β Fully typed shared state and actions
π§ 2. Create a Custom Hook for Access
function useTheme(): ThemeContextType {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
π Centralizes the logic and removes boilerplate
β
Protects against missing providers
π 3. Usage in Components
const ThemeToggler = () => {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme}>
Current Theme: {theme}
</button>
);
};
β
Autocomplete and type checking for theme
and toggleTheme
π¦ 4. Generic Reusable Context Creator
function createTypedContext<T>() {
const context = React.createContext<T | undefined>(undefined);
const useCtx = () => {
const ctx = useContext(context);
if (!ctx) throw new Error('Context must be used within Provider');
return ctx;
};
return [context.Provider, useCtx] as const;
}
β Usage:
type Auth = { user: string; logout: () => void };
const [AuthProvider, useAuth] = createTypedContext<Auth>();
π Reduces repetition across multiple contexts
β
Enforces consistent typing for shared logic
π§ͺ 5. Typing Context with Async Data
type UserContextType = {
user: User | null;
loading: boolean;
};
const UserContext = createContext<UserContextType | undefined>(undefined);
π Use when fetching from APIs (auth, profile)
β
Avoid any
or nullable types without checks
π§© 6. Type-safe Custom Hooks with Generics
function useList<T>(initial: T[]): [T[], (item: T) => void] {
const [list, setList] = useState<T[]>(initial);
const add = (item: T) => setList((prev) => [...prev, item]);
return [list, add];
}
β Usage:
const [names, addName] = useList<string>([]);
β
Fully reusable with different types
π Promotes DRY and scalable logic
π Best Practices
Best Practice | Why It Helps |
---|---|
β
Default context as undefined | Forces useContext checks |
β Use custom hooks to access context | Hides error handling & improves DX |
β
Use as const with generics | Preserves tuple shape and immutability |
β
Avoid any in context | Use interfaces or union types instead |
β Split provider and hook logic | Better testing and composition |
π Summary β Recap & Next Steps
Using TypeScript with React context and hooks improves safety, reusability, and clarity. You get complete control over data shape, access patterns, and prevent misuse at compile time.
π Key Takeaways:
- Use
<T | undefined>
for context default values - Wrap
useContext
with a custom hook for type safety - Use generics for reusable state hooks
- Validate context access with runtime guards
- Keep types, context, and hooks modular
βοΈ Real-World Relevance:
Used in design systems (Radix UI, MUI), enterprise apps, and large codebases to manage global themes, auth, language, and UI preferences.
β FAQ Section
β Why should I set context default as undefined
?
β
So your custom hook can throw an error when the context is accessed outside the provider.
β Whatβs the difference between createContext<T | null>
and T | undefined
?
β
undefined
is more idiomatic for missing context. null
is fine but less explicit in TS checks.
β Can I create multiple typed contexts?
β
Yes. Use a context factory like createTypedContext<T>()
to reuse logic for any type.
β Should I export context or just the hook?
β
Prefer exporting only the custom hook. Keep Provider
internal to the module for better encapsulation.
β Can I use context with async logic like login/logout?
β
Yes. Use useEffect
inside the provider and define a loading
state along with your context value.
Share Now :