UseContext for Global Data

Introduction

React is a component-based library that allows developers to build dynamic user interfaces by managing state and passing data between components. While props are the primary way to pass data from parent to child components, managing state across deeply nested components can become cumbersome. This problem, known as prop drilling, occurs when data must be passed through intermediate components that do not need it.

The React Context API solves this problem by providing a way to share data globally across the component tree without explicitly passing props at every level. The useContext hook makes it easy for functional components to access context values.

In this post, we will explore:

  1. Creating context
  2. Providing context values
  3. Consuming context in child components
  4. When to use context versus prop drilling

By the end of this post, you will have a comprehensive understanding of how to use useContext for global state management in React.


What is Context in React?

Context provides a way to share values like state, functions, or theme data across multiple components without passing props manually through every level. It is particularly useful for global data that many components need, such as:

  • User authentication state
  • Theme settings (dark/light mode)
  • Language preferences
  • Application settings

1. Creating Context

The first step is to create a context object using React.createContext. This object will hold the global data and provide methods to access it.

Example: Creating a Theme Context

import React from "react";

// Create a context with a default value
const ThemeContext = React.createContext("light");

export default ThemeContext;

Explanation:

  • ThemeContext is now a context object.
  • "light" is the default value, used when a component consuming the context does not have a matching provider above it in the tree.

2. Providing Context Values

To share data, you need a Context Provider. The provider wraps components and supplies context values.

Example: Providing Theme Value

import React, { useState } from "react";
import ThemeContext from "./ThemeContext";
import Toolbar from "./Toolbar";

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

  const toggleTheme = () => {
setTheme(theme === "light" ? "dark" : "light");
}; return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
  <div>
    <Toolbar />
    <button onClick={toggleTheme}>Toggle Theme</button>
  </div>
</ThemeContext.Provider>
); } export default App;

Explanation:

  • ThemeContext.Provider wraps the components that need access to the context.
  • The value prop contains the data we want to share. Here, it is an object containing theme and toggleTheme.
  • Components inside the provider can access this data without receiving props from the parent.

3. Consuming Context in Child Components

The useContext hook allows functional components to consume context values easily.

Example: Using Theme Context in a Child Component

import React, { useContext } from "react";
import ThemeContext from "./ThemeContext";

function Toolbar() {
  const { theme } = useContext(ThemeContext);

  return (
<div style={{ backgroundColor: theme === "light" ? "#fff" : "#333", color: theme === "light" ? "#000" : "#fff" }}>
  <p>Current theme: {theme}</p>
</div>
); } export default Toolbar;

Explanation:

  • useContext(ThemeContext) returns the current context value ({ theme, toggleTheme }).
  • No props need to be passed from App to Toolbar.
  • Changes in context automatically re-render all consuming components.

Example: Consuming Multiple Values

If you have multiple context values, you can consume them in a single component:

import React, { useContext } from "react";
import ThemeContext from "./ThemeContext";
import UserContext from "./UserContext";

function Dashboard() {
  const { theme } = useContext(ThemeContext);
  const { user } = useContext(UserContext);

  return (
<div style={{ backgroundColor: theme === "light" ? "#eee" : "#222", color: theme === "light" ? "#000" : "#fff" }}>
  <h1>Welcome, {user.name}</h1>
  <p>Theme: {theme}</p>
</div>
); }
  • Multiple useContext hooks can be used in the same component.

4. When to Use Context vs Prop Drilling

Prop Drilling

  • Definition: Passing data through multiple intermediate components that don’t need it.
  • Example: Passing theme from App to Toolbar through several nested components.
  • Problem: Makes the code verbose and harder to maintain.
function App() {
  const theme = "dark";
  return <Parent theme={theme} />;
}

function Parent({ theme }) {
  return <Child theme={theme} />;
}

function Child({ theme }) {
  return <Toolbar theme={theme} />;
}
  • Each intermediate component must receive and pass the prop.

Context

  • Definition: Provides a way to share data directly with the components that need it.
  • Benefits:
    1. Reduces unnecessary props.
    2. Simplifies component trees.
    3. Makes components more reusable.
  • Example: Using useContext eliminates intermediate props:
function Toolbar() {
  const { theme } = useContext(ThemeContext);
  return <div>{theme}</div>;
}

When to Use Context

  1. Global state or data needed by multiple components.
  2. Themes or UI settings.
  3. User authentication and roles.
  4. Language or localization data.
  5. Avoid using context for frequently changing high-volume data like input fields to prevent unnecessary re-renders.

Advanced Example: User Authentication

Creating User Context

import React from "react";

const UserContext = React.createContext(null);

export default UserContext;

Providing Context

import React, { useState } from "react";
import UserContext from "./UserContext";
import Dashboard from "./Dashboard";

function App() {
  const [user, setUser] = useState({ name: "Alice", role: "admin" });

  return (
&lt;UserContext.Provider value={{ user, setUser }}&gt;
  &lt;Dashboard /&gt;
&lt;/UserContext.Provider&gt;
); } export default App;

Consuming Context in Dashboard

import React, { useContext } from "react";
import UserContext from "./UserContext";

function Dashboard() {
  const { user } = useContext(UserContext);

  return (
&lt;div&gt;
  &lt;h1&gt;Welcome, {user.name}&lt;/h1&gt;
  &lt;p&gt;Role: {user.role}&lt;/p&gt;
&lt;/div&gt;
); } export default Dashboard;

Combining Multiple Contexts

When an application requires multiple contexts (theme, user, settings), you can nest providers or create a single provider combining multiple contexts.

function App() {
  return (
&lt;ThemeContext.Provider value={{ theme: "light" }}&gt;
  &lt;UserContext.Provider value={{ user: { name: "Alice" } }}&gt;
    &lt;Dashboard /&gt;
  &lt;/UserContext.Provider&gt;
&lt;/ThemeContext.Provider&gt;
); }
  • Components can consume any context they need using useContext.

Context with Dynamic Updates

Context values can be updated dynamically, and consuming components automatically re-render.

function ToggleThemeButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return <button onClick={toggleTheme}>Switch to {theme === "light" ? "Dark" : "Light"} Mode</button>;
}
  • The toggleTheme function updates the context state in the provider.
  • All components consuming theme reflect the change immediately.

Advantages of useContext

  1. Eliminates prop drilling.
  2. Makes components cleaner and easier to read.
  3. Centralizes shared state.
  4. Works well with functional components.
  5. Simple API with createContext and useContext.

Limitations of useContext

  1. Not a replacement for state management libraries: For complex apps, Redux, Zustand, or Recoil may be better.
  2. Frequent updates can re-render all consumers: Avoid putting rapidly changing data in context.
  3. Nested providers can become verbose: Consider combining providers.

Best Practices

  1. Only store global data in context.
  2. Keep context values stable using useMemo or useReducer if needed.
  3. Avoid storing transient UI state like input values in context.
  4. Name context clearly, e.g., ThemeContext, UserContext.
  5. Provide default values to avoid undefined errors.

Comments

Leave a Reply

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