React useContext: The Best Way to Manage State in React

March 22, 2023
copycat
React.js

The Fastest Way to Build React UI

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

How React useContext Helps You

Imagine you’re building an e-commerce website that allows users to purchase products from multiple vendors. Each vendor has its own inventory and shipping policies, and you need to keep track of all this information to provide accurate pricing and delivery estimates to users.

Using the traditional approach of passing state down through props can quickly become unwieldy and difficult to manage. However, with React useContext, you can create a vendor context that stores all the necessary information and pass it down to child components as needed. This can significantly simplify your code and make tracking all the data you need to manage easier.

Whether you’re working on a small app or a large-scale project, useContext can help you create clean, reusable code that’s easy to maintain. We’ll build a simple to-do app to demonstrate using useContext in a real-world scenario and highlight some best practices. But before diving into that, we first need to understand what React useContext is and how it works.

A Beginner’s Guide to React useContext Hook

https://dmitripavlutin.com/react-context-and-usecontext/

React useContext hook allows sharing data across a React component tree without passing props down through multiple levels. This can be especially useful when working with complex components requiring access to much data.

What is useContext in React?

The useContext hook is a built-in hook in React that allows you to consume data from a context provider. A context provider is a component that provides data to all of its children via a context object. The React useContext hook allows you to access this context object and retrieve the necessary data.

How it works

To use the useContext hook, you must define a context object in your React component. This context object can be a plain JavaScript object or a custom class that provides data to your component tree. Once you have defined your context object, you can wrap your component tree in a context provider component that provides the data to all its children.

You can use the useContext hook inside your component to retrieve the data from the context object. The useContext hook takes the context object as an argument and returns the current value of the context object. This allows you to retrieve the data you need and use it in your component.

Here’s a React useContext example with step-by-step instructions for how to use it in a React component:

1: Define a context object

import React from 'react';

const MyContext = React.createContext();

2: Create a provider component that wraps your component tree and provides the context object

import React from 'react';

const MyContext = React.createContext();

function MyProvider(props) {
  const myData = { /* your data goes here */ };

  return (
    <MyContext.Provider value={myData}>
      {props.children}
    </MyContext.Provider>
  );
}

3: Use the useContext hook to retrieve the data from the context object

import React, { useContext } from 'react';

const MyContext = React.createContext();

function MyComponent() {
  const myData = useContext(MyContext);

  // use myData here

  return (
    // your component JSX goes here
  );
}

In this React useContext example, we first define a context object using React.createContext(). We then create a provider component, MyProvider, that wraps our component tree and provides the context data using the value prop.

Inside our component, MyComponent, we use the useContext hook to retrieve the context data from the MyContext object. We can then use the myData variable to access the context data inside our component.

Note that the useContext hook can only be used inside a function component or a custom hook. If you’re using a class component, you can still access the context data using the Context.Consumer component.

For visual aid, use this video to learn useContext in 13 minutes:

What is context vs useContext?

Context is a feature in React that allows data to be passed down the component tree without having to pass props explicitly at every level. It’s a way to share data between components that are not in a parent-child relationship. Context is created using the createContext() method, which returns an object with a Provider component and a Consumer component.

On the other hand, useContext is a React Hook that provides a way to consume data from a Provider component in the context API. It is a more convenient and efficient way to access data from the Provider component than using the Consumer component. By using the useContext Hook, a component can subscribe to changes in the context and access the context value without having to wrap itself in a Consumer component.

You can go more in depth with this video:

When should I Use useContext React?

You should use useContext in React when passing data from a parent component to a deep-level child component without passing it down through all intermediate components. useContext is a powerful tool that can help you manage state in your React applications more efficiently and elegantly. It can also make your code easier to read and maintain by reducing the number of props that need to be passed down through the component tree.

An Example of Using useContext to manage state in a React component:

Let’s consider a React useContext example where we want to manage the theme of a website. We can create a ThemeContext object that stores the current theme value and a function to update it. We can then wrap our component tree in a ThemeProvider component that provides the theme data to all its children.

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

const ThemeContext = React.createContext();

function ThemeProvider(props) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {props.children}
    </ThemeContext.Provider>
  );
}

function App() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <div className={`app ${theme}`}>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
}

export default App;

The example above defines the ThemeContext object and the ThemeProvider component that manages the theme state. We then use the React useContext hook inside our App component to retrieve the theme data from the context object and update it using the toggleTheme function.

In the next section, we’ll explore the benefits of using useContext for state management in React and how it compares to other state management solutions like Redux.

Using useContext for State Management in React

