React useContext Hook – Complete Detailed Notes
The useContext Hook is a built-in React Hook that allows a component to read and subscribe to data provided by a Context. It helps share information between components without passing props manually through every intermediate component. It is commonly used for themes, authentication, language preferences, user profiles, and shopping cart data.
React JS Training: JustAcademy React JS Training Course
Course Demo: Register for React JS Course Demo
1. What Is the useContext Hook?
The useContext Hook reads the current value of a Context created using createContext(). The value comes from the closest matching Context provider above the component in the React component tree. If no matching provider exists, React uses the default value supplied to createContext().
Syntax:
import { useContext } from 'react';
const value = useContext(MyContext);
Explanation:
useContext is the React Hook used to read Context.
MyContext is the Context object created using createContext().
value contains the current value provided by the nearest matching provider, or the Context's default value if no provider exists.
- When the provided Context value changes, components that read that Context receive the updated value and re-render as needed.
2. Why Do We Need useContext?
In React, data is commonly passed from parent components to child components using props. When deeply nested components need the same information, passing props through every intermediate component can become difficult. This is often called prop drilling.
The useContext Hook helps solve prop drilling by allowing components to read shared data directly from a Context provider.
Common use cases:
- Sharing light and dark theme settings.
- Providing the currently authenticated user.
- Sharing language and localization preferences.
- Managing shopping cart information across components.
- Sharing application settings.
- Providing permissions or user roles.
- Sharing state and functions across a component tree.
3. What Is Prop Drilling?
Prop drilling occurs when data must pass through multiple components that do not need the data themselves, simply to reach a deeply nested child component.
Example without Context:
function App() {
const username = 'Rahul';
return <Parent username={username} />;
}
function Parent({ username }) {
return <Child username={username} />;
}
function Child({ username }) {
return <h2>Welcome, {username}!</h2>;
}
export default App;
Problem: The Parent component only passes the username to Child. If the component tree becomes deeper, many components may need to pass props they do not actually use.
Solution: Create a Context and let the component that needs the username read it directly with useContext.
4. Understanding createContext()
Before using useContext, you usually create a Context object with React's createContext() function.
Syntax:
import { createContext } from 'react';
const MyContext = createContext(defaultValue);
Explanation:
createContext() creates a Context object.
defaultValue is the fallback value used when no matching provider exists above the consumer.
- The default value is static; it does not update by itself.
- A provider supplies the actual value to components within its subtree.
Example:
import { createContext } from 'react';
const ThemeContext = createContext('light');
export default ThemeContext;
In this example, the default theme is light. A provider can supply a different value, such as dark.
5. Understanding the Context Provider
A Context provider makes a value available to components below it in the component tree.
React 19 syntax:
<ThemeContext value="dark">
<AppContent />
</ThemeContext>
Compatible syntax for React 18 and earlier:
<ThemeContext.Provider value="dark">
<AppContent />
</ThemeContext.Provider>
Important: In React 19, you can render the Context object directly as the provider. In React 18 and earlier, use Context.Provider.
6. Basic Example of useContext
This example demonstrates how to share a username with a child component using Context.
import { createContext, useContext } from 'react';
const UserContext = createContext('Guest');
function UserProfile() {
const username = useContext(UserContext);
return <h2>Welcome, {username}!</h2>;
}
function App() {
return (
<UserContext.Provider value="Rahul">
<UserProfile />
</UserContext.Provider>
);
}
export default App;
Explanation:
createContext('Guest') creates the UserContext with a default value of Guest.
UserContext.Provider provides the value Rahul.
useContext(UserContext) reads the value from the closest matching provider.
- The component displays Welcome, Rahul!
7. How Does useContext Work?
- Create a Context using
createContext().
- Wrap the relevant component tree with the matching provider.
- Pass the shared value through the provider's
value prop.
- Call
useContext(MyContext) inside a component that needs the value.
- React finds the closest matching provider above that component.
- If the provided value changes, React updates the components that consume the Context.
Basic flow:
createContext()
↓
Create a Context object
↓
Wrap components with a provider
↓
Pass data using the value prop
↓
Child component calls useContext()
↓
Context value is returned
↓
Consumer receives updated value when Context changes
8. Example: Light and Dark Theme
A theme is a common use case for useContext because multiple components may need the same theme setting.
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext(null);
function ThemeButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{
backgroundColor: theme === 'dark' ? '#222' : '#eee',
color: theme === 'dark' ? '#fff' : '#111'
}}
>
Current Theme: {theme} - Toggle Theme
</button>
);
}
function App() {
const [theme, setTheme] = useState('light');
function toggleTheme() {
setTheme(previousTheme =>
previousTheme === 'light' ? 'dark' : 'light'
);
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<div>
<h1>Theme Example</h1>
<ThemeButton />
</div>
</ThemeContext.Provider>
);
}
export default App;
Explanation:
ThemeContext stores the shared theme information.
useState() maintains the current theme in App.
- The provider passes the theme and toggle function to its descendants.
ThemeButton reads both values using useContext.
- Clicking the button changes the theme and updates the consuming component.
9. Sharing Multiple Values Through Context
A Context can provide a string, number, boolean, array, object, or function. An object is often useful when several related values need to be shared.
import { createContext, useContext } from 'react';
const UserContext = createContext(null);
function Profile() {
const user = useContext(UserContext);
return (
<div>
<p>Name: {user.name}</p>
<p>Role: {user.role}</p>
<p>City: {user.city}</p>
</div>
);
}
function App() {
const user = {
name: 'Priya',
role: 'Developer',
city: 'Mumbai'
};
return (
<UserContext.Provider value={user}>
<Profile />
</UserContext.Provider>
);
}
export default App;
Explanation: The provider shares one object containing the user's name, role, and city. The Profile component reads the object through useContext and displays its properties.
10. Using useContext with useState
Context itself does not manage changing state. To make shared values dynamic, combine Context with useState or useReducer and provide the current state and update functions.
import { createContext, useContext, useState } from 'react';
const CountContext = createContext(null);
function CounterDisplay() {
const { count } = useContext(CountContext);
return <h2>Count: {count}</h2>;
}
function CounterButtons() {
const { setCount } = useContext(CountContext);
return (
<div>
<button onClick={() => setCount(previous => previous + 1)}>
Increment
</button>
<button onClick={() => setCount(previous => previous - 1)}>
Decrement
</button>
</div>
);
}
function App() {
const [count, setCount] = useState(0);
return (
<CountContext.Provider value={{ count, setCount }}>
<CounterDisplay />
<CounterButtons />
</CountContext.Provider>
);
}
export default App;
Explanation: CounterDisplay and CounterButtons read the same shared Context. The buttons update state owned by App, and React supplies the new Context value to its consumers.
11. Example: Authentication Context
Authentication information, such as the current user and login status, can be shared through Context. This is a simplified learning example; real authentication also requires secure server-side verification and appropriate session or token handling.
import { createContext, useContext, useState } from 'react';
const AuthContext = createContext(null);
function UserStatus() {
const { user, login, logout } = useContext(AuthContext);
return (
<div>
{user ? (
<>
<p>Logged in as: {user.name}</p>
<button onClick={logout}>Logout</button>
</>
) : (
<button onClick={login}>Login</button>
)}
</div>
);
}
function App() {
const [user, setUser] = useState(null);
function login() {
setUser({ name: 'Rahul' });
}
function logout() {
setUser(null);
}
return (
<AuthContext.Provider value={{ user, login, logout }}>
<h1>Authentication Example</h1>
<UserStatus />
</AuthContext.Provider>
);
}
export default App;
Explanation:
- The AuthContext provides the user information and login/logout functions.
- The user state is managed by App.
- UserStatus reads the Context using useContext.
- Clicking Login or Logout updates the shared state and changes the displayed UI.
12. Default Value in Context
The default value supplied to createContext() is used only when there is no matching provider above the component. It is a fallback, not a value that automatically changes.
import { createContext, useContext } from 'react';
const LanguageContext = createContext('English');
function LanguageDisplay() {
const language = useContext(LanguageContext);
return <p>Language: {language}</p>;
}
export default LanguageDisplay;
If LanguageDisplay is rendered without a matching provider, it displays English.
Important: If a provider exists and its value is explicitly set to undefined, React returns undefined rather than falling back to the default value.
13. Nested Context Providers
You can place multiple providers for the same Context at different levels. A consumer reads the value from the closest matching provider above it.
import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function ThemeLabel() {
const theme = useContext(ThemeContext);
return <p>Theme: {theme}</p>;
}
function App() {
return (
<ThemeContext.Provider value="dark">
<ThemeLabel />
<ThemeContext.Provider value="light">
<ThemeLabel />
</ThemeContext.Provider>
</ThemeContext.Provider>
);
}
export default App;
Expected output:
- The first ThemeLabel displays Theme: dark.
- The nested ThemeLabel displays Theme: light.
14. Rules for Using useContext
- Call useContext at the top level of a function component or custom Hook.
- Do not call useContext inside loops, conditions, or nested functions.
- Pass the Context object itself to useContext, not its Provider or Consumer property.
- Make sure the matching provider is above the component that reads the Context.
- Use the same Context object for both providing and reading the value.
- Use state or a reducer when the shared value needs to change over time.
- Choose a meaningful default value for situations where no provider exists.
15. Common Mistakes When Using useContext
| Mistake |
Why It Is a Problem |
Correct Approach |
| Forgetting the provider |
The component receives the Context default value |
Wrap the component with the matching provider |
| Using the wrong Context object |
The consumer cannot read the intended provider value |
Import and use the same Context object |
| Expecting Context to manage state automatically |
Context shares a value but does not itself update it |
Combine Context with useState or useReducer |
| Calling useContext conditionally |
Breaks the Rules of Hooks |
Call the Hook at the top level |
| Mutating a shared object |
React may not detect a changed reference |
Create a new object when updating shared data |
| Creating a new provider object unnecessarily |
Consumers may re-render because the value has a new identity |
Consider useMemo and useCallback when profiling shows a need |
| Using one large Context for unrelated data |
Many consumers may update when any part of the shared value changes |
Separate Contexts by responsibility where appropriate |
16. useContext vs. Props
| Feature |
Props |
useContext |
| Data passing |
Passed directly from parent to child |
Read from a matching provider above the component |
| Best suited for |
Explicit component inputs and direct relationships |
Values needed by many components across a subtree |
| Intermediate components |
May need to pass props further down |
Do not need to pass Context values through every level |
| Data source |
Parent component |
Closest matching Context provider |
| Typical example |
Passing a product name to a ProductCard |
Sharing theme or authenticated user information |
17. useContext vs. useState
| Feature |
useContext |
useState |
| Main purpose |
Read and subscribe to a Context value |
Declare and update local component state |
| Creates state? |
No, not by itself |
Yes |
| Shares values across components? |
Yes, through a provider |
Not directly; state can be passed through props or provided through Context |
| Common use |
Theme, language, user information |
Input value, counter, toggle |
18. Improving Context Performance
When a provider supplies a new value, React compares the previous and next values using Object.is. Components that consume the Context receive updates when the value changes. If a provider creates a new object or function on every render, consumers may re-render even when the underlying data has not meaningfully changed.
For example, the following provider creates a new object each time its component renders:
<UserContext.Provider value={{ user, updateUser }}>
<Profile />
</UserContext.Provider>
For cases where profiling shows unnecessary updates, useMemo and useCallback can help stabilize the provider value and functions.
import { createContext, useCallback, useMemo, useState } from 'react';
const UserContext = createContext(null);
function App() {
const [user, setUser] = useState({ name: 'Rahul' });
const updateUser = useCallback((name) => {
setUser({ name });
}, []);
const contextValue = useMemo(() => ({
user,
updateUser
}), [user, updateUser]);
return (
<UserContext.Provider value={contextValue}>
<Profile />
</UserContext.Provider>
);
}
Note: This is an optimization, not a requirement for every Context. Measure performance before adding unnecessary memoization.
19. Practical Project: Theme Switcher
Project objective: Build a theme switcher that shares the current theme with a child component using useContext.
Features:
- Share theme information with child components.
- Toggle between light and dark modes.
- Update the appearance of the content.
- Keep theme state in a parent component.
Code:
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext(null);
function Content() {
const { theme, toggleTheme } = useContext(ThemeContext);
const isDark = theme === 'dark';
return (
<div
style={{
background: isDark ? '#222' : '#fff',
color: isDark ? '#fff' : '#222',
padding: '20px'
}}
>
<h2>Current Theme: {theme}</h2>
<p>This content reads the theme through Context.</p>
<button onClick={toggleTheme}>Switch Theme</button>
</div>
);
}
function App() {
const [theme, setTheme] = useState('light');
function toggleTheme() {
setTheme(previous =>
previous === 'light' ? 'dark' : 'light'
);
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<Content />
</ThemeContext.Provider>
);
}
export default App;
Learning outcomes:
- Understand Context creation and provider usage.
- Read shared data using useContext.
- Combine useContext with useState.
- Share values and functions between components.
20. Practice Exercises
- Create a ThemeContext that switches between light and dark themes.
- Create a LanguageContext that shares a selected language with multiple components.
- Create a UserContext that displays a user's name and role.
- Build an authentication example with Login and Logout buttons.
- Share a shopping cart count between a header and a product component.
- Create nested providers and demonstrate how the closest provider determines the value.
- Combine useContext and useReducer to manage a shared list of tasks.
- Identify and fix a component that receives the default value because its provider is missing.
21. Interview Questions and Answers
Q1. What is the useContext Hook?
Answer: useContext is a React Hook that reads and subscribes to a Context value from the closest matching provider above a component.
Q2. What is prop drilling?
Answer: Prop drilling is passing props through intermediate components that do not need the data themselves, simply to reach a deeper child component.
Q3. What is createContext()?
Answer: createContext() creates a Context object that can provide shared values to components within a subtree.
Q4. What happens if a provider is missing?
Answer: The consumer receives the default value supplied to createContext().
Q5. Can useContext update state?
Answer: useContext reads a value. To make that value change, a provider typically uses useState, useReducer, or another state-management mechanism.
Q6. Can multiple components use the same Context?
Answer: Yes. Any descendant of the matching provider can read the Context using useContext.
Q7. What happens when a Context value changes?
Answer: React updates components that consume that Context when the provided value changes.
Q8. Can Context providers be nested?
Answer: Yes. A consumer reads the value from the closest matching provider above it.
Q9. What is the difference between Context and props?
Answer: Props explicitly pass data from parent to child. Context allows components to read shared data without manually passing it through every intermediate component.
Q10. Can useContext replace every state-management library?
Answer: No. Context is useful for sharing values across a component tree, but complex applications may require additional state-management, caching, or data-fetching solutions.
22. Quick Revision Checklist
- Understand the purpose and syntax of useContext.
- Know how to create a Context using createContext().
- Understand provider values and default values.
- Know how Context helps reduce prop drilling.
- Practice sharing data across nested components.
- Combine useContext with useState or useReducer for dynamic values.
- Understand nested providers and closest-provider behavior.
- Follow the Rules of Hooks.
- Know the difference between props, useState, and useContext.
- Understand how provider value changes affect consuming components.
23. Conclusion
The useContext Hook is an important React feature for sharing information across a component tree without passing props through every intermediate component. By learning Context creation, providers, default values, dynamic state, nested providers, and performance considerations, developers can build React applications with cleaner and more maintainable data flow.
Continue learning with the JustAcademy React JS Training Course or register for a React JS course demo.