Josué Hernández
In React, handling global state efficiently is essential for building scalable applications. While local state (useState) is suitable for component-specific needs, it can become unmanageable when multiple components require access to the same data.
This is where React Context and the Provider Pattern come in. These tools enable developers to share data without prop drilling, improving code maintainability and scalability.
The React Context API allows data to be passed deeply through the component tree without manually passing props at every level.
Use Context when you need to share state globally across multiple components, such as:
Avoid Context for frequently changing state (e.g., form inputs, animations). Since Context updates trigger re-renders across all consuming components, it can negatively impact performance.
For frequently updated state, consider alternatives like local state (useState), state management libraries (Zustand, Redux), or React Query for data fetching.
The Provider Pattern allows React components to share state efficiently by wrapping them inside a Context Provider.
We will create a notification system using React Context and the Provider Pattern.
src
├── context
│ ├── NotificationContext.tsx
│ ├── NotificationProvider.tsx
├── components
│ ├── Notification.tsx
│ ├── SomeComponent.tsx
├── App.tsx
Inside the context folder, create NotificationContext.tsx:
import { createContext } from "react";
interface NotificationContextType {
message: string | null;
showNotification: (msg: string) => void;
hideNotification: () => void;
}
export const NotificationContext = createContext<NotificationContextType | undefined>(undefined);
This defines:
Now, let’s create NotificationProvider.tsx to manage notification state.
import React, { useState } from "react";
import { NotificationContext } from "./NotificationContext";
export const NotificationProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [message, setMessage] = useState<string | null>(null);
const hideNotification = () => {
setMessage(null);
};
const showNotification = (msg: string) => {
setMessage(msg);
setTimeout(hideNotification, 3000);
};
return (
<NotificationContext.Provider value={{ message, showNotification, hideNotification }}>
{children}
</NotificationContext.Provider>
);
};
The provider:
Create Notification.tsx to display notifications.
import React from "react";
import { useNotification } from "../hooks/useNotification";
const Notification = () => {
const { message } = useNotification();
if (!message) return null;
return <div className="notification">{message}</div>;
};
export default Notification;
This component listens for changes in message and renders the notification when necessary.
Now, let’s create a component that triggers notifications.
import React from "react";
import { useNotification } from "../hooks/useNotification";
const SomeComponent = () => {
const { showNotification } = useNotification();
return (
<button onClick={() => showNotification("Item added to cart!")}>
Add to Cart
</button>
);
};
export default SomeComponent;
Clicking the button triggers showNotification(), displaying a message.
Finally, wrap the application inside the provider in App.tsx:
import React from "react";
import { NotificationProvider } from "./context/NotificationProvider";
import Notification from "./components/Notification";
import SomeComponent from "./components/SomeComponent";
function App() {
return (
<NotificationProvider>
<Notification />
<SomeComponent />
</NotificationProvider>
);
}
export default App;
Now, all components inside NotificationProvider can access the notification context.
React Context and the Provider Pattern are powerful tools for managing global state efficiently. They work best for sharing UI state, authentication, or notifications across multiple components.
Future posts will dive deeper into Context + useReducer, Optimizing Context Performance, and Comparing Context to Redux.