React Context: The Detailed Guide

React Context: The Detailed Guide

Dead Simple Chat Team

Table of Contents

Dead Simple Chat offers a powerful JavaScript Chat API that you can use to add chat into any React or Web application in minutes. Dead Simple Chat is highly customizable and chat be used for any chat use case.

Usually when you want to pass data from the parent component to the child components then you use props.

But if the data has to be passed deeply from multiple components, when multiple components need the same data then it becomes cumbersome to use props to pass the data in the component tree.

Context can be used as an alternative to passing data using props. Context lets the parent component pass data to the entire tree of child components below it.

Thus greatly reducing the task of manually passing the props to all the child components that need the same data.

Let's look at a quick example using React Context.

Javascript Chat API and SDK | DeadSimpleChat
Easily add chat to your website or app within seconds. Pre-built chat with API and Javascript SDK | DeadSimpleChat.

Quick React Context Example

We will create a very basic React application using React Context, to pass the theme value down the component tree.

The app will allow the users to toggle between light and dark themes

  1. Create a ThemeProvider.jsx file to store the logic of our ThemeContext.
import { createContext, useState } from 'react';

export const ThemeContext = createContext();

const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');

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

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

export default ThemeProvider;

We have used the createContext() method to create a context named ThemeContext

Then we create a ThemeProvider and in the ThemeProvider method we will add the state and methods that we want to pass down to all the child components.

In the return method, we are exposing theme state variable and toggleTheme method to all the child components using the prop value={{ theme, toggleTheme }}

The theme state and the  toggleTheme  variable will be available to all the child components.

2. Import the ThemeProvider into your App.js and wrap the ThemeProvider around your root component. This allows all the child components to access the Context.

import React from 'react';
import ThemeProvider from './ThemeProvider';
import MainComponent from './MainComponent';

function App() {
  return (
    <ThemeProvider>
      <MainComponent />
    </ThemeProvider>
  );
}

export default App;

3. Now we will create the MainComponent.js file to consume the Context

import { useContext } from 'react';
import { ThemeContext } from './ThemeProvider';

