In React applications, prop drilling is a common problem where data is passed down multiple levels through props, even when intermediate components don’t need it. This can make the code harder to maintain and update.
React’s Context API with the useContext hook provides a built-in solution to avoid prop drilling and simplify state management.
In this post, you’ll learn how to use context API with the useContext hook to avoid prop drilling.
Before we get started, don’t forget to subscribe to my newsletter!
Get the latest tips, tools, and resources to level up your web development skills delivered straight to your inbox. Subscribe here!
Now let’s jump right into it!🚀
What is Prop Drilling?
State management is important in React to keep different components in sync. In small applications, passing state from a parent component to a child component is simple. But as your app grows, you may need to share state between multiple components that are not related. This makes the process inefficient.
When state is passed at multiple levels inside a component, this is called prop drilling.
For example:
const Parent = () => {
const user = { name: "John" };
return <Child user={user} />;
};
const Child = ({ user }) => {
return <GrandChild user={user} />;
};
const GrandChild = ({ user }) => {
return <p>Hello, {user.name}!</p>;
};
In this example, the user
is passed down three levels, even though only GrandChild
needs it.
👉 If you’re new to how state and props work in React, check out my beginner-friendly guide on State and Props before continuing.
How to Avoid Prop Drilling?
To avoid prop drilling, you can use React Context API.
React Context API is a built-in state management solution that provides a global store that you can access anywhere within the app and share state globally across components without passing props manually.
Context API is useful because:
- It avoids prop drilling.
- This is a lightweight alternative to Redux.
- Needs no extra set up as it is built in React.
- Works well for small to medium-sized applications.
How to Use React Context API (Step-by-Step Guide)
Here’s a step-by-step guide to setting up context API:
Step 1: Create a Context
Create a new file UserContext.js
and define a context.
import { createContext } from "react";
// Create Context
export const UserContext = createContext();
Here,
-
createContext()
creates a new global context. - This context will be used to share data between components.
Tip: Keep your context files in a separate folder within the app for code maintainability.
You might be wondering, “Do I need to memorize this Syntax?”
No worries! You can always refer back to this guide or the official docs whenever needed.
Step 2: Create a Context Provider
Create a context provider in the same file like this:
import { useState } from "react";
export function UserProvider({ children }) {
const [user, setUser] = useState("John");
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}
Here,
-
useState(“John”)
initializes state with the value John. -
<UserContext.Provider>
wraps the app and provides the user state. - The value prop makes
user
andsetUser
available throughout the app.
Now, your UserContext.js
file will look like this:
import { createContext, useState } from "react";
export const UserContext = createContext();
export function UserProvider({ children }) {
const [user, setUser] = useState("John");
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}
Step 3: Wrap the App in the Provider
Modify App.js
to wrap the app with UserProvider
.
import { UserProvider } from "./UserProvider";
import Home from "./Home";
import About from "./About";
function App() {
return (
<UserProvider>
<Home />
<About />
</UserProvider>
);
}
export default App;
Here,
- The
UserProvider
wraps the entire application. - Now,
Home
andAbout
can access the user state without prop drilling.
Step 4: Consume the Context in Components
(A) Read Context Data using useContext Hook
import { useContext } from "react";
import { UserContext } from "./UserContext";
function Home() {
// The value of user is extracted using destructuring
const { user } = useContext(UserContext);
return <h1>Welcome, {user}!</h1>;
}
export default Home;
Here,
-
useContext(UserContext)
gives access to the global state. -
user
is directly used in the component without passing props.
(B) Updating the State in Context
import { useContext } from "react";
import { UserContext } from "./UserContext";
function About() {
const { user, setUser } = useContext(UserContext);
return (
<div>
<h1>Current User: {user}</h1>
<button onClick={() => setUser("David")}>Change User</button>
</div>
);
}
export default About;
Here, clicking the button updates the user globally across all components.
Performance Optimization
When the context state gets updated, all of the connected components get re-rendered, which is unnecessary. To optimize this:
Use useMemo for Context Value
Wrap the state in useMemo
to prevent re-renders unless the state actually changes.
const value = useMemo(() => ({ user, setUser }), [user]);
Complete example:
export function UserProvider({ children }) {
const [user, setUser] = useState("John");
const value = useMemo(() => ({ user, setUser }), [user]);
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
}
Without useMemo
, every re-render of the provider will create a new object for value={{ user, setUser }};
this will cause all the components (which are using the user state) to re-render even if the user hasn’t changed.
Split Contexts for Better Performance
Instead of using a single context for everything, create multiple contexts for different concerns (e.g., user state, theme state).
For example:
<UserProvider>
<ThemeProvider>
<App />
</ThemeProvider>
</UserProvider>
This prevents unnecessary updates when only one state changes.
How Does This Work?
- UserProvider manages the user state (e.g., authentication, profile details).
- ThemeProvider manages theme preferences (light/dark mode).
- The App component and all its children can now access both user data and theme settings without prop drilling.
Creating Theme Context
import { createContext, useState } from "react";
export const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
Now, any component inside <ThemeProvider>
can access and update the theme.
When to Use Context API?
- When multiple components are sharing the same state.
- To avoid prop drilling.
- To avoid heavy setup of Redux.
- When you want to manage a global state with a lightweight solution.
- For managing themes, authentication, or language settings.
🎯Wrapping Up
That’s all for today!
For paid collaboration connect with me at : connect@shefali.dev
I hope this post helps you.
If you found this post helpful, here’s how you can support my work:
☕ Buy me a coffee – Every little contribution keeps me motivated!
📩 Subscribe to my newsletter – Get the latest tech tips, tools & resources.
𝕏 Follow me on X (Twitter) – I share daily web development tips & insights.
Keep coding & happy learning!
Top comments (0)