Calling useContext Outside of a Context Provider
A common React mistake is attempting to use useContext
when there’s no matching <Context.Provider>
higher up in the component tree, which leads to receiving the context’s default value (often undefined
) rather than the expected provided value.
The Problem
// ❌ No provider wrapping the component
const MyContext = createContext();
function Component() {
const value = useContext(MyContext); // value = undefined
return <div>{value}</div>; // Renders nothing or breaks
}
Why this happens:
createContext()
defines a default value (can beundefined
)- Without a provider, consumers get this default value
- No error is thrown, leading to silent failures
- Components may break when expecting certain values
Correct Solutions
1. Wrap with Provider (Basic)
const MyContext = createContext();
function App() {
return (
<MyContext.Provider value="Hello">
<Component /> {/* Now receives context value */}
</MyContext.Provider>
);
}
function Component() {
const value = useContext(MyContext); // ✅ Gets "Hello"
return <div>{value}</div>;
}
2. Provide Meaningful Default
// ✅ Default value prevents undefined
const MyContext = createContext({
theme: 'light',
toggleTheme: () => {}
});
function Component() {
const { theme } = useContext(MyContext); // ✅ Safe access
return <div className={theme}>Content</div>;
}
3. Custom Hook with Validation
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
// Usage - throws error if no provider
function ThemedComponent() {
const theme = useTheme(); // ✅ Safe with error boundary
}
4. TypeScript Solution
interface ContextType {
user: User | null;
login: (user: User) => void;
}
// ✅ Explicit type with no default (will be undefined without provider)
const AuthContext = createContext<ContextType | undefined>(undefined);
function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
Common Mistakes
- Assuming provider exists:
const value = useContext(MyContext);
value.doSomething(); // ❌ TypeError if undefined
- Nested providers confusion:
<MyContext.Provider value="A">
<MyContext.Provider value="B">
<Component /> {/* Gets "B" */}
</MyContext.Provider>
<Component /> {/* Gets "A" */}
</MyContext.Provider>
- Dynamic provider values without memo:
<MyContext.Provider value={{ theme, toggleTheme }}>
{/* ❌ New object every render causes consumers to update */}
</MyContext.Provider>
Best Practices
- Always provide meaningful defaults in
createContext
- Create custom hooks for context consumption with validation
- Memoize provider values to prevent unnecessary re-renders
const value = useMemo(() => ({ theme, toggleTheme }), [theme]);
return <MyContext.Provider value={value}>{children}</MyContext.Provider>;
- Document provider requirements in component docs
- Use TypeScript to catch missing providers at compile time
Advanced Patterns
1. Multiple Providers
function App() {
return (
<AuthProvider>
<ThemeProvider>
<UserPreferencesProvider>
<MainApp />
</UserPreferencesProvider>
</ThemeProvider>
</AuthProvider>
);
}
2. Context Selectors
function useUser() {
const { user } = useContext(AuthContext);
return user; // Only re-renders when user changes
}
3. Provider Composition
function CombinedProvider({ children }) {
const auth = useAuthState();
const theme = useThemeState();
return (
<AuthContext.Provider value={auth}>
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
</AuthContext.Provider>
);
}