Passing down data in React components has been one of the major challenges faced by React developers since the introduction of functional components. Every component needs to use data, either wholly or in parts. While useState
has been our go-to for holding data locally, its limitations make it difficult to pass down data efficiently. In the past, we relied on props, leading to issues like prop drilling and cumbersome state management, making components error-prone and harder to maintain. This is where useContext
comes in.
In this article, we will discuss:
- What is
useContext
? - How to set up Context in React
- How to use
useContext
in components - When to use
useContext
- Common mistakes and best practices
On that note, let's dive into it!
What is useContext
?
Simply put, useContext
is a way of sharing data across a React application without having to pass it manually through props. It works with React.createContext
to pass down data to components.
Imagine we have a large farm, which undoubtedly leads to a large storehouse. This storehouse has a farmhand who keeps track of the tools and delivers them where needed. React Context is the storehouse that holds all the data or “tools,” and useContext
is our trusted farmhand that fetches these tools from storage and delivers them where needed.
useContext
was introduced in React version 16.8. It leverages the Context API, which allows a user to store data in a global state. How does it do this? React.createContext
is used to create a Context object (which includes Provider and Consumer components), and useContext
is a hook that allows you to consume the context value from React.createContext
. It is important to note that the useContext
hook can only be used in functional components.
How to Set Up Context in a React Project
Setting up Context in a React application is straightforward. Follow these steps:
- Create a context file.
- Create the context using the React Context API.
- Provide the context value.
- Wrap the app in the provider.
- Consume the context in the child component.
See? Very easy! Now, let's put these steps into code.
Step 1: Create a Context File
Some developers prefer to create a context
folder and store all context files inside it. This is especially useful when dealing with multiple contexts in a project.
Step 2: Create the Context using the React Context API
import React, { createContext, useContext, useState } from "react";
const DataContext = createContext();
Above, we are:
- Importing
useState
(to locally hold the data). - Using
createContext
to create the context. - Importing
useContext
so we can send the data to the components.
Step 3: Provide the Context Value
In our project, the context value will be data fetched from an API and passed down to components. This ensures that all components have access to the fetched data.
export const DataProvider = ({ children }) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
return (
<DataContext.Provider value={{ data, loading, error }}>
{children}
</DataContext.Provider>
);
};
// Custom hook to use the DataContext
export const useData = () => useContext(DataContext);
Above, we:
- Created the
DataProvider
component, which acceptschildren
as an argument. - Initialized the
data
,loading
, anderror
states. - Used
DataContext.Provider
to wrap thechildren
, providing data to all components in the tree. - Created a
useData
hook to make accessing context data easier.
Now, let's fetch our data using useEffect
:
useEffect(() => {
setLoading(true);
axios
.get("https://jsonplaceholder.typicode.com/users")
.then((response) => {
setData(response.data);
setLoading(false);
})
.catch((error) => {
setError(error);
setLoading(false);
console.log(error);
});
}, []);
Step 4: Wrap the App in the Provider
To ensure our data is accessible to all components, we wrap our App
component with DataProvider
:
const App = () => {
return (
<DataProvider>
<Components />
</DataProvider>
);
};
export default App;
Step 5: Consume the Context in the Child Component
import DataContext from "../context/DataContext";
import { useContext } from "react";
const { data, error, loading } = useContext(DataContext);
Now, all values (data
, error
, loading
) can be used in any component!
When to Use useContext
There are various scenarios where useContext
is useful:
-
Avoiding Prop Drilling – When sharing data across multiple levels of components,
useContext
can provide direct access to the data without passing props manually. -
Managing Theme or UI Preferences – For apps with cross-functional UI preferences such as light/dark mode,
useContext
is a great way to manage state globally. -
Centralizing API Data or Configurations – When API data needs to be used across various components,
useContext
is an efficient way to share this globally. -
Simplifying Component Composition – When multiple components use the same logic or shared values (e.g., modals, notifications, form steps),
useContext
provides a cleaner way to manage them.
Common Mistakes and Best Practices When Using useContext
-
Using Context for Everything – Overusing
useContext
when props would be a better choice. Instead, useuseContext
for truly shared or global state. -
Using
useContext
in a Component Not Wrapped in a Provider – Ensure all components that consume context are wrapped in<Context.Provider>
. - Using Multiple Contexts Inefficiently – Avoid deeply nesting multiple contexts. Instead, use a custom provider component to combine them efficiently.
Conclusion
In this article, we covered:
- The concept and importance of
useContext
in React. - How to set up and use
useContext
effectively. - Common pitfalls to avoid and best practices to follow.
useContext
is a powerful tool that simplifies state management in React applications. It eliminates unnecessary prop drilling and makes components cleaner and more maintainable. However, it's essential to use it wisely and only when necessary.
To learn more, check out the official React documentation on Context.
Happy coding! 🚀