Introduction
The Context API in React is a powerful feature that allows developers to share state and logic across multiple components without prop drilling. Combined with the useContext hook, it provides a clean, efficient way to access global data in functional components.
The Context API is especially useful for scenarios like:
- Theme switching (dark/light mode)
- Authentication state management
- Sharing user preferences or settings
In this article, we will cover practical examples of using useContext for real-world scenarios, demonstrating how it simplifies state management in React applications.
What is useContext?
The useContext hook allows functional components to consume a context created using React.createContext. It eliminates the need to pass props through intermediate components.
Basic Syntax
import React, { useContext } from "react";
import { MyContext } from "./MyContext";
function MyComponent() {
const value = useContext(MyContext);
return <div>{value}</div>;
}
Creating and Providing Context
To use useContext, you first need to create a context and provide a value using a Provider.
Example
import React, { createContext } from "react";
export const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const theme = "light";
return (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
);
}
Explanation
ThemeContextis created usingcreateContext().ThemeProviderwraps the component tree and provides athemevalue.- Components consuming this context can access
themeusinguseContext.
Example 1: Theme Switcher (Dark/Light Mode)
Step 1: Create Context
import React, { createContext, useState } from "react";
export const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const toggleTheme = () => {
setTheme(theme === "light" ? "dark" : "light");
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
Step 2: Consume Context in a Component
import React, { useContext } from "react";
import { ThemeContext } from "./ThemeContext";
function ThemeSwitcher() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div style={{ background: theme === "light" ? "#fff" : "#333", color: theme === "light" ? "#000" : "#fff", padding: "20px" }}>
<p>Current theme: {theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}
Step 3: Wrap App with Provider
import React from "react";
import ReactDOM from "react-dom";
import { ThemeProvider } from "./ThemeContext";
import ThemeSwitcher from "./ThemeSwitcher";
ReactDOM.render(
<ThemeProvider>
<ThemeSwitcher />
</ThemeProvider>,
document.getElementById("root")
);
Explanation
ThemeContextprovides the current theme and a toggle function.ThemeSwitcherconsumes the context usinguseContext.- The background and text color dynamically change based on the theme.
Example 2: Authentication State Across the App
Managing authentication state globally is one of the most common use cases for useContext.
Step 1: Create Auth Context
import React, { createContext, useState } from "react";
export const AuthContext = createContext();
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = (username) => setUser({ name: username });
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
Step 2: Consume Auth Context in Components
Login Component
import React, { useContext, useState } from "react";
import { AuthContext } from "./AuthContext";
function Login() {
const { login } = useContext(AuthContext);
const [username, setUsername] = useState("");
const handleLogin = () => login(username);
return (
<div>
<input type="text" value={username} onChange={(e) => setUsername(e.target.value)} placeholder="Username" />
<button onClick={handleLogin}>Login</button>
</div>
);
}
Dashboard Component
import React, { useContext } from "react";
import { AuthContext } from "./AuthContext";
function Dashboard() {
const { user, logout } = useContext(AuthContext);
if (!user) return <p>Please log in</p>;
return (
<div>
<p>Welcome, {user.name}</p>
<button onClick={logout}>Logout</button>
</div>
);
}
Step 3: Wrap App with AuthProvider
import React from "react";
import ReactDOM from "react-dom";
import { AuthProvider } from "./AuthContext";
import Login from "./Login";
import Dashboard from "./Dashboard";
function App() {
return (
<AuthProvider>
<Login />
<Dashboard />
</AuthProvider>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
Explanation
AuthContextprovidesuser,login, andlogout.- Components access authentication state anywhere in the component tree without prop drilling.
- This approach makes managing login state clean and scalable.
Example 3: Shared User Preferences
User preferences such as language, font size, or layout style can be shared globally using useContext.
Step 1: Create Preferences Context
import React, { createContext, useState } from "react";
export const PreferencesContext = createContext();
export function PreferencesProvider({ children }) {
const [preferences, setPreferences] = useState({
language: "en",
fontSize: "medium"
});
const updatePreferences = (newPrefs) => {
setPreferences({ ...preferences, ...newPrefs });
};
return (
<PreferencesContext.Provider value={{ preferences, updatePreferences }}>
{children}
</PreferencesContext.Provider>
);
}
Step 2: Consume Preferences in Components
Language Selector
import React, { useContext } from "react";
import { PreferencesContext } from "./PreferencesContext";
function LanguageSelector() {
const { preferences, updatePreferences } = useContext(PreferencesContext);
return (
<div>
<p>Current Language: {preferences.language}</p>
<button onClick={() => updatePreferences({ language: "en" })}>English</button>
<button onClick={() => updatePreferences({ language: "es" })}>Spanish</button>
</div>
);
}
Font Size Selector
import React, { useContext } from "react";
import { PreferencesContext } from "./PreferencesContext";
function FontSizeSelector() {
const { preferences, updatePreferences } = useContext(PreferencesContext);
return (
<div>
<p>Font Size: {preferences.fontSize}</p>
<button onClick={() => updatePreferences({ fontSize: "small" })}>Small</button>
<button onClick={() => updatePreferences({ fontSize: "medium" })}>Medium</button>
<button onClick={() => updatePreferences({ fontSize: "large" })}>Large</button>
</div>
);
}
Step 3: Wrap App with PreferencesProvider
import React from "react";
import ReactDOM from "react-dom";
import { PreferencesProvider } from "./PreferencesContext";
import LanguageSelector from "./LanguageSelector";
import FontSizeSelector from "./FontSizeSelector";
function App() {
return (
<PreferencesProvider>
<LanguageSelector />
<FontSizeSelector />
</PreferencesProvider>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
Explanation
- Preferences are stored in a single context.
- Multiple components can read or update preferences without passing props.
- This approach is scalable for adding new preference options in the future.
Combining Multiple Contexts
In larger apps, you may need to use multiple contexts (theme, auth, preferences) together.
Example
import React from "react";
import { ThemeProvider } from "./ThemeContext";
import { AuthProvider } from "./AuthContext";
import { PreferencesProvider } from "./PreferencesContext";
import Dashboard from "./Dashboard";
function App() {
return (
<ThemeProvider>
<AuthProvider>
<PreferencesProvider>
<Dashboard />
</PreferencesProvider>
</AuthProvider>
</ThemeProvider>
);
}
Explanation
- Multiple providers can be nested to provide separate contexts.
- Each component can consume one or more contexts as needed using
useContext.
Best Practices for useContext
- Use for Global Data Only
- Do not overuse context for local state.
- Local component state should still be managed with
useState.
- Keep Context Small and Focused
- Avoid storing unrelated data in a single context.
- Create separate contexts for theme, auth, preferences, etc.
- Memoize Context Values
const value = useMemo(() => ({ preferences, updatePreferences }), [preferences]);- Prevents unnecessary re-renders of consuming components.
- Avoid Frequent Updates in Context
- Avoid putting rapidly changing state (like animations) in context.
- Use local state or other state management tools for high-frequency updates.
- Combine Contexts Carefully
- Nest providers logically and avoid deep nesting when possible.
Leave a Reply