Using useContext in React: a comprehensive guide

Michael Gold
9 min readMay 2, 2023

1. Introduction to React and useContext

React is a popular JavaScript library for building user interfaces, and it provides developers with a variety of tools to manage state and data flow within an application. One such tool is the useContext hook, which allows developers to easily share data across multiple components without the need for prop drilling. In this article, we'll explore the useContext hook in detail and learn how to use it effectively in a React application.

2. What is Context in React?

In React, “context” refers to a mechanism that allows data to be shared across multiple components without explicitly passing it through props. It provides a way to pass data through the component tree without having to pass props down manually at every level. Context is particularly useful when dealing with data that is considered “global” or needs to be accessible by many components within the application.

3. Why useContext is Important

Sharing Data Across Components

One of the main benefits of using context is the ability to share data across multiple components. This is especially useful when dealing with data that needs to be accessed by several components at different levels of the component tree.

Avoiding Prop Drilling

Prop drilling is the process of passing data from one component to another through props. This can become cumbersome and difficult to manage when data needs to be passed through multiple levels of components. Context eliminates the need for prop drilling by providing a centralized way to share data.

4. Creating a Context in React

To create a context in React, we use the React.createContext method. This method returns a context object that can be used to provide and consume values within the component tree.

5. Using the useContext Hook

Accessing Context Values

The useContext hook allows us to access the value of a context within a functional component. By passing the context object to the useContext hook, we can retrieve the current value of the context.

Updating Context Values

To update the value of a context, we use the context provider. The provider allows us to define the value that should be made available to all components that consume the context.

6. Practical Example: Theme Switcher

Creating the Theme Context

Let’s create a practical example of a theme switcher using context. First, we’ll create a context object to store the current theme.

import React, { useContext, useState } from 'react';

// Define the shape of the context data using a TypeScript interface
interface ThemeContextData {
theme: string;
toggleTheme: () => void;
}

// Create the context with an initial value and the TypeScript interface
const ThemeContext = React.createContext<ThemeContextData>({
theme: 'light',
toggleTheme: () => {},
});

Adding a Theme Provider

Then we’ll add a ThemeProvider component that will provide the context value to the child components.

export interface IThemeProviderProps {
children: React.ReactNode;
}

// Create a ThemeProvider component to provide the context value to child components
export const ThemeProvider: React.FC<IThemeProviderProps> = ({ children } ) => {
const [theme, setTheme] = useState("light");

// Function to toggle the theme between light and dark
const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light"));
};

return (
<ThemeContext.Provider value={{ theme, toggleTheme }} >
<div className = {theme == 'light' ? 'lightTheme': 'darkTheme'}>
{children}
</div>
</ThemeContext.Provider>

);
};

Implementing a Theme Switcher Child Component

Next, we’ll create a theme switcher component that allows users to toggle between light and dark themes with a button press. This component will consume the ThemeContext through useContext.

// Create a ThemeSwitcher component that consumes the context value
export const ThemeSwitcher: React.FC = () => {
// Use the useContext hook to access the context value
const { theme, toggleTheme } = useContext(ThemeContext);

return (
<div >
<p className='themeTitleText'>Current theme: {theme}</p>
<button onClick={toggleTheme} className={theme + 'ButtonTheme'} >Toggle Theme</button>
</div>
);
};

Wrapping with the ThemeProvider

In all React applications we have a functional component named App that serves as the root component of the React application. The App component is responsible for rendering the ThemeSwitcher component, which is a child component that allows users to switch between different themes (e.g., light and dark themes).

The ThemeProvider component is a context provider that is used to make the theme-related data and functionality available to all of its child components. The ThemeProvider component is created using the React context API and is responsible for managing the theme state and providing a function to toggle the theme.

The ThemeSwitcher component is wrapped inside the ThemeProvider component, which means that the ThemeSwitcher component (and any other child components inside the ThemeProvider) will have access to the theme context. This access is achieved using the useContext hook.

The code below shows how we use the ThemeProvider to wrap our child component, ThemeSwitcher. All this is done in the App.tsx component of our application.

// Use the ThemeProvider to wrap the ThemeSwitcher component
const App: React.FC = () => {
return (
<div className='ThemeSwitcherContainer'>
<ThemeProvider>
<ThemeSwitcher />
</ThemeProvider>
</div>
);
};

By the way, if you’re curious, here’s the accompanying CSS that helps illustrate the two themes we toggle between: light and dark. This CSS includes a set of styles for the main container of the app, as well as a pair of light and dark themes for each button component:

.lightTheme {
background-color: #fff;
color: #000;
height: 350px;
margin-left: 50px;
margin-right: 50px;
display: flex;
flex-direction: row;
justify-content: center;
}

.darkTheme {
background-color: #000;
color: #fff;
height: 350px;
margin-left: 50px;
margin-right: 50px;
display: flex;
flex-direction: row;
justify-content: center;
justify-content: center;
}

.darkButtonTheme {
background-color: #000;
color: #fff;
border-radius: 5px;
border: 1px solid #fff;
display: flex;
flex-direction: row;
justify-content: center;

}

