← Back to Chapters

Nested Contexts

⚛️ Nested Contexts

⚡ Quick Overview

In large React applications, you rarely have just one global state. You might have separate global states for theme, user authentication, language, etc. React lets you create multiple Contexts and nest their Providers so that different global states can coexist and be managed independently.

This pattern is called Nested Contexts and it helps keep your app modular, maintainable, and easier to scale.

? Multiple global states ? Separate concerns ? Cleaner architecture

? Key Concepts

  • Multiple Contexts – e.g., ThemeContext, UserContext, LanguageContext.
  • Nesting Providers – wrap components with more than one Provider.
  • Independent State – each Context controls its own state and consumers.
  • Performance – unrelated Context updates don’t re-render everything.
  • Composition – you can build a single wrapper that combines multiple Providers.

? Syntax & Theory

A Context is created with createContext(). To share data, you wrap components inside <MyContext.Provider>. When you have multiple pieces of global data, you simply create multiple Contexts and nest their Providers:

<ThemeContext.Provider> ➜ <UserContext.Provider> ➜ <App />

Components inside the tree can then use useContext() to read from each Context. The closest Provider in the tree overrides any outer Providers of the same Context.

?️ Example: Theme + User Context

First, create two separate Contexts: one for theme and one for the current user.

? View Code Example – ThemeContext.js
// ThemeContext.js: provides app-wide theme value
import { createContext } from "react";
const ThemeContext = createContext("light");
export default ThemeContext;
? View Code Example – UserContext.js
// UserContext.js: stores information about the current user
import { createContext } from "react";
const UserContext = createContext({ name: "Guest" });
export default UserContext;

Each Context now represents a different concern: visual theme vs. user identity.

?️ Nesting Providers in App

Now nest both Providers at the top level of your app so that all child components can access them.

? View Code Example – App.js
// App.js: nests ThemeContext and UserContext providers
import React, { useState } from "react";
import ThemeContext from "./ThemeContext";
import UserContext from "./UserContext";
import Dashboard from "./Dashboard";

function App() {
const [theme, setTheme] = useState("light");
const [user, setUser] = useState({ name: "Meghraj" });

return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<UserContext.Provider value={{ user, setUser }}>
<Dashboard />
</UserContext.Provider>
</ThemeContext.Provider>
);
}

export default App;

? Explanation

Both ThemeContext.Provider and UserContext.Provider wrap the <Dashboard /> component. As a result, Dashboard (and all its children) can read and update both theme and user using useContext().

? Consuming Multiple Contexts

You can consume multiple Contexts inside the same component by calling useContext() more than once.

? View Code Example – Dashboard.js
// Dashboard.js: reads from both ThemeContext and UserContext
import React, { useContext } from "react";
import ThemeContext from "./ThemeContext";
import UserContext from "./UserContext";

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

return (
<div className={`p-3 text-center bg-${theme === "light" ? "light" : "dark"} text-${theme === "light" ? "dark" : "light"}`}>
<h4>Welcome, {user.name}!</h4>
<button
className="btn btn-primary"
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
>
Toggle Theme
</button>
</div>
);
}

export default Dashboard;

?️ Live Output (Conceptual)

When this component renders, it shows a greeting like “Welcome, Meghraj!”. Clicking the Toggle Theme button flips the theme between "light" and "dark", updating both the background and text styles. The user data stays untouched because it belongs to a separate Context.

? Order of Providers

The order of nested Providers matters. Components inside the tree will receive the nearest Provider’s value for a given Context.

? View Code Example – Provider Order
// Example: ThemeContext value is "dark" inside this tree
<ThemeContext.Provider value="dark">
<UserContext.Provider value={{ name: "John" }}>
<App />
</UserContext.Provider>
</ThemeContext.Provider>

If another ThemeContext.Provider is nested deeper in the tree, it will override the outer value for all components inside its scope.

⚙️ Combining Providers (Cleaner Pattern)

Deeply nesting multiple Providers can make your root file messy. A common pattern is to create a single AppProviders component that composes all your Providers in one place.

? View Code Example – AppProviders.js
// AppProviders.js: wraps children with all global providers
import React from "react";
import ThemeContext from "./ThemeContext";
import UserContext from "./UserContext";

function AppProviders({ children }) {
const [theme, setTheme] = React.useState("light");
const [user, setUser] = React.useState({ name: "Guest" });

return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
</ThemeContext.Provider>
);
}

export default AppProviders;
? View Code Example – index.js
// index.js: mounts the app inside AppProviders
import React from "react";
import ReactDOM from "react-dom";
import AppProviders from "./AppProviders";
import App from "./App";

ReactDOM.render(
<AppProviders>
<App />
</AppProviders>,
document.getElementById("root")
);

? Why This Is Better

  • Your index.js stays small and easy to read.
  • Adding or removing a Provider is done in one place (AppProviders).
  • Helps organize all global state wiring into a single, reusable component.

? Summary

  • Multiple Contexts – manage different types of shared data (Theme, Auth, Language, etc.).
  • Nesting Providers – Providers can be nested; inner Providers override outer ones of the same Context.
  • Consumption – use multiple useContext() hooks in a single component to read multiple Contexts.
  • Cleaner Pattern – combine all Providers into an AppProviders wrapper for better structure.

? Tips & Best Practices

  • Keep each Context focused on a single responsibility (e.g., theme only, user only).
  • Use clear, descriptive names like ThemeContext, UserContext, LanguageContext.
  • Avoid very deep Provider trees by using an AppProviders wrapper component.
  • If you find yourself creating too many Contexts, consider a dedicated state library (Redux, Zustand, Jotai, etc.).
  • Prefer colocating Contexts near the part of the tree that actually needs them rather than at the absolute root.

? Try It Yourself

  1. Create ThemeContext and LanguageContext in separate files.
  2. Wrap your app with both Providers (either directly or via AppProviders).
  3. In a single component (e.g., Header), use useContext() twice to read both the current theme and language.
  4. Add buttons to toggle the theme (light/dark) and language (e.g., "en"/"hi") independently.
  5. Display the current theme and language in the UI so you can see the changes.

Goal: Get comfortable creating, nesting, and consuming multiple Contexts to manage different parts of your app’s global state.