React’s useContext hook is not limited to accessing data from a context object, and it can also be used for state management in a React application. Using the useContext hook to manage the state, you can avoid the complexity and boilerplate code associated with other state management solutions like Redux.

Benefits and Downsides of Using useContext for State Management in React

https://www.techomoro.com/how-to-use-context-api-in-a-next-js-app/

While the useContext hook can be a powerful tool for managing state in a React application, it also has its benefits and downsides.

Benefits:

  1. Reduced Boilerplate Code: One of the primary benefits of using the useContext hook for state management in React is that it reduces the amount of code you need to write. This is because you don’t need to create separate actions, reducers, and store objects as you would in Redux.
  2. Simplified Data Flow: Using useContext for state management can simplify the data flow in your application. Since the state is managed at the top level of your component tree, any component that needs access to the state can use the useContext hook to retrieve the state data and update it as required.
  3. Easy to Share State: Since the state is managed at the top level of your component tree, it’s easy to share state across multiple components in your application.

Downsides:

  1. Not Suitable for Large Applications: While useContext can help manage state in small to medium-sized applications, it may not be suitable for large applications. As your application grows in size and complexity, managing state using useContext may become unwieldy and difficult to maintain.
  2. Limited Debugging Tools: Unlike Redux, which provides many debugging tools, useContext does not have many built-in debugging tools. This can make it more difficult to debug issues related to state management in your application.
  3. Difficult to Scale: While useContext helps manage state in small to medium-sized applications, it may be challenging to scale as your application grows. As your application becomes more complex, you may need more powerful state management tools like Redux to manage your state effectively.

Table: Benefits and Downsides of Using useContext for State Management in React

BenefitsDownsides
Reduced Boilerplate CodeNot Suitable for Large Applications
Simplified Data FlowLimited Debugging Tools
Easy to Share StateDifficult to Scale

Despite its downsides, the useContext hook remains a valuable tool for state management in React applications. It’s important to consider your application’s needs carefully before deciding whether to use useContext or another state management solution like Redux.

Setting up a context provider and consumer components:

To use the useContext hook for state management in React, you must first set up context provider and consumer components. The provider component is responsible for defining your state and state update functions and making them available to all its children via the context object. The consumer component is responsible for retrieving the state data, updating functions from the context object, and using them in your components. Here’s how to do it:

  1. Define the Context: The first step is to define the context object that will hold your state. You can do this using the createContext() function from the React library. For example:
import React from 'react';

const MyContext = React.createContext();

  1. Create a Provider Component: Next, you must create a provider component to make the state available to all child components. This component should wrap all the child components that need access to the state. The provider component should have a value prop that contains the initial state. For example:
import React, { useState } from 'react';

const MyContext = React.createContext();

const MyProvider = ({ children }) => {
  const [state, setState] = useState({});

  return (
    <MyContext.Provider value={[state, setState]}>
      {children}
    </MyContext.Provider>
  );
};

In the example above, we’ve created a provider component called “MyProvider” that uses the useState hook to manage the state. The state is initialized as an empty object, but you can initialize it to any value you want.

  1. Create a Consumer Component: Finally, you need to create a consumer component that will retrieve the state from the provider component. You can do this by using the useContext hook and passing in the context object. For example:
import React, { useContext } from 'react';

const MyContext = React.createContext();

const MyConsumer = () => {
  const [state, setState] = useContext(MyContext);

  return (
    <div>
      <h1>My State:</h1>
      <pre>{JSON.stringify(state, null, 2)}</pre>
    </div>
  );
};

In the example above, we’ve created a consumer component called “MyConsumer” that retrieves the state using the useContext hook. The state is then displayed on the page as a JSON object. Note that we’re using array destructuring to assign the state and setState values from the context object.

Using useContext to Manage Global State in a React Application:

When building larger-scale React applications, managing state across multiple components can become difficult. One solution is to use the useContext hook to manage global state. Here’s how you can use the useContext hook to manage global state in a React application:

  1. Define the Context: First, define a context object that will hold your global state. This can be done using the createContext() function from the React library. For example:
import React from 'react';

const GlobalStateContext = React.createContext();

  1. Create a Provider Component: Next, create a provider component that will wrap all the child components that need access to the global state. The provider component should have a value prop that contains the initial state. For example:
import React, { useState } from 'react';

const GlobalStateContext = React.createContext();

const GlobalStateProvider = ({ children }) => {
  const [state, setState] = useState({});

  return (
    <GlobalStateContext.Provider value={[state, setState]}>
      {children}
    </GlobalStateContext.Provider>
  );
};


