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:
- 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;
- 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>
);
}
- 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.