React Context Usecontext
008 - React Context API and useContext
The React Context API provides a way to pass data through the component tree without having to pass props down manually at every level. This is particularly useful for global data (like user authentication, theme, or in this case, toast notifications) that many components might need access to.
The Problem Context Solves (Prop Drilling)
Imagine you have a deeply nested component tree, and a piece of data (e.g., a user object) is needed by a component several levels down. Without Context, you'd have to pass that data as a prop through every intermediate component, even if those components don't directly use the data. This is known as "prop drilling" and can make your code verbose and harder to maintain.
How Context API Works
The Context API consists of three main parts:
createContext: Creates a Context object. When React renders a component that subscribes to this Context object, it will read the current context value from the closest matchingProviderabove it in the tree.Provider: A React component that allows consuming components to subscribe to context changes. It accepts avalueprop to be passed to consuming components that are descendants of this Provider.useContext: A React Hook that lets you read context from a functional component.
Example: Toast Notification System
This project uses the Context API to manage and display toast notifications globally. Let's examine src/components/ToastContext.js and src/hooks/useToast.js.
src/components/ToastContext.js (The Provider)
import React, { createContext, useState, useCallback } from 'react'; import Toast from './Toast'; export const ToastContext = createContext(); let id = 0; // Simple counter for unique toast IDs export const ToastContext = ({ children }) => { const [toasts, setToasts] = useState([]); // State to hold active toasts const addToast = useCallback((toast) => { const newToast = { ...toast, id: id++ }; setToasts((prevToasts) => { if (prevToasts.length >= 5) { // Limit to 5 toasts const updatedToasts = prevToasts.slice(0, prevToasts.length - 1); return [newToast, ...updatedToasts]; } return [newToast, ...prevToasts]; }); }, []); // Memoize addToast function const removeToast = useCallback((id) => { setToasts((prevToasts) => prevToasts.filter((toast) => toast.id !== id)); }, []); // Memoize removeToast function return ( <ToastContext.Provider value={{ addToast, removeToast }}> {children} <div className="fixed top-28 right-10 z-50"> {toasts.map((toast) => ( <Toast key={toast.id} id={toast.id} title={toast.title} message={toast.message} duration={toast.duration} removeToast={removeToast} /> ))} </div> </ToastContext.Provider> ); };Explanation:
export const ToastContext = createContext();: A Context object namedToastContextis created. This object will be used by both the Provider and the Consumer.ToastContextComponent: This is a functional component that will wrap parts of your application (as seen inApp.js).const [toasts, setToasts] = useState([]);: Manages the array of active toast notifications usinguseState.addToastandremoveToastfunctions: These functions are responsible for adding new toasts to thetoastsarray and removing them. They are wrapped inuseCallbackto prevent unnecessary re-creations, which is an optimization for performance.<ToastContext.Provider value={{ addToast, removeToast }}>: This is the core of the Provider. It makes theaddToastandremoveToastfunctions available to any component that consumesToastContextand is rendered within this Provider's tree. Thevalueprop is crucial here.{children}: This renders whatever components are passed as children to theToastContext. These children (and their descendants) will have access to the context value.- Toast Rendering: The
ToastContextalso directly renders the actualToastcomponents based on thetoastsstate, positioning them in the top-right corner of the screen.
src/hooks/useToast.js (The Consumer Hook)
import { useContext } from 'react'; import { ToastContext } from '../components/ToastContext'; export const useToast = () => { return useContext(ToastContext); };Explanation:
import { useContext } from 'react';: Imports theuseContextHook from React.import { ToastContext } from '../components/ToastContext';: Imports theToastContextobject that was created inToastContext.js.export const useToast = () => { ... };: This is a custom hook. Custom hooks are a powerful feature in React that allow you to extract reusable stateful logic from components. ThisuseToasthook simplifies consuming theToastContext.return useContext(ToastContext);: This line is where the magic happens. WhenuseContext(ToastContext)is called, React looks up the component tree for the closestToastContext.Providerand returns itsvalueprop. In this case, it returns{ addToast, removeToast }.
How it's Used in a Component (e.g., BlogPostPage.js)
// Inside BlogPostPage.js (or any other component that needs toasts) import { useToast } from '../hooks/useToast'; const CodeBlock = ({ /* ... */ }) => { const { addToast } = useToast(); // Access addToast function const handleCopy = () => { // ... copy logic ... addToast({ title: 'Success', message: 'Copied to clipboard!', duration: 3000, }); // ... }; // ... };Any component that needs to display a toast simply imports and calls useToast(), and it immediately gets access to the addToast function without needing to receive it as a prop from its parent.
Summary
The React Context API, combined with the useContext hook, provides an elegant solution for managing global state and sharing functions across your component tree, avoiding prop drilling and making your application's architecture cleaner and more maintainable. The toast notification system in this project is a prime example of its effective use.