The useContext Hook and Context API

As React applications grow, there comes a situation where data needs to be shared across many components at different levels of the component tree. Passing that data as props through every intermediate component becomes tedious and messy — this problem is commonly known as prop drilling.

The Context API solves this by providing a way to share data globally across the component tree. The useContext Hook is used inside functional components to consume that shared data.

What is Prop Drilling?

Imagine a user's theme preference (light/dark mode) needs to be accessed in a deeply nested component. Without Context, the theme would need to be passed as a prop from the top-level App component down through every intermediate component — even those that do not need the theme themselves:


// Prop drilling example (messy)
App → Layout → Sidebar → UserMenu → ThemeToggle

Each component in the chain must receive and forward the theme prop, even if it has no use for it. Context eliminates this chain entirely.

Creating a Context

Context is created using React.createContext(). This is typically done in a separate file and exported for use across the application:


import { createContext } from 'react';

const ThemeContext = createContext("light"); // "light" is the default value

export default ThemeContext;

The argument passed to createContext() is the default value — it is used when a component consumes the context but is not wrapped in a Provider.

Providing Context with a Provider

The Provider is a component that wraps the part of the application that needs access to the context value. Any component inside the Provider can consume the value:


import { useState } from 'react';
import ThemeContext from './ThemeContext';
import MainLayout from './MainLayout';

function App() {
  const [theme, setTheme] = useState("light");

  return (
    <ThemeContext.Provider value={theme}>
      <MainLayout />
    </ThemeContext.Provider>
  );
}

The value prop on the Provider is the data being shared. Every component inside this Provider can access theme directly — without it being passed as a prop.

Consuming Context with useContext

Inside any component wrapped by the Provider, the useContext Hook retrieves the context value:


import { useContext } from 'react';
import ThemeContext from './ThemeContext';

function ThemeDisplay() {
  const theme = useContext(ThemeContext);

  return <p>Current theme: {theme}</p>;
}

No matter how deeply ThemeDisplay is nested inside the component tree, it can directly access the theme value without receiving it through props.

Full Example — Theme Toggler

This example shows a complete implementation of Context with a toggle feature. To keep it within a single file, all parts are included together:


import { createContext, useContext, useState } from 'react';

// 1. Create Context
const ThemeContext = createContext();

// 2. Create a custom Provider component
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");

  function toggleTheme() {
    setTheme((prev) => (prev === "light" ? "dark" : "light"));
  }

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 3. Consume the context in a nested component
function ThemeButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <div>
      <p>Current Theme: {theme}</p>
      <button onClick={toggleTheme}>Switch to {theme === "light" ? "Dark" : "Light"} Mode</button>
    </div>
  );
}

// 4. Wrap the app in the Provider
function App() {
  return (
    <ThemeProvider>
      <ThemeButton />
    </ThemeProvider>
  );
}

export default App;

In this example, both the theme value and the toggleTheme function are passed through context together as an object. Any component inside ThemeProvider can access and use both.

When to Use Context

Context is a good fit for data that is:

  • Needed by many components at different nesting levels
  • Global in nature, such as the current user, theme, or language preference
  • Relatively static or only updated infrequently

Context is not a replacement for all state management. For simple parent-to-child communication, props are still the right tool. For frequent, complex state updates, dedicated libraries like Redux may be more appropriate — covered in a later topic.

Multiple Contexts

An application can have multiple independent contexts — one for authentication, one for theme, one for language, etc. Each is created separately and wrapped around the relevant part of the application:


<AuthContext.Provider value={authData}>
  <ThemeContext.Provider value={themeData}>
    <App />
  </ThemeContext.Provider>
</AuthContext.Provider>

Key Points

  • Context API solves the prop drilling problem by sharing data globally across the component tree.
  • createContext() creates a new context object with an optional default value.
  • The Provider component wraps the parts of the app that need access to the context value.
  • The useContext() Hook retrieves the context value inside any wrapped component.
  • Context is ideal for global data like themes, authentication, and language settings.
  • For simple parent-to-child communication, props are still the better option.

The next topic covers the useReducer Hook — an alternative to useState that is better suited for managing complex state logic with multiple related actions.

Leave a Comment

Your email address will not be published. Required fields are marked *