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
ThemeContext, UserContext, LanguageContext.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.
First, create two separate Contexts: one for theme and one for the current user.
// ThemeContext.js: provides app-wide theme value
import { createContext } from "react";
const ThemeContext = createContext("light");
export default ThemeContext;
// 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.
Now nest both Providers at the top level of your app so that all child components can access them.
// 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;
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().
You can consume multiple Contexts inside the same component by calling useContext() more than once.
// 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;
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.
The order of nested Providers matters. Components inside the tree will receive the nearest Provider’s value for a given Context.
// 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.
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.
// 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;
// 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")
);
index.js stays small and easy to read.AppProviders).useContext() hooks in a single component to read multiple Contexts.AppProviders wrapper for better structure.ThemeContext, UserContext, LanguageContext.AppProviders wrapper component.ThemeContext and LanguageContext in separate files.AppProviders).Header), use useContext() twice to read both the current theme and language.light/dark) and language (e.g., "en"/"hi") independently.Goal: Get comfortable creating, nesting, and consuming multiple Contexts to manage different parts of your app’s global state.