React Context is a feature that simplifies state management of data in React applications by sharing the data across components without prop drilling. Context is one of the many advantages of using React as it really simplifies the coding experience and streamlines applications.
Using the Context API consists of a createContext
function to establish a context, a Provider
component to wrap the part of the component tree where data is shared, and a Consumer
component or the useContext
hook to access and consume the shared data in child components. This allows for more efficient and cleaner global state management, making it especially beneficial for applications with deeply nested component hierarchies.
If you are interested in more similar React tutorials to enhance your React projects, consider checking out:
To get a better overview of React contextApi we will create a notification system with React TypeScript.
A notification system where components can trigger notifications to be displayed. Use React Context to manage the notifications globally, allowing any part of the application to trigger or display notifications without passing data through props.
npx create-react-app article-react-context-notification --template typescript
To create a TypeScript project in React, you have to use flag --template typescript
along with CRA.
Following a proper folder structure and the proper naming convention is a good practice for the maintainability and scalability of a project.
src/
|-- components/
| |-- NotificationProvider/
| | |-- NotificationProvider.tsx
| |
| |-- Notification/
| |-- Notification.tsx
|
|-- hooks/
| |-- useNotification.ts
|
|-- styles/
| |-- Notification.css
|
|-- App.tsx
|-- index.tsx
Define the initial state of your context. In this case of a notification system, this might include an array of notifications.
// src/components/NotificationProvider/NotificationProvider.tsx
import React, { createContext, useContext, useState, ReactNode } from "react";
interface NotificationContextProps {
showNotification: (message: string) => void;
removeNotification: (index: number) => void;
}
const NotificationContext = createContext<NotificationContextProps | undefined>(
undefined
);
interface NotificationProviderProps {
children: ReactNode;
}
export const NotificationProvider: React.FC<NotificationProviderProps> = ({
children,
}) => {
const [notifications, setNotifications] = useState<string[]>([]);
const showNotification = (message: string) => {
setNotifications((prevNotifications) => [...prevNotifications, message]);
};
const removeNotification = (index: number) => {
setNotifications((prevNotifications) =>
prevNotifications.filter((_, i) => i !== index)
);
};
return (
<NotificationContext.Provider
value={{ showNotification, removeNotification }}
>
{children}
<div className="notification-container">
{notifications.map((message, index) => (
<div
key={index}
className="notification"
onClick={() => removeNotification(index)}
>
{message}
</div>
))}
</div>
</NotificationContext.Provider>
);
};
export const useNotification = () => {
const context = useContext(NotificationContext);
if (!context) {
throw new Error(
"useNotification must be used within a NotificationProvider"
);
}
return context;
};
In this example, the NotificationProvider
initializes a state for storing notifications. The showNotification
function adds a new notification, and removeNotification
removes a notification by its index.
It will throw error when the context is null
or undefined
which is likely to be happen when you useNotification()
outside NotificationProvider
component.
Create a Notification component which renders the notification message.
// src/components/Notification/Notification.tsx
import React from "react";
interface NotificationProps {
message: string;
}
const Notification: React.FC<NotificationProps> = ({ message }) => {
return <div className="notification">{message}</div>;
};
export default Notification;
Wrap the entire component tree with the context provider. This ensures that all child components can access the context.
// src/index.tsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { NotificationProvider } from "./components/NotificationProvider/NotificationProvider";
ReactDOM.render(
<React.StrictMode>
<NotificationProvider>
<App />
</NotificationProvider>
</React.StrictMode>,
document.getElementById("root")
);
Once the context is created, you can create the hook component, which returns the hook as a function.
// src/hooks/useNotification.ts
import { useNotification as useNotificationContext } from "../components/NotificationProvider/NotificationProvider";
const useNotification = () => {
const { showNotification } = useNotificationContext();
return { showNotification };
};
export default useNotification;
Here we return the hooks, when we create a hook it name should start with use
. It can be used to passed the value to all the components.
// src/App.tsx
import React, { useState } from "react";
import Notification from "./components/Notification/Notification";
import useNotification from "./hooks/useNotification";
import "./styles/Notification.css";
const App: React.FC = () => {
const [message, setMessage] = useState("");
const { showNotification } = useNotification();
const HandleButtonClick = (message: string) => {
showNotification(message);
};
return (
<div>
<h1>React Context API Notification System</h1>
<input
className="messageInput"
type="text"
placeholder="Type a notification message"
onChange={(e) => setMessage(e.target.value.trim())}
/>
<button
className="notificationButton"
onClick={() => HandleButtonClick(message)}
>
Show Notification
</button>
<Notification message="Hello from App component!" />
</div>
);
};
export default App;
It includes an input field to enter a notification message, a button to trigger the notification, and a placeholder notification rendered by default. The state hook (useState
) manages the input message. The useNotification
hook is used to access the global notification context, and the HandleButtonClick
function shows a notification when the button is clicked.
Here we all set, now run the React project.
yarn start
or
npm start
After compilation the project will run on localhost at port 3000 (Default port for react application).
http://localhost:3000/
Output:
Context allows for dynamic changes to the shared values. When the context value changes, all components that consume that context will be re-rendered.
The Provider
component defines the scope of the context, and only components within its subtree can consume the provided context. This establishes a clear provider-consumer relationship.
Context providers can have default values, providing a fallback in case a component does not find a matching provider in its ancestry.
Context can act as a single source of truth for shared state, reducing the risk of inconsistencies that might arise from managing state in multiple places.
Ideal for complex state management scenarios where multiple components at different levels of the component tree need access to the same state or functions.
State and functions need to be explicitly passed down the component tree via props. This can lead to a more predictable flow of data, but it might become cumbersome in deeply nested structures.
In simpler applications, especially those with a shallow component tree, prop drilling might be straightforward and less of a concern. In such cases, using context could introduce unnecessary complexity.
Prop drilling provides a clear and explicit data flow, making it easier to trace the flow of data through the application.
Components are often more self-contained and can be easier to test since their behavior is directly tied to the props they receive.
For very large applications, prop drilling might lead to performance issues due to the sheer number of props being passed down the component tree. In such cases, context might offer a more optimized solution.
When passing data through many components gets messy, and you want an easier way to share state without threading it through every level.
If you have state that multiple parts of your app need to access or modify, and you want a central place to handle it.
When you have UI state, like modals or theme settings, that needs to be shared among different components.
If you're dealing with forms spread across various components and want an organized way to manage and update form data.
When you need different parts of your app to respond to changes in user authentication status without passing the info through props.
If your app involves dynamic theming or configuration changes that components need to adapt to.
When you want to share the current language or localization settings across different components.
Yes, you can use multiple contexts in a React application. Each context operates independently, and components can subscribe to multiple contexts.
React Context uses a form of "render prop" optimization. It only re-renders components that consume the context when the context value changes, preventing unnecessary renders.
Yes, React Context can be used with both functional and class components. However, using it with functional components and hooks like useContext
is more common in modern React development.
If a component tries to consume a context without a matching provider in its ancestry, it will use the default value provided during the context creation.