In the example above, we’ve created a provider component called “GlobalStateProvider” that uses the useState hook to manage the global state. The state is initialized as an empty object, but you can initialize it to any value you want.

  1. Create Child Components: Now, you can create child components that will consume the global state. To do this, you can use the useContext hook and pass in the context object. For example:
import React, { useContext } from 'react';

const GlobalStateContext = React.createContext();

const ChildComponent = () => {
  const [state, setState] = useContext(GlobalStateContext);

  const handleClick = () => {
    setState(prevState => ({ ...prevState, message: 'Hello, world!' }));
  };

  return (
    <div>
      <p>{state.message}</p>
      <button onClick={handleClick}>Update State</button>
    </div>
  );
};


In the example above, we’ve created a child component called “ChildComponent” that consumes the global state using the useContext hook. We’ve also added a button that updates the state when clicked.

Using the useContext hook to manage global state, you can easily share state across multiple components in your React application. However, it’s essential to remember that managing state with useContext alone might become unwieldy as your application grows. In such cases, consider using a state management library like Redux or MobX.

When to Use useContext and When to Use Redux in React

When it comes to managing state in React applications, two popular approaches are to use the useContext hook or to use a dedicated state management library like Redux.

Pros and Cons of useContext

StrengthsWeaknesses
State managementSimple and intuitive API for managing stateNot suitable for complex state management
Boilerplate CodeLess code required to set up compared to ReduxMay cause unnecessary re-renders if not used carefully
ScalabilityGood for small to medium-scale applicationsMay lead to prop drilling in large-scale applications
PerformanceFast and lightweightCan be slower and less performant than Redux for large-scale applications
Learning CurveEasy to learn and useLimited flexibility and functionality compared to Redux

Pros and Cons of Redux

StrengthsWeaknesses
State managementProvides a powerful and flexible solution for complex state managementRequires more boilerplate code to set up compared to the useContext hook
Boilerplate CodeAllows for modular code and a separation of concernsSteep learning curve and requires additional setup and configuration
ScalabilityScales well for large-scale applicationsOverkill for small to medium-scale applications
PerformanceOptimized for performance and can handle large-scale applicationsCan be more difficult to debug and optimize compared to the useContext hook
Learning CurveOnce learned, can be a powerful and flexible toolSteep learning curve and may not be necessary for simpler applications

Here’s a comparison of the two approaches:

Comparing and Contrasting the useContext Hook with Redux:

The useContext hook and Redux both provide solutions for managing state in React applications. However, there are some key differences between the two approaches.

useContext HookRedux
LibraryPart of ReactStandalone library
Boilerplate CodeLessMore
PerformanceMay cause unnecessary re-renders if not used carefullyOptimized for performance
ScalabilityGood for small to medium-scale applicationsBetter for large-scale applications
State ManagementGood for simpler state management requirementsGood for complex state management requirements
Ease of useSimple to useMore difficult to set up and use
Learning CurveEasySteeper

Is useContext better than Redux?

The useContext hook is a simpler and lightweight alternative to Redux for state management in React. It can be a good choice for more straightforward state management requirements in small to medium-scale applications, and it requires less boilerplate code and is easier to learn and use than Redux.

On the other hand, Redux provides a more robust and flexible solution for complex state management requirements in larger-scale applications. It allows for modular code and separation of concerns, making it easier to maintain and scale. However, it requires more boilerplate code and has a steeper learning curve than the useContext hook.

Ultimately, the choice between useContext and Redux depends on the specific needs of your application. The useContext hook is a good choice for simpler state management requirements in small to medium-scale applications. At the same time, Redux provides a more powerful and flexible state management solution for complex state management requirements in larger-scale applications. Both approaches have their strengths and weaknesses, and their choice ultimately depends.

Guidance on When to Use useContext versus Redux in a React Application:

When should you use the useContext hook versus Redux in a React application? Here are some guidelines to help you decide:

  • If you’re building a small to medium-scale application and the state management requirements are relatively simple, consider using the useContext hook.
  • If you’re making a larger-scale application and the state management requirements are more complex, consider using Redux.
  • If you need help deciding which approach to use, start with the useContext hook and then switch to Redux if you find the useContext hook becoming unwieldy as your application grows.
  • Remember that there’s no one-size-fits-all solution, so you should always choose the best approach for your specific use case.

Building a Simple Todo App with useContext in React

https://www.microsoft.com/en-au/microsoft-365/microsoft-to-do-list-app

This section will build a simple to-do app using the useContext hook for state management in React. We will also highlight best practices for using useContext in a real-world application.

Walking through the Process of Building a Simple Todo App