const MainComponent = () => {
  const { theme, toggleTheme } = useContext(ThemeContext);

  const themeStyles = {
    backgroundColor: theme === 'light' ? '#fff' : '#333',
    color: theme === 'light' ? '#333' : '#fff',
  };

  return (
    <div style={themeStyles}>
      <h1>Main Component</h1>
      <p>Current Theme: {theme}</p>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
};

export default MainComponent;

In the above code, we are using the useContext hook to fetch the theme state variable and the toggleTheme method from the context.

useContext

The useContext method is used to use the context in the child components. The useContext method accepts the context that the child component wants to use and we can extract the methods and variables exposed by the context.

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

More Examples Using React Context

Let's look at some more examples using React Context and where it makes sense to use React Context in your application.

Using React Context with React Toastify

React Toastify is a library that is used to display toast notifications in your application.

If you want to learn a detailed tutorial on React Toastify, you can refer to our React Toastify Complete Guide blog post.

For now, we will create a basic app using React Toastify, and our goal is to allow the application to trigger toast notifications from any component.

We could create a method to trigger a toast notification and pass it down to child components, but it would become cumbersome as the component tree grows with lots of child components.

To solve this, we could create a Context and add the method to display the toast notification in the context, and the child components could use the context and call the method to show the toast notification.

  1. First, we will install the react-toastify package
npm install --save react-toastify

2. Next we will create a file called as ToastProvider.js this file will contain our ToastContext and ToastProvider

import { createContext } from "react";
import { toast } from "react-toastify";

export const ToastContext = createContext();

const ToastProvider = ({ children }) => {
  const showToast = (message, type) => {
    toast[type](message);
  };

  return (
    <ToastContext.Provider value={{ showToast }}>
      {children}
    </ToastContext.Provider>
  );
};

export default ToastProvider;

3. Wrap the ToastProvider around the Root component in your App.js file

import React from 'react';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import ToastProvider from './ToastProvider';
import MainComponent from './MainComponent';

function App() {
  return (
    <ToastProvider>
      <MainComponent />
      <ToastContainer />
    </ToastProvider>
  );
}

export default App;

3. Create the MainComponent.js file to store the MainComponent. In the MainComponent we will use the context to trigger the toast notification

import { useContext } from "react";
import ChildComponent from "./ChildComponent";
import { ToastContext } from "./ToastProvider";

const MainComponent = () => {
  const { showToast } = useContext(ToastContext);

  const handleShowToast = (type) => {
    const message = `This is a ${type} toast!`;
    showToast(message, type);
  };

  return (
    <div>
      <h1>Main Component</h1>
      <button onClick={() => handleShowToast("success")}>
        Show Success Toast
      </button>
      <button onClick={() => handleShowToast("error")}>Show Error Toast</button>
      <ChildComponent />
    </div>
  );
};

export default MainComponent;

4. Create a component called as the ChildComponent.js this will allow us to use the Context in the Child Component as well

import { useContext } from "react";
import AnotherChildComponent from "./AnotherChildComponent";

import { ToastContext } from "./ToastProvider";

function ChildComponent() {
  const { showToast } = useContext(ToastContext);

  function triggerToast() {
    showToast("Toast tiggered from child component", "success");
  }

  return (
    <div>
      <h1>Child Componenet</h1>
      <button
        onClick={() => {
          triggerToast();
        }}
      >
        Trigger Toast
      </button>

      <AnotherChildComponent />
    </div>
  );
}

export default ChildComponent;

5. We will more components which is the child of the "ChildComponent" called as AnotherChildComponent.js

import { useContext } from "react";

import { ToastContext } from "./ToastProvider";

function AnotherChildComponent() {
  const { showToast } = useContext(ToastContext);

  function triggerToast() {
    showToast("Toast tiggered from AnotherChild component", "success");
  }

  return (
    <div>
      <h1>Another Child Componenet</h1>
      <button
        onClick={() => {
          triggerToast();
        }}
      >
        Trigger Toast
      </button>
    </div>
  );
}

export default AnotherChildComponent;

Here is the complete code running the CodeSandbox:

Javascript Chat API and SDK | DeadSimpleChat
Easily add chat to your website or app within seconds. Pre-built chat with API and Javascript SDK | DeadSimpleChat.

Example with Multiple Contexts

Now let's look at an example that uses multiple React Contexts. We will create a multi-page React Application using react-router-dom and will create two Contexts, one will be the ThemeContext to manage the theme and another one will be the UserContext.

  1. Create a new react application using create-react-app
npx create-react-app multi-context-app
cd multi-context-app

2. Now install react-router-dom package, which will allow us to create routing in our application

npm install react-router-dom

3. Now we will create our ThemeContext. Create a folder called contexts inside the src folder and there create a file called ThemeContext.js

// src/contexts/ThemeContext.js

import { createContext, useState } from "react";

const ThemeContext = createContext();

const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState("light");

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

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

export { ThemeContext, ThemeProvider };

The ThemeContext is similar to the ThemeContext that we have created in our first example.

4. Next create another context called the UserContext, inside our contexts folder. The UserContext will hold the information about the logged-in user.

// src/contexts/UserContext.js

import { createContext, useState } from "react";

const UserContext = createContext();

const UserProvider = ({ children }) => {
  const [user, setUser] = useState(null);

  const login = (username, password) => {
    // perform login logic and set the user object
    setUser({ username, email: "user@example.com" });
  };

  const logout = () => {
    // perform logout logic and set the user object to null
    setUser(null);
  };

  return (
    <UserContext.Provider value={{ user, login, logout }}>
      {children}
    </UserContext.Provider>
  );
};

export { UserContext, UserProvider };

We will create some components that would use the context.

We will create Header component that will display the current the theme and allow the user to toggle it and we will also create a Profile component that will show the info about the currently logged-in user, and also allow them to logout.

You might have already guessed, the Header component will use the ThemeContext and the Profile component would use the UserContext.

5. Create a file called src/Header.js this will hold our header component, and add the following code to that file:

// src/Header.js

import { useContext } from "react";
import { ThemeContext } from "./contexts/ThemeContext";
import { Link } from "react-router-dom";

const Header = () => {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <header className={`header ${theme}`}>
      <button onClick={toggleTheme}>Toggle Theme</button>
      <nav>
        <ul style={{ listStyle: "none" }}>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/dashboard">Dashboard</Link>
          </li>
        </ul>
      </nav>
    </header>
  );
};

export default Header;

In the above code, we are using the ThemeContext and get the current theme information and set the current theme as the className for the header.

We are also calling the toggleTheme method provided to us by the ThemeContext when the user clicks the Toggle Theme button.

6. Create another file called as src/Profile.js this will hold the code for our Profile component.  

// src/Profile.js

import { useContext } from "react";
import { UserContext } from "./contexts/UserContext";

const Profile = () => {
  const { user, login, logout } = useContext(UserContext);

  const handleLogin = () => {
    login("myusername", "mypassword");
  };

  const handleLogout = () => {
    logout();
  };

  return (
    <div className="profile">
      {user ? (
        <>
          <h2>{user.username}</h2>
          <p>{user.email}</p>
          <button onClick={handleLogout}>Logout</button>
        </>
      ) : (
        <>
          <button onClick={handleLogin}>Login</button>
        </>
      )}
    </div>
  );
};

export default Profile;

The Profile component uses the UserContext. We have simplified the login and logout and will display the user info. In the profile component, we have also added the login and logout buttons that when pressed will call the Context methods to simulate login and logout.

7. Now let's setup routing in our application and create two pages Home and Dashboard that would use our Header and Profile components.

Create a file called as src/Home.js that would hold our HomePage.

// src/Home.js

import React from "react";
import Profile from "./Profile";

const Home = () => {
  return (
    <div className="home">
      <h2>Welcome to My App</h2>
      <p>This is the home page of my app</p>
      <Profile />
    </div>
  );
};

