React Hooks: useContext

Updated:

In React, you encapsulate UI and state inside of components and compose them together to create an application. Whenever you have state that multiple components depend on, you use callback functions to pass that state up to the nearest parent component and then pass it down via props.

This approach works well for small applications, but as your application grows, it becomes more difficult to manage state. You end up with a lot of props that are passed down through multiple layers of components.

To solve this problem, React comes with a built-in API called Context. It allows you to pass data through the component tree without having to pass props down manually at every level.

Context is only a mechanism for transferring data, not a state manager.

It involves three steps:

  1. Creating a Context by calling React.createContext:
// File: LanguageContext.js

import * as React from "react";

const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {},
});

export default LanguageContext;
  1. Deciding what to transfer and to where

Now you need to decide what data to transfer and where to make it available in the application. When you created a Context in the previous step, you created an object with a Provider property. This property accepts a value prop that represents the data you want to transfer to any component, no matter how nested, that is a child of the Provider component.

// File: LanguageProvider.js

import * as React from "react";
import LanguageContext from "./LanguageContext";

export default function LanguageProvider({ children }) {
  const [language, setLanguage] = React.useState("en");

  return (
    <LanguageContext.Provider value={{ language, setLanguage }}>
      {children}
    </LanguageContext.Provider>
  );
}

Once you have created a Provider component, you can use it to wrap any component that needs access to the transferred data.

// File: main.js

import { createRoot } from "react-dom/client";

import LanguageProvider from "./LanguageProvider";
import App from "./App";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(
  <LanguageProvider>
    <App />
  </LanguageProvider>
);

Let’s use the LanguageProvider component to wrap the App component. This means that the App component and all of its children will have access to the data being transferred.

// File: App.js

import LanguageSelector from "./LanguageSelector";

export default function App() {
  return (
    <div>
      <LanguageSelector />
    </div>
  );
}
  1. Access the transfered data from inside a component

To get access to what was passed to the value prop of the Provider, you need to use React’s useContext hook, passing it the Context as the first argument.

// File: LanguageSelector.js

import * as React from "react";
import LanguageContext from "./LanguageContext";

export default function LanguageSelector() {
  const { language, setLanguage } = React.useContext(LanguageContext);

  return (
    <div>
      <p>{language}</p>
      <button onClick={() => setLanguage("en")}>English</button>
      <button onClick={() => setLanguage("it")}>Italian</button>
    </div>
  );
}

When you invoke useContext by passing it a Context object, it will return what was passed to the prop value of the nearest Provider component of the same Context.

However, if there is no parent Provider in the component tree where useContext is used, the App component will get its value from the first parameter passed to createContext when the Context object was created.

import * as React from "react";

const ThemeContext = React.createContext("light"); // default value is "light"

function ChildComponent() {
  const theme = React.useContext(ThemeContext);

  return <div>{theme}</div>;
}

function ParentComponent() {
  return (
    <ThemeContext.Provider value="dark">
      <ChildComponent />
    </ThemeContext.Provider>
  );
}

Although the ThemeContext object was created with a default value of light, the ChildComponent will use the dark value, because it has a parent ThemeContext.Provider with a value of dark.

The examples above represent small demos of how to use React Context. To see the real power of Context, you need to use it in a real application.

Think of an application that uses internationalization and localization, where you need to pass locale data to any component in the component tree, no matter how deeply nested they are.