In this React useContext example, we will create a simple todo app that allows users to add and delete tasks. We will use the useContext hook to manage the state of our app.

Step 1: Setting up the project

We will start by creating a new React project using the create-react-app command:

npx create-react-app todo-app


Step 2: Creating the TodoContext

Next, we will create a new context for our todo app using the createContext() function from React:

import { createContext } from 'react';

const TodoContext = createContext();
export default TodoContext;


Step 3: Creating the TodoProvider

Now that we have created our context, we will create a provider component that will provide the state and functions to manage the state to our app:

import React, { useState } from 'react';
import TodoContext from './TodoContext';

const TodoProvider = ({ children }) => {
  const [tasks, setTasks] = useState([]);

  const addTask = (newTask) => {
    setTasks([...tasks, newTask]);
  };

  const deleteTask = (taskId) => {
    setTasks(tasks.filter((task) => task.id !== taskId));
  };

  return (
    <TodoContext.Provider value={{ tasks, addTask, deleteTask }}>
      {children}
    </TodoContext.Provider>
  );
};

export default TodoProvider;


In the above code, we have created a provider component called TodoProvider that has a state called tasks and two functions to manage the state called addTask and deleteTask. We have also wrapped our children components with the TodoContext.Provider component and passed in the tasks, addTask, and deleteTask as the value prop.

Step 4: Creating the TodoList component

Next, we will create a component called TodoList that will display the tasks to the user:

import React, { useContext } from 'react';
import TodoContext from './TodoContext';

