DEV Community

Cover image for React Context: The Detailed Guide
alakkadshaw
alakkadshaw

Posted on • Originally published at deadsimplechat.com

React Context: The Detailed Guide

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.

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

## 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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Here is the complete code running the CodeSandbox:

%[https://codesandbox.io/s/dazzling-murdock-zig94y?from-embed]

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

2.

     npx create-react-app multi-context-app
     cd multi-context-app
    ```
{% endraw %}


    2\. Now install {% raw %}`react-router-dom`{% endraw %} package, which will allow us to create routing in our application

3.
{% raw %}
 ```bash
     npm install react-router-dom
    ```
{% endraw %}


    3\. Now we will create our {% raw %}`ThemeContext`{% endraw %}. Create a folder called contexts inside the src folder and there create a file called {% raw %}`ThemeContext.js`{% endraw %}

4.
{% raw %}
 ```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 };
    ```
{% endraw %}


    The {% raw %}`ThemeContext`{% endraw %} is similar to the ThemeContext that we have created in our first example.

    4\. Next create another context called the {% raw %}`UserContext`{% endraw %}, inside our contexts folder. The {% raw %}`UserContext`{% endraw %} will hold the information about the logged-in user.

5.
{% raw %}
 ```js
     // 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 };
    ```
{% endraw %}


    In the above code, we are using the {% raw %}`ThemeContext`{% endraw %} and get the current theme information and set the current theme as the {% raw %}`className`{% endraw %} for the header.

    We are also calling the {% raw %}`toggleTheme`{% endraw %} method provided to us by the {% raw %}`ThemeContext`{% endraw %} when the user clicks the {% raw %}`Toggle Theme`{% endraw %} button.

6. Create another file called as {% raw %}`src/Profile.js`{% endraw %} this will hold the code for our {% raw %}`Profile`{% endraw %} component
{% raw %}



```javascript
// 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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Here is the complete code running in Code Sandbox:

%[https://codesandbox.io/s/reverent-rumple-o1pdv6?from-embed]

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.

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.

This article was originally published on the DeadSimpleChat Blog:

React Context: The Detailed Guide: https://deadsimplechat.com/blog/react-context-tutorial

Top comments (1)

Collapse
 
alakkadshaw profile image
alakkadshaw

Thanks for reading. I hope you liked the article