export default Home;

The Home The component is using the Profile component, to display the profile info.

Create another file called as Dashboard.js this will show the Dashboard route.

// src/Dashboard.js

import { useContext } from "react";
import { UserContext } from "./contexts/UserContext";

const Dashboard = () => {
  const { user, login, logout } = useContext(UserContext);

  function handleLogin() {
    login("username", "password");
  }

  return (
    <div className="dashboard">
      {user ? (
        <>
          <h2>Dashboard</h2>
          <p>Welcome to your dashboard, {user.username}</p>
        </>
      ) : (
        <>
          <p>You need to be logged in to access the dashboard</p>
          <button onClick={handleLogin}>Login</button>
        </>
      )}
    </div>
  );
};

export default Dashboard;

In the Dashboard component we are directly using the context to fetch the user info, and we have also added a login button to allow the user to log in, if the user is not logged in.

8. Update the App.js file to import the Providers, Header and Routers

// src/App.js

import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Header from "./Header";
import Home from "./Home";
import Dashboard from "./Dashboard";
import { ThemeProvider } from "./contexts/ThemeContext";
import { UserProvider } from "./contexts/UserContext";
import "./App.css";

const App = () => {
  return (
    <Router>
      <ThemeProvider>
        <UserProvider>
          <div className="App">
            <Header />
            <Routes>
              <Route exact path="/" element={<Home />} />
              <Route exact path="/dashboard" element={<Dashboard />} />
            </Routes>
          </div>
        </UserProvider>
      </ThemeProvider>
    </Router>
  );
};

export default App;

Here is the complete code running in Code Sandbox:

Now we have seen a comprehensive example using React Context, now lets discuss where it makes sense to use React Context and in what scenarios React Context should be avoided.

You can use Dead Simple Chat's JavaScript Chat API to easily add Chat in your React Applications.

Scenarios to use React Context

React context is particularly useful when there is a global application-wide state that needs to be shared with many components. Here are some of the scenarios where it makes sense to use React Context.

  1. Management of Global State: When you have a global state that needs to be shared among many components, like theme, customization, app configuration, user authentication etc. then React Context is a good choice.
  2. Preventing Prop Drilling: We have discussed and seen the examples as well. When you need to pass the data through multiple components it is very cumbersome to pass the data through props. Using React Context you can cleanly pass the data through your component tree resulting in maintainable code.
  3. Adding Reusablity to the Code: With React Context you can reusable logic into your application. As we have seen in the example below, we are using React Context to launch Toast to show notifications from anywhere in the component tree.

It is important to note that you should not overuse Context. As the official react documentation mentions that "Just because you need to pass some props several levels deep doesn't mean you should put that information into context".

First start by passing props downward and if it becomes too cumbersome then refactor it into using React Context.

Passing props downward is preferable because it adds a level of transparency in the codebase.

Additionally, if your application requires complex state management you might consider using a dedicated state management library like Redux or MobX.  

Scenarios to not use React Context

Here are some of the scenarios where you should avoid using React Context.

  1. Locally Related State: When the state is only relevant to a few closely related components, it is overkill to use React Context and would introduce unnecessary complexity.
  2. Frequently updating state: React context re-renders all the components using the state. If a state value is frequently updated and is not relevant to all the components then it would cause performance issues and unnecessary re-rendering of the components.
  3. Complex State Management: React context is suitable for managing simple global state. If your application requires complex state management and advanced features then using something like Redux or MobX would make more sense.
  4. Component-to-Component communication: If you want two child components to communicate with each other then React context is not a good choice. In this case, you can lift the state up or pass callback functions.
  5. Complex data structures: React Context is best suited for flat data structures. If your state requires a complex nested data structure then React context might not be the best choice, and it would be difficult to maintain and would result in performance issues.

React Context Pitfalls

When using React Context here are some pitfalls that you should be aware of to prevent performance issues, bottlenecks and the creation of code that is hard to maintain.

  1. Overusing Context: It is easier to overuse React Context and even the React State. Adding new context and even new state adds complexity to the application, so you should be careful when using React Context and add it to your application when it feels absolutely necessary.
  2. Tight Coupling of Components: Using too much Context in your application would result in components that are tightly coupled with each other, and updating one component would result in unnecessary side effects in other components. To avoid this design components with a clear boundary.  
  3. Mutable Context Values: Never update the context values directly, always use the setState method or create a method in the context provider to update the state. Updating context value directly would result in unexpected behaviour and hard-to-find bugs.
  4. Unnecessary Renders: When a context value changes, all the components that consume the context will be re-rendered, this can lead to performance issues when many components use the context. Hence you should not use context when its value changes frequently or you should split the context and only relevant components should consume the frequently updating context.

You might be interested in some of our other articles

Conclusion

In this guide, we have explored the React Context API, and seen more simple as well as complex examples that use multiple contexts.

We have also discussed the scenarios where it makes sense to use the React Context, and where it should be avoided, and also noted some pitfalls when using React Context.

I hope this guide will give you a good understanding of React Context.