useContext lets any component read shared data from a Context Provider without passing props through every parent component. It is mainly used to avoid prop drilling — passing the same props through many components that do not need them.
What Is Context?
Context is React’s built-in way to share data across a component tree. A Provider supplies the value, and any child component can read it using useContext.
Real-World Use Cases
Developers commonly use Context for data needed in many places:
- Theme: light/dark mode.
- Logged-in user: username, role, profile photo.
- Language: English, Tamil, Hindi, etc.
- Cart: products, total price, add/remove item.
- Search query: shared between a search bar and results page.
- Authentication token: stored after login and used by API calls.
Your Theme Context
Your ThemeProvider is a good real-world example. It reads the saved theme from localStorage, checks the user’s system preference, updates the HTML data-theme attribute, and saves the choice.
import React from "react";
import { useState, useEffect, createContext, useContext } from "react";
const ThemeContext = createContext();
const getInitialTheme = () => {
const saved = localStorage.getItem("theme");
if (saved) return saved;
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
};
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState(getInitialTheme);
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
localStorage.setItem("theme", theme);
}, [theme]);
const toggleTheme = () => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export const useTheme = () => useContext(ThemeContext);
How It Works
- createContext() creates the Context.
- ThemeProvider holds the actual theme state.
- value={{ theme, toggleTheme }} shares both the current theme and the function to change it.
- useTheme() lets any component read or update the theme.
Using It in a Component
import { useTheme } from "./ThemeContext";
function ThemeButton() {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme}>
Current theme: {theme}
</button>
);
}
export default ThemeButton;
When toggleTheme() changes theme, React automatically re-renders every component that calls useTheme().
Your Search Context
Your SearchContext is useful when a search input and a results list are in different components.
import { createContext, useContext, useState } from "react";
const SearchContext = createContext(null);
export function SearchProvider({ children }) {
const [query, setQuery] = useState("");
return (
<SearchContext.Provider value={{ query, setQuery }}>
{children}
</SearchContext.Provider>
);
}
export function useSearch() {
return useContext(SearchContext);
}
Search Input
import { useSearch } from "./SearchContext";
function SearchBar() {
const { query, setQuery } = useSearch();
return (
<input
type="text"
placeholder="Search products..."
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
);
}
export default SearchBar;
Search Results
import { useSearch } from "./SearchContext";
function SearchResults({ products }) {
const { query } = useSearch();
const filteredProducts = products.filter((product) =>
product.name.toLowerCase().includes(query.toLowerCase())
);
return (
<ul>
{filteredProducts.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
export default SearchResults;
Here, SearchBar updates query, and SearchResults reads the same query — without passing props between them.
Wrap Providers in App
import { ThemeProvider } from "./ThemeContext";
import { SearchProvider } from "./SearchContext";
import Navbar from "./Navbar";
import SearchBar from "./SearchBar";
import SearchResults from "./SearchResults";
function App() {
return (
<ThemeProvider>
<SearchProvider>
<Navbar />
<SearchBar />
<SearchResults products={products} />
</SearchProvider>
</ThemeProvider>
);
}
export default App;
Any component inside these providers can use useTheme() or useSearch().
Benefits of useContext
| Benefit | Meaning |
|---|---|
| Avoids prop drilling | No need to pass props through components that do not use them. react+1 |
| Global/shared state | Theme, user, language, and cart data can be accessed anywhere under the Provider. w3schools |
| Clean components | Child components become simpler because they do not receive unnecessary props. |
| Built into React | No external library is required for small and medium apps. react |
| Automatic updates | When context value changes, consumers re-render with fresh data. react+1 |
Disadvantages of useContext
| Disadvantage | Why It Matters |
|---|---|
| Unnecessary re-renders | When the Provider value changes, every component using that Context re-renders — even if it uses only one small part of the value. react+1 |
| Not a full state manager | Context shares values, but it does not provide selectors, caching, middleware, or advanced state tools like Redux or Zustand. tech-interview |
| Harder to test | Components depend on a Provider, so tests may need to wrap components with that Provider. |
| Can become messy | Putting too much data in one Context creates a “mega context” that is difficult to maintain. frontend+1 |
| Not ideal for frequent updates | Fast-changing values such as mouse position, scroll position, or typing text can cause many re-renders. frontend |
Important Performance Tip
This line creates a new object on every render:
<ThemeContext.Provider value={{ theme, toggleTheme }}>
Because the object reference changes, all context consumers may re-render more often than needed. You can memoize it:
import { useMemo } from "react";
const value = useMemo(
() => ({ theme, toggleTheme }),
[theme]
);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
useMemo keeps the same object reference unless theme actually changes.
Best Practices
- Create separate contexts for unrelated data: ThemeContext, AuthContext, CartContext.
- Do not put everything into one huge global context.
- Use Context for low-frequency global data such as theme, user, and language.
- For high-frequency updates, keep state close to the component that needs it or use refs/external stores.
- Expose a custom hook like useTheme() or useSearch() instead of calling useContext directly everywhere.
- For larger apps with complex shared state, consider Redux Toolkit, Zustand, or Jotai.
Simple Rule
Use useContext when many components — especially deeply nested ones — need the same data. Use normal useState when the data is needed only inside one component or a small part of the UI.
Top comments (0)