Back to blog
ReactTypescript

How to create multiple snackbar with Material-UI 🚀🚀

Material-ui does not allow multiple snackbars by default. As a matter of fact such behavior is discouraged by material design specification. This article will show you how to do it.

September 4, 2024Marcus Nguyen
How to create multiple snackbar with Material-UI 🚀🚀

In web development, providing feedback to users is crucial for a smooth and engaging experience. One popular way to give feedback is through snackbars—short, informative messages that briefly appear on the screen. Material UI, a popular React component library, offers a robust Snackbar component to handle these messages. But what if you need to display multiple snackbars simultaneously? In this guide, we'll walk through how to create and manage multiple snackbars using Material UI.

Understanding the Snackbar Component
Material UI’s Snackbar component is a notification that appears on the screen to provide feedback about an operation or an event. It typically shows a message and can include an optional action like a button to undo an action.

Here’s a basic example of a single Snackbar:

javascript
import React from 'react';
import Snackbar from '@mui/material/Snackbar';
import Button from '@mui/material/Button';
import Alert from '@mui/material/Alert';

function BasicSnackbar() {
  const [open, setOpen] = React.useState(false);

  const handleClick = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  return (
    <div>
      <Button onClick={handleClick}>Open Snackbar</Button>
      <Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
        <Alert onClose={handleClose} severity="success">
          This is a success message!
        </Alert>
      </Snackbar>
    </div>
  );
}

export default BasicSnackbar;

Displaying Multiple Snackbars

To manage multiple snackbars, you'll need to extend this basic concept. The idea is to keep a list of snackbars in your component's state and render them dynamically. Here’s a step-by-step approach:

  1. Set Up State Management: Maintain an array of snackbars in your component's state. Each snackbar can be represented by an object containing its message, type, and a unique ID.
  2. Render Multiple Snackbars: Use the map function to render multiple Snackbar components based on the state array.
  3. Handle Snackbar Actions: Ensure you have mechanisms to close individual snackbars and remove them from the state array.

Here’s a complete example demonstrating how to achieve this:

typescript
import { Alert, AlertColor, Snackbar, Stack } from '@mui/material';
import React, { ReactNode, createContext, useCallback, useContext, useState } from 'react';

// Notification interface
interface Notification {
  id: number;
  message: string;
  severity: AlertColor;
}

// Context interface
interface NotificationContextType {
  addNotification: (message: string, severity?: AlertColor) => void;
  success: (message: string) => void;
  warning: (message: string) => void;
  error: (message: string) => void;
  info: (message: string) => void;
}

// Context creation
const NotificationContext = createContext<NotificationContextType | undefined>(undefined);

export const useNotification = (): NotificationContextType => {
  const context = useContext(NotificationContext);
  if (!context) {
    throw new Error('useNotification must be used within a NotificationProvider');
  }
  return context;
};

// Provider props interface
interface NotificationProviderProps {
  children: ReactNode;
}

// NotificationProvider component
const NotificationProvider: React.FC<NotificationProviderProps> = ({ children }) => {
  const [notifications, setNotifications] = useState<Notification[]>([]);

  const addNotification = useCallback((message: string, severity: AlertColor = 'info') => {
    setNotifications((prevNotifications) => {
      const isDuplicate = prevNotifications.some(
        (notification) => notification.message === message && notification.severity === severity
      );
      if (isDuplicate) return prevNotifications;

      return [{ id: Date.now(), message, severity }, ...prevNotifications];
    });
  }, []);

  // Methods for different notification types
  const success = (message: string) => addNotification(message, 'success');
  const warning = (message: string) => addNotification(message, 'warning');
  const error = (message: string) => addNotification(message, 'error');
  const info = (message: string) => addNotification(message, 'info');

  const removeNotification = useCallback((id: number) => {
    setNotifications((prevNotifications) =>
      prevNotifications.filter((notification) => notification.id !== id)
    );
  }, []);

  const handleAlertClose = (event: any, reason: any, id: number) => {
    if (reason === 'clickaway') {
      return;
    }
    removeNotification(id);
  };

  return (
    <NotificationContext.Provider value={{ addNotification, success, warning, error, info }}>
      {children}
      <Stack sx={{ position: 'absolute', bottom: 10, left: 10 }} spacing={2}>
        {notifications.map((notification) => (
          <Snackbar
            key={notification.id}
            open={true}
            autoHideDuration={3000}
            onClose={(event, reason) => handleAlertClose(event, reason, notification.id)}
            anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
            sx={{ position: 'relative', left: '10px !important', bottom: '10px !important' }}
          >
            <Alert
              onClose={() => removeNotification(notification.id)}
              severity={notification.severity}
              elevation={6}
              variant="filled"
            >
              {notification.message}
            </Alert>
          </Snackbar>
        ))}
      </Stack>
    </NotificationContext.Provider>
  );
};

export default NotificationProvider;

Usage:

typescript
const function Demo() {
  const notification = useNotification();

  const handleClick = () => {
    notification.info('This is a message');
  };

  return (
    <div>
      <button onClick={handleClick}>Open snackbar</button>
    </div>
  )
}


Explanation

  1. State Management: snackbars state holds an array of snackbar objects, each with a unique id, message, and severity.
  2. Adding Snackbars: handleClick adds a new snackbar to the state with a unique ID.
  3. Closing Snackbars: handleClose removes a snackbar from the state based on its ID. This ensures that each snackbar is managed individually.
  4. Rendering Snackbars: The map function iterates over the snackbars array to render each Snackbar component. The key prop helps React identify which items have changed, are added, or are removed.

And here is the result: 🎉🎉🎉

snackbar