
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:
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:
- 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.
- Render Multiple Snackbars: Use the
mapfunction to render multipleSnackbarcomponents based on the state array. - 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:
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:
const function Demo() {
const notification = useNotification();
const handleClick = () => {
notification.info('This is a message');
};
return (
<div>
<button onClick={handleClick}>Open snackbar</button>
</div>
)
}
Explanation
- State Management:
snackbarsstate holds an array of snackbar objects, each with a uniqueid,message, andseverity. - Adding Snackbars:
handleClickadds a new snackbar to the state with a unique ID. - Closing Snackbars:
handleCloseremoves a snackbar from the state based on its ID. This ensures that each snackbar is managed individually. - Rendering Snackbars: The
mapfunction iterates over thesnackbarsarray to render eachSnackbarcomponent. Thekeyprop helps React identify which items have changed, are added, or are removed.
And here is the result: 🎉🎉🎉
