In React, the useContext hook allows you to access values from React Context in a functional component. React Context provides a way to share values (such as global data or functions) between components without passing props down manually at every level.
This is especially useful when you need to pass data deeply down the component tree, avoiding “prop drilling.”
How React Context Works
React.createContext(): Creates a context object.Context.Provider: Wraps around components that need access to the context and provides the value to its children.useContext: A hook that allows components to consume the context value directly.
Basic Syntax of useContext
- Create a context using
React.createContext. - Wrap components with
Context.Providerand provide the value. - Use the
useContexthook to access the context value in a component.
const MyContext = React.createContext();
function MyComponent() {
const value = useContext(MyContext);
return <div>{value}</div>;
}
Step-by-Step Example
Let’s explore a simple example where we have an application theme (like dark or light mode) that needs to be shared between multiple components.
1. Create a Context
import React, { createContext } from 'react';
// Create a Context for the theme
const ThemeContext = createContext('light'); // Default value is 'light'
export default ThemeContext;
2. Provide the Context in a Parent Component
We use the Context.Provider to supply the value (theme in this case) to the children components.
import React, { useState } from 'react';
import ThemeContext from './ThemeContext';
import ChildComponent from './ChildComponent';
function App() {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={theme}>
<button onClick={toggleTheme}>Toggle Theme</button>
<ChildComponent />
</ThemeContext.Provider>
);
}
export default App;
- Here,
ThemeContext.Providerwraps aroundChildComponentto provide the currentthemevalue to the subtree. - The
toggleThemefunction allows us to change the theme between ‘light’ and ‘dark’.
3. Consume the Context Using useContext
In any descendant component, you can consume the theme value using the useContext hook.
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';
function ChildComponent() {
const theme = useContext(ThemeContext); // Consume the theme context
return (
<div style={{ backgroundColor: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}>
<p>The current theme is {theme}</p>
</div>
);
}
export default ChildComponent;
useContext(ThemeContext)allowsChildComponentto access the currentthemevalue.- The background and text color change based on the current theme.
Full Example
Here’s the full example of using useContext:
// ThemeContext.js
import React, { createContext } from 'react';
const ThemeContext = createContext('light');
export default ThemeContext;
// App.js
import React, { useState } from 'react';
import ThemeContext from './ThemeContext';
import ChildComponent from './ChildComponent';
function App() {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={theme}>
<button onClick={toggleTheme}>Toggle Theme</button>
<ChildComponent />
</ThemeContext.Provider>
);
}
export default App;
// ChildComponent.js
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';
function ChildComponent() {
const theme = useContext(ThemeContext);
return (
<div style={{ backgroundColor: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}>
<p>The current theme is {theme}</p>
</div>
);
}
export default ChildComponent;
Example 2: Nested Context Usage
Context is particularly useful when data needs to be passed deeply through nested components. Here’s an example with user authentication:
1. Create a User Context
const UserContext = createContext();
2. Provide the Context in a Higher-Level Component
function App() {
const user = { name: 'John Doe', loggedIn: true };
return (
<UserContext.Provider value={user}>
<Profile />
</UserContext.Provider>
);
}
3. Access Context in a Nested Component
function Profile() {
return (
<div>
<ProfileDetails />
</div>
);
}
function ProfileDetails() {
const user = useContext(UserContext);
return (
<div>
<h1>Profile</h1>
<p>Name: {user.name}</p>
<p>Status: {user.loggedIn ? 'Logged In' : 'Logged Out'}</p>
</div>
);
}
In this example:
- The
userobject is passed fromUserContext.Providerto deeply nested components, allowingProfileDetailsto access it without intermediate components needing to know about it.
Benefits of useContext
- Avoids Prop Drilling: Context eliminates the need to pass props through every level of the component tree. Instead, data is provided at a higher level and accessed directly at deeper levels.
- Simplifies Global State Management: Context is useful for small-scale global state management, like theme, language, or authentication status.
- Easier Component Maintenance: As components are decoupled from their parents, maintenance becomes easier because changes to the parent do not affect the intermediate components.
When Not to Use useContext
- Frequent Updates: If the context value changes frequently, all components using that context will re-render. In such cases, other state management tools (like Redux or Recoil) may offer better performance.
- Overuse: Overusing context for every piece of state can make components harder to maintain. Use it only for global/shared state.
Example of Combined useReducer and useContext
Often, you’ll see useReducer and useContext combined for global state management:
import React, { useReducer, createContext, useContext } from 'react';
const CountContext = createContext();
function countReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function App() {
const [state, dispatch] = useReducer(countReducer, { count: 0 });
return (
<CountContext.Provider value={{ state, dispatch }}>
<Counter />
</CountContext.Provider>
);
}
function Counter() {
const { state, dispatch } = useContext(CountContext);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}
export default App;
Conclusion
useContextis a powerful tool for sharing state globally between components without the hassle of prop drilling.- It simplifies managing global or shared state, such as themes or authentication, but should be used wisely for performance reasons.
- For more complex state,
useContextcan be combined withuseReducer.
