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
- 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.
- 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:
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
.
Create a new react application using
create-react-app
npx create-react-app multi-context-app cd multi-context-app
Now install
react-router-dom
package, which will allow us to create routing in our applicationnpm install react-router-dom
Now we will create our
ThemeContext
. Create a folder called contexts inside the src folder and there create a file calledThemeContext.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.Next create another context called the
UserContext
, inside our contexts folder. TheUserContext
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
.
- 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.
Create another file called as
src/Profile.js
this will hold the code for ourProfile
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 theUserContext
. 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.Now let's setup routing in our application and create two pages
Home
andDashboard
that would use ourHeader
andProfile
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.
- 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 behavior and hard-to-find bugs.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
this article was originally published on the DeadSimpleChat blog: React Context: The Detailed Guide
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.