Beginners guide to React Context
Intro
In this post we're going to understand basic concepts of React Context and after that we learn how to implement context in an application in both functional components and class components. Then we cover some best practices on how to handle context in applications.
Concept
Before we implement context in our application we should understand the concept and possible use cases it has. According to official react docs:
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
There are some types of props that several components at different levels of project tree need access to. In this case, if we provide the props manually through the application we find ourselves with several components with props that are just getting passed to lower levels of project tree without any actual usage inside that component (which is known as Prop Drilling). This can pollute our component props and cause pain and confusion in the debugging phase.
Context is designed to share data that can be considered “global”
We can consider context as a global store where data lives, and all the data in this store (and any changes that will be made in the future) will be broadcasted to any component that needs it. For example, some use cases the Context API is ideal for: theming, user language, authentication, etc.
Implementation
Context comes with two components: Provider and Consumer. Provider is used every time we need to create a context object, after that we can access values from it with the consumer component.
The process of creating a provider is the same with functional and class components, the only difference is in implementing consumers.
For better understanding, We are going to use context to implement a dark-mode feature in a project. First we have to initialize our context object. Then we have to create our provider which has a dark-mode state and a toggle method inside it. We can have access to state and toggler method through provider.
import { useState } from "react";
// Class component context
export const ClassContext = React.createContext();
export const ClassProvider = ({ children }) => {
const [darkMode, setDarkMode] = useState(false);
const toggleDarkMode = () => {
setDarkMode(!darkMode);
};
return (
<ClassContext.Provider value={{ darkMode, toggleDarkMode }}>
{children}
</ClassContext.Provider>
);
};Then we have to wrap the part of our app that needs access to context with the provider. In this scenario, we have a class component that has a <Content /> and a button inside it, and clicking the button will trigger the toggler method which changes the content of our <Content />
import { ClassProvider } from "./contexts/theme-context";
import ClassComponent from "./componenst/class-component";
const App = () => {
return (
<div className="app-container">
<ClassProvider> <ClassComponent /> </ClassProvider> </div>
);
};
export default App;Here in the <ClassComponent /> we have to use the consumer variable inside classContext. Keep in mind that the child of a context consumer should always be a function that receives the variables and methods we need from context.
import { Component } from "react";
import Content from "./content";
import { ClassContext } from "../contexts/theme-context";
class classComponent extends Component {
render() {
return (
<ClassContext.Consumer>
{({ darkMode, toggleDarkMode }) => { return (
<div className="class-component">
<Content darkMode={darkMode} />
<button
className={`toggle ${darkMode && "dark-mode"}`}
onClick={() => toggleDarkMode()}
>
Switch to {darkMode ? "Day" : "Night"}
</button>
</div>
);
}}
</ClassContext.Consumer>
);
}
}
export default classComponent;And finally in <Content /> we have a bunch of if statements that changes the render every time we change the value in context:
import "../styles.scss";
import { Moon } from "../assets/moon";
import { Sun } from "../assets/sun";
const Content = ({ darkMode }) => {
return (
<div className={`content-container ${darkMode && "dark-mode"}`}> <div className="status-card">
{darkMode ? <Moon /> : <Sun />} <div className="card-title">
{darkMode ? "Good Night!" : "Good Morning!"} </div>
</div>
</div>
);
};
export default Content;Congrats, you have successfully implemented context in your app.
Now what if you need to divide your application into two sections and use different providers for each section?
For learning purposes, imagine we need to implement the same dark-mode application, but this time we use functional components and hooks.
Let’s start with a second provider, but with the same variables and properties:
// Functional component context
export const FunctionalContext = React.createContext();
export const FunctionalProvider = ({ children }) => {
const [darkMode, setDarkMode] = useState(false);
const toggleDarkMode = () => {
setDarkMode(!darkMode);
};
return (
<FunctionalContext.Provider value={{ darkMode, toggleDarkMode }}>
{children}
</FunctionalContext.Provider>
);
};Now let’s wrap the new section of our app (we’ll call it FunctionalComponent) with the provider we just created in `
import "./styles.scss";
import { ClassProvider, FunctionalProvider } from "./contexts/theme-context";import ClassComponent from "./componenst/class-component";
import FunctionalComponent from "./componenst/functional-component";
const App = () => {
return (
<div className="app-container">
<ClassProvider>
<ClassComponent />
</ClassProvider>
<FunctionalProvider> <FunctionalComponent /> </FunctionalProvider>
</div>
);
};
export default App;In the <FunctionalComponent /> we don’t need to use consumer variable from context, instead we can use a react hook called useContext and pass the actual context to it to access any variable or method we need from it:
import { useContext } from "react";
import { FunctionalContext } from "../contexts/theme-context";import Content from "./content";
export default function FunctionalComponent() {
const { darkMode, toggleDarkMode } = useContext(FunctionalContext);
return (
<div className="functional-component">
<Content darkMode={darkMode} />
<button
className={`toggle ${darkMode && "dark-mode"}`}
onClick={() => toggleDarkMode()}
>
Switch to {darkMode ? "Day" : "Night"}
</button>
</div>
);
}Conclusion
Context is definitely a great approach if you need to avoid prop-drilling and want to write cleaner codes. But, if you’re thinking about replacing a complex state-management system like Redux with context, you might want to think twice and consider the limitations it offers. It may not be a suitable alternative to redux in complex state management or large scale projects, but if you’re working on a small project with few global states and want to avoid Redux boilerplates, context might be a good choice.
See the example in codesandbox