Delving Developer

Mastering Toast Notifications in React: A Comprehensive Guide to Portals, Hooks, and Context

Eddie Cunningham
Eddie Cunningham
5 min readReact.js
Cover Image for Mastering Toast Notifications in React: A Comprehensive Guide to Portals, Hooks, and Context

Toast notifications are a popular way to display short, non-intrusive messages to users. They are often used to provide feedback on user actions, such as form submissions or data updates. In this article, we will walk you through the process of building a Toast Notification component in React using Portals, Hooks, and Context. By the end of this tutorial, you will be able to add toast messages from anywhere within your application.

Understanding React Portals for Toast Notificationslink

React Portals offer a powerful way to render child components into a DOM node that exists outside the DOM hierarchy of the parent component. This feature is particularly useful for components like modals, tooltips, and toast notifications, which need to be rendered outside the main application container to avoid issues with CSS positioning, overflow, and stacking context.

When using a portal, React maintains the logical structure of the component tree, even though the rendered content is placed in a different part of the DOM. This ensures that event bubbling and other React features work as expected.

To use a portal, you will need to create a DOM node where the portal will render its content. In our case, we will create a div with the id "toast-root" in the index.html file:

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- ... -->
  </head>
  <body>
    <div id="root"></div>
    <div id="toast-root"></div>
  </body>
</html>

Creating a Versatile Toast Notification Component with Different Typeslink

First, let's create a flexible Toast component that will display the message, type, and automatically disappear after a certain duration:

import { useEffect } from 'react';

const Toast = ({ duration = 3000, message, type, id, removeToast }) => {
  useEffect(() => {
    const timer = setTimeout(() => removeToast(id), duration);

    return () => {
      clearTimeout(timer);
    };
  }, []);

  const toastClass = `toast toast-${type}`;

  return <div className={toastClass}>{message}</div>;
};

export default Toast;

In the code above, we added a type prop to the Toast component, which will be used to determine the styling of the toast message. The toastClass variable combines the base "toast" class with a specific type class (e.g., "toast-info", "toast-warning", or "toast-success"). You can style these as you wish to theme your toast notifications.

Now, let's create a ToastContainer component that will use a portal to render the Toast components:

import ReactDOM from 'react-dom';
import Toast from './Toast';

const ToastContainer = ({ toasts, removeToast }) => {
  const toastRoot = document.getElementById('toast-root');

  return ReactDOM.createPortal(
    <div className="toast-container">
      {toasts.map((toast) => (
        <Toast key={toast.id} {...toast} removeToast={removeToast} />
      ))}
    </div>,
    toastRoot
  );
};

export default ToastContainer;

The ToastContainer component retrieves the "toast-root" DOM node and uses ReactDOM.createPortal to render the Toast components as children of that node. This ensures that the toast notifications are rendered outside the main application container.

Leveraging Hooks and Context for Efficient Toast Message Managementlink

To manage the toast messages, we will use the useState and useContext hooks along with the createContext function. First, let's create a ToastProvider component that will manage the state of the toast messages and provide the context to the rest of the application. We'll also create a hook that will allow us to add toast messages:

import { createContext, useContext, useState } from 'react';
import ToastContainer from './ToastContainer';

const ToastContext = createContext()

const ToastProvider = ({ children }) => {
  const [toasts, setToasts] = useState([]);

  const addToast = ({ duration, message, type = "info" } = {}) => {
    const newToast = {
      duration,
      id: Date.now(),
      message,
      type
    }
    setToasts((prevToasts) => [...prevToasts, newToast]);
  };

  const removeToast = (id) => {
    setToasts((prevToasts) => prevToasts.filter((toast) => toast.id !== id));
  };

  return (
    <ToastContext.Provider value={{ addToast }}>
      {children}
      <ToastContainer toasts={toasts} removeToast={removeToast} />
    </ToastContext.Provider>
  );
};

const useToast = () => {
  const context = useContext(ToastContext)
  if (context === undefined) {
    throw new Error('useToast must be used within a ToastProvider')
  }
  return context
}

export { ToastProvider, useToast };

In the ToastProvider component, we manage the state of the toast messages using the useState hook. The addToast function accepts a message and an optional type (defaulting to info) and appends a new toast object to the state. The removeToast function filters out the toast with the specified id from the state.

useToast allows us to

Wrap your application with the ToastProvider in the index.js file:

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from './App'
import { ToastProvider } from './ToastProvider'

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(
  <StrictMode>
    <ToastProvider>
      <App />
    </ToastProvider>
  </StrictMode>
);

Seamlessly Adding Toast Messages from Anywhere in the Applicationlink

Now that we have our ToastProvider set up, we can use the useToast hook to access the addToast function from anywhere in our application:

import React from 'react';
import { useToast } from "./ToastProvider";

const MyComponent = () => {
  const { addToast } = useToast();

  const handleClick = (type) => {
    addToast({ duration: 3000, message: `This is a ${type} toast message!`, type });
  };

  return (
    <div>
      <button onClick={() => handleClick('info')}>Show Info Toast</button>
      <button onClick={() => handleClick('warning')}>Show Warning Toast</button>
      <button onClick={() => handleClick('success')}>Show Success Toast</button>
    </div>
  );
};

export default MyComponent;

In the example above, we import the ToastContext and use the useContext hook to access the addToast function. When a button is clicked, the handleClick function is called with the corresponding type, which triggers the addToast function with a custom message and type. This will display a toast notification with the specified message and styling based on the type.

Conclusionlink

In this article, we have covered the process of building a comprehensive Toast Notification component in React using Portals, Hooks, and Context. We have created a versatile Toast component that displays a message, type, and automatically disappears after a certain duration. We have also created a ToastContainer component that uses a portal to render the Toast components outside the main application container.

We have leveraged the useState and useContext hooks along with the createContext function to manage the state of the toast messages and provide the context to the rest of the application. Finally, we have demonstrated how to seamlessly add toast messages with different types (info, warning, success) from anywhere within the application using the useContext hook to access the addToast function.

By following this tutorial, you should now be able to create and manage versatile toast notifications in your React applications effectively.