Savvi Studio

Toast Notifications with MUI Snackbar

This guide shows how to use MUI's Snackbar component for toast notifications in the application.

Why MUI Snackbar?

  • ✅ Already in dependencies (no extra packages)
  • ✅ Matches application design system
  • ✅ Full TypeScript support
  • ✅ Accessible by default
  • ✅ Flexible and customizable

Basic Usage

Simple Toast

import { useState } from 'react';
import { Snackbar, Alert, Button } from '@mui/material';

function MyComponent() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Button onClick={() => setOpen(true)}>Show Toast</Button>
      <Snackbar 
        open={open} 
        autoHideDuration={6000} 
        onClose={() => setOpen(false)}
      >
        <Alert severity="success" onClose={() => setOpen(false)}>
          Operation successful!
        </Alert>
      </Snackbar>
    </>
  );
}

With State Management Hook

Using useBoolean from usehooks-ts for cleaner state management:

import { useBoolean } from 'usehooks-ts';
import { Snackbar, Alert, Button } from '@mui/material';

function MyComponent() {
  const { value: open, setTrue: show, setFalse: hide } = useBoolean();

  return (
    <>
      <Button onClick={show}>Show Toast</Button>
      <Snackbar open={open} autoHideDuration={6000} onClose={hide}>
        <Alert severity="success" onClose={hide}>
          Success!
        </Alert>
      </Snackbar>
    </>
  );
}

Severity Variants

Alert supports four severity levels:

<Alert severity="success">Success message</Alert>
<Alert severity="error">Error message</Alert>
<Alert severity="warning">Warning message</Alert>
<Alert severity="info">Info message</Alert>

Common Patterns

After Form Submission

import { useBoolean } from 'usehooks-ts';
import { Snackbar, Alert } from '@mui/material';

function SaveButton() {
  const { value: success, setTrue: showSuccess } = useBoolean();
  const { value: error, setTrue: showError, setFalse: hideError } = useBoolean();
  const [errorMessage, setErrorMessage] = useState('');

  const handleSave = async () => {
    try {
      await saveData();
      showSuccess();
    } catch (err) {
      setErrorMessage(err instanceof Error ? err.message : 'Save failed');
      showError();
    }
  };

  return (
    <>
      <Button onClick={handleSave}>Save</Button>
      
      <Snackbar open={success} autoHideDuration={3000}>
        <Alert severity="success">Saved successfully!</Alert>
      </Snackbar>
      
      <Snackbar open={error} onClose={hideError}>
        <Alert severity="error" onClose={hideError}>
          {errorMessage}
        </Alert>
      </Snackbar>
    </>
  );
}

With Offline Detection

import { useOnlineStatus } from '@/hooks/core/network';
import { Snackbar, Alert } from '@mui/material';

function AppLayout({ children }) {
  const { isOnline, wasOffline } = useOnlineStatus();

  return (
    <>
      {children}
      
      {/* Offline warning */}
      <Snackbar open={!isOnline}>
        <Alert severity="warning">
          You're offline. Changes will sync when reconnected.
        </Alert>
      </Snackbar>
      
      {/* Reconnection notification */}
      <Snackbar 
        open={wasOffline && isOnline} 
        autoHideDuration={4000}
      >
        <Alert severity="success">
          Connection restored!
        </Alert>
      </Snackbar>
    </>
  );
}

Async Operations

function AsyncAction() {
  const [loading, setLoading] = useState(false);
  const { value: success, setTrue: showSuccess } = useBoolean();
  const { value: error, setTrue: showError } = useBoolean();

  const handleAction = async () => {
    setLoading(true);
    try {
      await performAction();
      showSuccess();
    } catch (err) {
      showError();
    } finally {
      setLoading(false);
    }
  };

  return (
    <>
      <Button onClick={handleAction} disabled={loading}>
        {loading ? 'Processing...' : 'Perform Action'}
      </Button>
      
      <Snackbar open={success} autoHideDuration={3000}>
        <Alert severity="success">Action completed!</Alert>
      </Snackbar>
      
      <Snackbar open={error}>
        <Alert severity="error">Action failed</Alert>
      </Snackbar>
    </>
  );
}

Positioning

Control toast position with anchorOrigin:

<Snackbar
  open={open}
  anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
>
  <Alert>Top right toast</Alert>
</Snackbar>

Options:

  • vertical: 'top' | 'bottom'
  • horizontal: 'left' | 'center' | 'right'

Auto-Dismiss

// Auto-dismiss after 6 seconds (default)
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>

// No auto-dismiss (user must close manually)
<Snackbar open={open}>

// Quick dismiss (3 seconds)
<Snackbar open={open} autoHideDuration={3000} onClose={handleClose}>

Custom Actions

<Snackbar open={open} onClose={handleClose}>
  <Alert 
    severity="info"
    action={
      <Button color="inherit" size="small" onClick={handleUndo}>
        UNDO
      </Button>
    }
  >
    Item deleted
  </Alert>
</Snackbar>

Multiple Toasts

For managing multiple toasts, use an array in state:

interface ToastMessage {
  id: string;
  message: string;
  severity: 'success' | 'error' | 'info' | 'warning';
}

function MultiToast() {
  const [toasts, setToasts] = useState<ToastMessage[]>([]);

  const addToast = (message: string, severity: ToastMessage['severity']) => {
    const id = Date.now().toString();
    setToasts(prev => [...prev, { id, message, severity }]);
  };

  const removeToast = (id: string) => {
    setToasts(prev => prev.filter(toast => toast.id !== id));
  };

  return (
    <>
      <Button onClick={() => addToast('Success!', 'success')}>
        Add Toast
      </Button>
      
      {toasts.map((toast) => (
        <Snackbar
          key={toast.id}
          open={true}
          autoHideDuration={6000}
          onClose={() => removeToast(toast.id)}
        >
          <Alert severity={toast.severity} onClose={() => removeToast(toast.id)}>
            {toast.message}
          </Alert>
        </Snackbar>
      ))}
    </>
  );
}

Best Practices

  1. Use appropriate severity

    • success: Completed actions
    • error: Failed operations
    • warning: Important notices
    • info: General information
  2. Set reasonable auto-dismiss times

    • Success: 3-4 seconds
    • Error: Manual dismiss (or longer duration)
    • Info: 4-6 seconds
    • Warning: Manual dismiss
  3. Keep messages concise

    • Short, actionable messages
    • Avoid technical jargon
    • Be specific about what happened
  4. Provide actions when needed

    • Undo for destructive actions
    • Retry for failed operations
    • Links for more details