.darkButtonTheme:hover {
background-color: #ddd; /* Change the background color on hover */
color: #000;
cursor: pointer; /* Change the cursor to a pointer on hover */
}


.lightButtonTheme {
background-color: #fff;
color: #000;
border-radius: 5px;
border: 1px solid #000;
display: flex;
flex-direction: row;
justify-content: center;
align-content: center;
}

.lightButtonTheme:hover {
background-color: #ddd; /* Change the background color on hover */
cursor: pointer; /* Change the cursor to a pointer on hover */
}

.themeTitleText {
text-align: center;
margin-top: 50px;
margin-bottom: 50px;
font-size: 60px;
}
Output for Light Theme
Output for Dark Text

7. useContext vs. Redux

useContext and Redux are both popular tools for managing state in React applications, but they serve different purposes and have different use cases. Let's explore the differences between useContext and Redux, as well as when to use each.

useContext

useContext is a React hook that provides a way to share data (context) across multiple components without explicitly passing it through props. It is part of the React Context API, which is built into the React library.

  • Simplicity: useContext is relatively simple to use and understand. It is suitable for small to medium-sized applications where state management requirements are not overly complex.
  • Built-in: useContext is part of the React library, so there is no need to install additional dependencies.
  • Local State Management: useContext is ideal for managing state that is specific to a particular part of the application, such as theme preferences or user authentication status.
  • Limitations: useContext lacks some advanced features, such as middleware support and performance optimizations. It may not be suitable for applications with complex state management needs or frequent state updates.

Redux

Redux is a standalone state management library that can be used with any JavaScript application, including React. It provides a centralized store for managing global state and follows a strict unidirectional data flow.

  • Scalability: Redux is designed to handle complex state management requirements and is suitable for large applications with multiple state updates and interactions.
  • Predictability: Redux follows strict rules for state updates, making it easier to reason about and debug state changes. It enforces immutability, ensuring that state updates are predictable and consistent.
  • Middleware Support: Redux allows the use of middleware to handle side effects, such as asynchronous actions and API calls. Middleware can also be used for logging, error handling, and more.
  • DevTools: Redux provides powerful developer tools that enable time-travel debugging, state inspection, and action logging. These tools can significantly improve the development experience.
  • Boilerplate: Redux requires more boilerplate code compared to useContext. Developers need to define actions, action creators, reducers, and store configurations.

When to Use Each

  • useContext: Consider using useContext for simple state management needs or for sharing state within a specific part of the application. It is a good choice for small to medium-sized applications where state management requirements are straightforward.
  • Redux: Consider using Redux for complex state management needs, especially in large applications with frequent state updates and interactions. Redux is also a good choice when you need advanced features like middleware, time-travel debugging, and a centralized store for global state.

In summary, while useContext provides a simple and convenient way to share state within a React application, Redux offers a more robust and scalable solution for managing complex global state. The choice between useContext and Redux ultimately depends on the specific requirements and scale of the application.

8. Common Use Cases for useContext

User Authentication

Context can be used to manage user authentication status and provide user-specific data to components that need it.

Language Localization

Context is useful for implementing language localization by providing translated strings to components based on the user’s language preference.

Theme Customization

Context can be used to manage and apply custom themes to an application, allowing users to personalize the appearance of the app.

9. Best Practices for Using useContext

When using useContext, it’s important to follow best practices to ensure maintainable and efficient code. These include avoiding unnecessary re-renders, structuring context providers, and using multiple contexts when needed.

10. Limitations of useContext

One of the limitations of useContext is that it does not have built-in performance optimizations. When the value provided by a context provider changes, all components that consume the context will re-render, regardless of whether the change is relevant to them. This can lead to unnecessary re-renders and negatively impact performance, especially in large applications with frequent state updates. Developers need to implement their own performance optimizations, such as using React.memo to prevent unnecessary re-renders of child components.

11. Conclusion

The useContext hook in React provides a convenient way to share data across components without prop drilling. By understanding how to create and use context, developers can build more scalable and maintainable applications. Whether it’s user authentication, language localization, or theme customization, useContext is a valuable tool in a React developer’s toolkit.

Frequently Asked Questions (FAQs)

  1. What is the difference between useContext and Redux?

useContext is a React hook that allows sharing data across components, while Redux is a state management library that provides a centralized store for managing global state.

2. Can I use multiple contexts in a single component?

Yes, you can use multiple contexts in a single component by calling the useContext hook multiple times with different context objects.

3. How do I update the value of a context

To update the value of a context, you need to use the context provider and set the new value using the value prop.

4. Is useContext a replacement for state management libraries like Redux?

useContext is not a complete replacement for state management libraries like Redux. While it provides a simple way to share data, it lacks some of the advanced features and optimizations provided by Redux.

5. When should I use useContext instead of passing props?

useContext is best used when you need to share data across multiple components at different levels of the component tree, especially when passing props becomes cumbersome and difficult to manage.

Thanks for reading and feel free to send feedback to me on the article at mike@microgold.com

Note: If you want to learn more about React and TypeScript in a fun way, checkout, Creating a Wordle Game in React and TypeScript.

Fun with React and TypeScript
A fun way to learn React, TypeScript and React Hooks

--

--