const TodoList = () => {
  const { tasks, deleteTask } = useContext(TodoContext);

  return (
    <ul>
      {tasks.map((task) => (
        <li key={task.id}>
          {task.taskName}
          <button onClick={() => deleteTask(task.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
};

export default TodoList;


In the above code, we have used the useContext hook to access the tasks state and the deleteTask function from the TodoContext. We have also created a list of tasks using the map function and provided a delete button for each task.

Step 5: Creating the TodoForm component

Finally, we will create a component called TodoForm that will allow the user to add new tasks:

import React, { useState, useContext } from 'react';
import TodoContext from './TodoContext';

const TodoForm = () => {
  const [newTask, setNewTask] = useState('');
  const { addTask } = useContext(TodoContext);

  const handleSubmit = (e) => {
    e.preventDefault();
    addTask({ id: new Date().getTime(), taskName: newTask });
    setNewTask('');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={newTask}
        onChange={(e) => setNewTask(e.target.value)}
      />
      <button type="submit">Add Task</button>
    </form>
  );
};

export default TodoForm;


Example explanation

In the above code, we created a functional component called TodoForm which uses the useState and useContext hooks from the React library. The useState hook manages the state of the input field for adding new tasks, and the useContext hook acceses the addTask function from the TodoContext.

Inside the TodoForm component, we defined a handleSubmit function that is called when the user submits the form. This function prevents the default form submission behavior, creates a new task object with a unique id and the taskName input from the user, and adds the new task to the existing list of tasks using the addTask function from the TodoContext.

The TodoForm component returns a form element with an input field and a button. The input field is bound to the newTask state variable using the useState hook, and the input field’s value is updated whenever the user types a new task name. When the user clicks the “Add Task” button, the handleSubmit function is called, and the new task is added to the list of tasks.

Now that we’ve walked through building a simple todo app using the useContext hook for state management, it’s important to highlight some best practices for using useContext in a real-world application. While this example is pretty straightforward, real-world applications can be much more complex, and using useContext to promote clean code and maintainability is important. By following some best practices, you can ensure that your useContext implementation is efficient and effective.

Highlighting best practices for using useContext in a real-world application

When using useContext for state management in a real-world application, following some best practices is important to ensure your code is efficient, maintainable, and easy to understand. Here are some key best practices to keep in mind:

1. Use multiple contexts to manage different types of state

If your application has multiple types of states, it’s a good idea to use multiple contexts to manage every kind of state separately. This helps to keep your code organized and makes it easier to understand the purpose of each context. For example, you could have one context for managing user data, another for managing product data, and so on.

// UserContext.js
import React from 'react';

const UserContext = React.createContext();

export default UserContext;

// ProductContext.js
import React from 'react';

const ProductContext = React.createContext();

export default ProductContext;


2. Use the useContext hook sparingly

While the React useContext hook is a powerful tool, it’s important to use it sparingly to avoid cluttering your code with unnecessary hooks. Instead, consider using the useContext hook only for states that must be shared across multiple components.

// Good example
import React, { useContext } from 'react';
import UserContext from './UserContext';

const UserAvatar = () => {
  const { user } = useContext(UserContext);

  return <img src={user.avatarUrl} alt="User Avatar" />;
}

export default UserAvatar;

// Bad example
import React, { useContext } from 'react';
import UserContext from './UserContext';
import ProductContext from './ProductContext';
import CartContext from './CartContext';
import ...

const MyComponent = () => {
  const { user } = useContext(UserContext);
  const { products } = useContext(ProductContext);
  const { cartItems } = useContext(CartContext);
  ...

  return <div>...</div>;
}

export default MyComponent;

In the good example code snippet above, we have a UserAvatar component that only uses the UserContext to access the user’s avatarUrl. This component is a good candidate for using the useContext hook as it only requires one context. However, in the bad example code, we have a MyComponent that uses multiple contexts, including UserContext, ProductContext, CartContext, and potentially more. This can lead to a cluttered codebase and negatively impact the application’s performance. It’s better only to use the useContext hook when it’s necessary to prevent cluttering the code and improve performance.

3. Define a separate file for each context:

Creating a separate file for each context you use in your application is good practice, and this keeps your code organized and makes it easier to maintain. For example, let’s say you have a user context used throughout your application. Create a UserContext.js file and export your user context from there.

// UserContext.js

import { createContext } from 'react';

const UserContext = createContext();

export default UserContext;


4. Use a default value for your context:

It’s a best practice to always provide a default value for your context. This ensures that your application doesn’t break if a component tries to use the context before it’s been initialized. For example, if you have a ThemeContext that provides a theme for your application, you could set the default theme to “light”.

// ThemeContext.js

import { createContext } from 'react';

const ThemeContext = createContext('light');

export default ThemeContext;


5. Keep context providers as high in the component tree as possible:

The higher up in the component tree that you define your context provider, the easier it is to manage your application’s state. For example, if you have a UserContext that provides information about the currently logged in user, you could define the context provider in your top-level App component.

// App.js

import React from 'react';
import UserContext from './UserContext';
import HomePage from './HomePage';

const App = () => {
  const user = { name: 'John Doe', email: 'john@example.com' };

  return (
    <UserContext.Provider value={user}>
      <HomePage />
    </UserContext.Provider>
  );
};

export default App;


6. Avoid deeply nested contexts

Avoid nesting too many contexts within each other as it can make your code harder to read and understand. If you find yourself needing to nest contexts, consider refactoring your code to use a different approach.

For example, if you have a ProductContext that provides information about the products in your application, you could define the context provider in your top-level App component instead of creating a separate context provider in each child component.

// App.js

import React from 'react';
import ProductContext from './ProductContext';
import HomePage from './HomePage';

const products = [
  { id: 1, name: 'Product 1', price: 10.99 },
  { id: 2, name: 'Product 2', price: 19.99 },


The Future of useContext in React

https://www.vidyard.com/blog/role-of-video-in-the-future-of-work/

The React useContext hook has become an essential tool for developers to manage the state of their applications. As React continues to evolve, it’s important to keep an eye on the future of this hook. React’s official documentation suggests that the useContext hook is not going anywhere and will continue to be a core part of React. However, there are discussions within the React community about potential improvements and updates to the useContext hook, such as support for asynchronous data fetching and error handling.

As the React team releases updates and improvements, staying up-to-date with best practices for using the useContext hook in your applications is crucial. By following best practices and avoiding common mistakes, you can ensure your React code is clean, efficient, and easy to maintain. Additionally, keeping an eye on the future of React and the useContext hook can help you stay ahead of the curve and be prepared for any changes or updates that come your way.

Stay Ahead of the Curve with React useContext

As React continues to evolve and improve, useContext will likely become an even more integral part of building web applications. So, it’s worth learning and mastering the useContext hook to stay ahead of the curve in the ever-changing world of web development. With the proper use of useContext, you can easily create complex React applications and provide seamless user experiences. So, what are you waiting for? Dive in and start exploring the world of useContext in React!

Related Articles

  • React.js

    All You Need to Know About Typescript Function Type

    Introduction Functions are the building blocks of an application. They're a piece of code wrapped in a block to be reusable as a whole instead of repeating the piece of code over and over again when they're needed. In this…

    December 20, 2022
  • React.js

    Your Introductory Guide to React Intl

    Introduction In our attempt to build web applications optimized for globalization and accessibility, internationalization or i18n and localization or l10n are very important. Eliminating user obstacles to localizing or deploying an application globally is the goal of internationalization. The use…

    September 14, 2022

Convert Figma To React

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Convert Design to Code