DEV Community

Rakesh Daniel
Rakesh Daniel

Posted on

useContext in React — Real-World Guide

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);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>
);
Enter fullscreen mode Exit fullscreen mode

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)