Marcus Nguyen
Software Engineer
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;
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:
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>
)
}
And here is the result: 🎉🎉🎉