React’s Context API: Sharing State Without Prop Drilling
React’s Context API provides a way to share data across a component tree without passing props through every level. This is especially useful for global application state like theme settings, user authentication, or UI preferences. In this tutorial, we’ll build a theme toggler using Context, covering both class-based and functional components.
The Problem: Prop Drilling
Consider a small app with a Text component that needs to know the current theme color. The theme is managed with useState in the top-level App component:
setTheme( theme === "red"? "blue": "red");
This works, but the theme value must be passed down through every intermediate component, even those that don’t use it. In a deeply nested tree, this “prop drilling” becomes unwieldy and clutters component code. Context solves this by making data available to any component that requests it, regardless of nesting depth.
Setting Up a Theme Context
First, create a file called ThemeContext.js in your /src folder. To create a context object, use React.createContext():
import React from "react";
const ThemeContext = React.createContext("light");
export default ThemeContext;
Here, the default value is the string "light". To make this value available to components, every context object includes a Provider component. Wrap your application (or a section of it) in the provider and pass the shared data through a value prop:
function App() {
const theme = "light";
return (
<ThemeContext.Provider value = {theme}>
<div>
</div>
</ThemeContext.Provider>
);
}
Any component inside the provider can now consume the context value.
Defining Theme Colors
Next, create a Colors.js file containing color objects for each theme mode:
const AppTheme = {
light: {
textColor: "#000",
backgroundColor: "#fff"
},
dark: {
textColor: "#fff",
backgroundColor: "#333"
}
}
export default AppTheme;
This object maps themes to their associated CSS color values, which components will read from context.
Consuming Context in Class Components
Class-based components have two options for consuming a context.
Using contextType
The first approach assigns the context object to the static contextType property of the class. The context value then becomes accessible via this.context:
import React, { Component } from "react";
import ThemeContext from "../Context/ThemeContext";
import AppTheme from "../Colors";
class Main extends Component{
constructor(){
super();
}
static contextType = ThemeContext;
render(){
const currentTheme = AppTheme[this.context];
return(
<main></main>
);
}
}
render() {
const currentTheme = AppTheme[this.context];
return (
<main style={{
padding: "1rem",
backgroundColor: `${currentTheme.backgroundColor}`,
color: `${currentTheme.textColor}`,
}}>
<h1>Heading 1</h1>
<p>This is a paragraph</p>
<button> This is a button</button>
</main>
The downside of this method is that a class can only consume a single context.
Using Context.Consumer
The second approach uses the Consumer component that comes with every context object. It expects a function as a child, receiving the context value as an argument:
class Main extends Component {
constructor() {
super();
this.state = {
}
}
render(){
return(
<ThemeContext.Consumer>
{
(theme) => {
const currentTheme = AppTheme[theme];
return(
<main style = {{
padding: "1rem",
backgroundColor: `${currentTheme.backgroundColor}`,
color: `${currentTheme.textColor}`,
}}>
<h1>Heading 1</h1>
<p>This is a paragraph</p>
<button> This is a button</button>
</main>
)
}
}
</ThemeContext.Consumer>
);
}
}
This approach supports multiple consumers and does not require assignment of a static property.
Consuming Context in Functional Components
Functional components simplify context consumption significantly with the useContext hook. Pass the context object to the hook, and it returns the current value:
const Main = () => {
const theme = useContext(ThemeContext);
const currentTheme = AppTheme[theme];
return(
<main style = {{
padding: "1rem",
backgroundColor: `${currentTheme.backgroundColor}`,
color: `${currentTheme.textColor}`,
}}>
<h1>Heading 1</h1>
<p>This is a paragraph</p>
<button> This is a button</button>
</main>
);
}
export default Main;
This is the most concise way to access shared state in functional components.
Making the Theme Dynamic
The setup above uses a static value. To toggle themes, the context must hold both the current theme and an update function. Modify the createContext call to mirror the structure of a useState result:
const ThemeContext = React.createContext(["light", () => {}]);
Then, in App.js, supply a real state hook as the provider’s value:
function App() {
const themeHook = useState("light");
return (
<ThemeContext.Provider value = {themeHook}>
<div>
<Header />
<Main />
</div>
</ThemeContext.Provider>
);
}
Now consumers can destructure the context: the first element is the theme, the second is the setter. The ThemeToggler component flips the theme with that setter:
import React,{useContext} from "react";
import ThemeContext from "../Context/ThemeContext";
const themeTogglerStyle = {
cursor: "pointer"
}
const ThemeToggler = () => {
const[themeMode, setThemeMode] = useContext(ThemeContext);
return(
<div style = {themeTogglerStyle} onClick = {() => {setThemeMode(themeMode === "light"? "dark": "light")}}>
<span title = "switch theme">
{themeMode === "light" ? "🌙" : "☀️"}
</span>
</div>
);
}
export default ThemeToggler;
Finally, update the Main components so they read the theme from the context. The class-based versions using contextType and Consumer, plus the function-based version, all follow the patterns outlined above and are included in the complete set of code blocks.
When to Use Context
Context is most valuable for data that many components rely on and that changes infrequently—like a theme, a logged-in user, or locale settings. For high-frequency state updates, React’s state management tools may be a better fit.
The Context API elegantly avoids prop drilling, and with useContext, functional components have a straightforward path to shared state, while class components have two well-defined mechanisms for the same.
Wrapping Up
The theme toggle is now fully functional, with both light and dark modes available through the Context API. Beyond the working feature, the implementation demonstrates the core ideas behind Context:
- What the Context API is and the specific problem it solves in React state management.
- The appropriate scenarios where Context should be used instead of prop drilling.
- How to create a
Contextinstance and consume it in both functional components (viauseContext) and class-based components (viaContext.ConsumerorcontextType).
Related Resources
For more on modern React development and related frontend techniques, the following articles are worth reading:
- Styling In Modern Web Apps
- Building Mobile Apps With Ionic And React
- Build A PWA With Webpack And Workbox
- Getting To Know The MutationObserver API




