DEV Community

James Parker
James Parker

Posted on

Advanced Toast Notifications with Notistack in React

Notistack is a powerful, Material-UI-based notification library for React that provides a simple API for displaying snackbar notifications. It offers extensive customization options, queue management, and seamless integration with Material-UI themes. This guide walks through advanced usage of Notistack with React, including custom configurations, action buttons, and complex notification patterns. This is part 33 of a series on using Notistack with React.

Prerequisites

Before you begin, make sure you have:

  • Node.js version 14.0 or higher installed
  • npm, yarn, or pnpm package manager
  • A React project (version 16.8 or higher) or create-react-app setup
  • Material-UI (MUI) installed in your project
  • Basic knowledge of React hooks (useState, useEffect, useCallback)
  • Familiarity with Material-UI components
  • Understanding of async/await and Promises

Installation

Install Notistack and Material-UI dependencies:

npm install notistack @mui/material @emotion/react @emotion/styled
Enter fullscreen mode Exit fullscreen mode

Or with yarn:

yarn add notistack @mui/material @emotion/react @emotion/styled
Enter fullscreen mode Exit fullscreen mode

Or with pnpm:

pnpm add notistack @mui/material @emotion/react @emotion/styled
Enter fullscreen mode Exit fullscreen mode

After installation, your package.json should include:

{
  "dependencies": {
    "notistack": "^3.0.0",
    "@mui/material": "^5.0.0",
    "@emotion/react": "^11.0.0",
    "@emotion/styled": "^11.0.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Project Setup

Set up the SnackbarProvider in your main entry file:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { SnackbarProvider } from 'notistack';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <SnackbarProvider maxSnack={3}>
      <App />
    </SnackbarProvider>
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

First Example / Basic Usage

Let's create a simple notification component. Create a new file src/NotificationExample.jsx:

// src/NotificationExample.jsx
import React from 'react';
import { useSnackbar } from 'notistack';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';

function NotificationExample() {
  const { enqueueSnackbar } = useSnackbar();

  const showSuccess = () => {
    enqueueSnackbar('Operation completed successfully!', {
      variant: 'success',
      anchorOrigin: {
        vertical: 'top',
        horizontal: 'right'
      }
    });
  };

  const showError = () => {
    enqueueSnackbar('Something went wrong!', {
      variant: 'error',
      anchorOrigin: {
        vertical: 'top',
        horizontal: 'right'
      }
    });
  };

  const showWarning = () => {
    enqueueSnackbar('Please review your input.', {
      variant: 'warning',
      anchorOrigin: {
        vertical: 'top',
        horizontal: 'right'
      }
    });
  };

  const showInfo = () => {
    enqueueSnackbar('Here is some information.', {
      variant: 'info',
      anchorOrigin: {
        vertical: 'top',
        horizontal: 'right'
      }
    });
  };

  return (
    <Stack spacing={2} direction="row" sx={{ padding: 2 }}>
      <Button variant="contained" color="success" onClick={showSuccess}>
        Success
      </Button>
      <Button variant="contained" color="error" onClick={showError}>
        Error
      </Button>
      <Button variant="contained" color="warning" onClick={showWarning}>
        Warning
      </Button>
      <Button variant="contained" color="info" onClick={showInfo}>
        Info
      </Button>
    </Stack>
  );
}

export default NotificationExample;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

// src/App.jsx
import React from 'react';
import NotificationExample from './NotificationExample';
import './App.css';

function App() {
  return (
    <div className="App">
      <NotificationExample />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Understanding the Basics

Notistack provides several key features:

  • useSnackbar Hook: React hook for accessing notification functions
  • enqueueSnackbar: Function to add notifications to the queue
  • Variants: success, error, warning, info, default
  • Positioning: Customizable anchor positions (top/bottom, left/right/center)
  • Actions: Add action buttons to notifications
  • Queue Management: Automatic queue handling with maxSnack limit
  • Material-UI Integration: Seamless integration with MUI themes

Key concepts for advanced usage:

  • Custom Content: Use the content option to render custom React components
  • Action Buttons: Add action buttons with callbacks
  • Persistent Notifications: Notifications that don't auto-dismiss
  • Queue Management: Control how many notifications show at once
  • Custom Styling: Override default styles with Material-UI's sx prop

Here's an example with action buttons:

// src/ActionNotificationExample.jsx
import React from 'react';
import { useSnackbar } from 'notistack';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';

function ActionNotificationExample() {
  const { enqueueSnackbar, closeSnackbar } = useSnackbar();

  const showActionNotification = () => {
    const action = (snackbarId) => (
      <>
        <Button
          size="small"
          onClick={() => {
            alert('Action clicked!');
            closeSnackbar(snackbarId);
          }}
        >
          Undo
        </Button>
        <IconButton
          size="small"
          aria-label="close"
          color="inherit"
          onClick={() => closeSnackbar(snackbarId)}
        >
          <CloseIcon fontSize="small" />
        </IconButton>
      </>
    );

    enqueueSnackbar('Item deleted successfully', {
      variant: 'success',
      action,
      anchorOrigin: {
        vertical: 'bottom',
        horizontal: 'left'
      }
    });
  };

  const showPersistentNotification = () => {
    enqueueSnackbar('This notification will not auto-dismiss', {
      variant: 'info',
      persist: true,
      action: (snackbarId) => (
        <IconButton
          size="small"
          aria-label="close"
          color="inherit"
          onClick={() => closeSnackbar(snackbarId)}
        >
          <CloseIcon fontSize="small" />
        </IconButton>
      )
    });
  };

  return (
    <div style={{ padding: '20px' }}>
      <Button variant="contained" onClick={showActionNotification}>
        Show Action Notification
      </Button>
      <Button
        variant="contained"
        color="secondary"
        onClick={showPersistentNotification}
        sx={{ ml: 2 }}
      >
        Show Persistent Notification
      </Button>
    </div>
  );
}

export default ActionNotificationExample;
Enter fullscreen mode Exit fullscreen mode

Practical Example / Building Something Real

Let's build a comprehensive notification system with custom hooks, error handling, and advanced features:

// src/hooks/useNotification.js
import { useCallback } from 'react';
import { useSnackbar } from 'notistack';

export const useNotification = () => {
  const { enqueueSnackbar, closeSnackbar } = useSnackbar();

  const showSuccess = useCallback((message, options = {}) => {
    enqueueSnackbar(message, {
      variant: 'success',
      anchorOrigin: {
        vertical: 'top',
        horizontal: 'right'
      },
      autoHideDuration: 3000,
      ...options
    });
  }, [enqueueSnackbar]);

  const showError = useCallback((message, options = {}) => {
    enqueueSnackbar(message, {
      variant: 'error',
      anchorOrigin: {
        vertical: 'top',
        horizontal: 'right'
      },
      autoHideDuration: 5000,
      ...options
    });
  }, [enqueueSnackbar]);

  const showWarning = useCallback((message, options = {}) => {
    enqueueSnackbar(message, {
      variant: 'warning',
      anchorOrigin: {
        vertical: 'top',
        horizontal: 'right'
      },
      autoHideDuration: 4000,
      ...options
    });
  }, [enqueueSnackbar]);

  const showInfo = useCallback((message, options = {}) => {
    enqueueSnackbar(message, {
      variant: 'info',
      anchorOrigin: {
        vertical: 'top',
        horizontal: 'right'
      },
      autoHideDuration: 3000,
      ...options
    });
  }, [enqueueSnackbar]);

  const showLoading = useCallback((message) => {
    return enqueueSnackbar(message, {
      variant: 'info',
      persist: true,
      anchorOrigin: {
        vertical: 'top',
        horizontal: 'right'
      }
    });
  }, [enqueueSnackbar]);

  return {
    showSuccess,
    showError,
    showWarning,
    showInfo,
    showLoading,
    closeSnackbar
  };
};
Enter fullscreen mode Exit fullscreen mode

Now create an advanced notification system component:

// src/AdvancedNotificationSystem.jsx
import React, { useState } from 'react';
import { useNotification } from './hooks/useNotification';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';
import { useSnackbar } from 'notistack';

function AdvancedNotificationSystem() {
  const { showSuccess, showError, showWarning, showInfo, showLoading, closeSnackbar } = useNotification();
  const { enqueueSnackbar } = useSnackbar();
  const [formData, setFormData] = useState({
    message: '',
    variant: 'success',
    duration: 3000
  });

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: name === 'duration' ? parseInt(value) || 0 : value
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!formData.message) {
      showError('Please enter a message.');
      return;
    }

    const options = {
      autoHideDuration: formData.duration || undefined,
      anchorOrigin: {
        vertical: 'top',
        horizontal: 'right'
      }
    };

    switch (formData.variant) {
      case 'success':
        showSuccess(formData.message, options);
        break;
      case 'error':
        showError(formData.message, options);
        break;
      case 'warning':
        showWarning(formData.message, options);
        break;
      case 'info':
        showInfo(formData.message, options);
        break;
      default:
        showInfo(formData.message, options);
    }

    setFormData({ message: '', variant: 'success', duration: 3000 });
  };

  const simulateApiCall = async () => {
    const loadingId = showLoading('Processing your request...');

    try {
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 2000));

      closeSnackbar(loadingId);
      showSuccess('Operation completed successfully!');
    } catch (error) {
      closeSnackbar(loadingId);
      showError('Failed to complete operation. Please try again.');
    }
  };

  const showCustomContent = () => {
    enqueueSnackbar('Custom notification with React component', {
      variant: 'info',
      content: (key, message) => (
        <Paper
          key={key}
          elevation={3}
          sx={{
            p: 2,
            backgroundColor: '#1976d2',
            color: 'white',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'space-between',
            minWidth: 300
          }}
        >
          <Typography variant="body1">{message}</Typography>
          <IconButton
            size="small"
            aria-label="close"
            sx={{ color: 'white' }}
            onClick={() => closeSnackbar(key)}
          >
            <CloseIcon fontSize="small" />
          </IconButton>
        </Paper>
      ),
      anchorOrigin: {
        vertical: 'top',
        horizontal: 'right'
      }
    });
  };

  const showActionNotification = () => {
    enqueueSnackbar('Item will be deleted', {
      variant: 'warning',
      action: (snackbarId) => (
        <Stack direction="row" spacing={1}>
          <Button
            size="small"
            color="inherit"
            onClick={() => {
              showSuccess('Action cancelled');
              closeSnackbar(snackbarId);
            }}
          >
            Undo
          </Button>
          <IconButton
            size="small"
            aria-label="close"
            color="inherit"
            onClick={() => closeSnackbar(snackbarId)}
          >
            <CloseIcon fontSize="small" />
          </IconButton>
        </Stack>
      ),
      anchorOrigin: {
        vertical: 'bottom',
        horizontal: 'left'
      }
    });
  };

  return (
    <Box sx={{ padding: 3, maxWidth: 600, margin: '0 auto' }}>
      <Typography variant="h4" gutterBottom>
        Advanced Notification System
      </Typography>

      <Paper elevation={2} sx={{ p: 3, mb: 3 }}>
        <form onSubmit={handleSubmit}>
          <Stack spacing={2}>
            <TextField
              label="Message"
              name="message"
              value={formData.message}
              onChange={handleInputChange}
              fullWidth
              required
            />

            <FormControl fullWidth>
              <InputLabel>Variant</InputLabel>
              <Select
                name="variant"
                value={formData.variant}
                onChange={handleInputChange}
                label="Variant"
              >
                <MenuItem value="success">Success</MenuItem>
                <MenuItem value="error">Error</MenuItem>
                <MenuItem value="warning">Warning</MenuItem>
                <MenuItem value="info">Info</MenuItem>
              </Select>
            </FormControl>

            <TextField
              label="Duration (ms)"
              name="duration"
              type="number"
              value={formData.duration}
              onChange={handleInputChange}
              fullWidth
              helperText="0 for persistent notification"
            />

            <Button type="submit" variant="contained" fullWidth>
              Show Notification
            </Button>
          </Stack>
        </form>
      </Paper>

      <Stack spacing={2}>
        <Button
          variant="outlined"
          onClick={simulateApiCall}
          fullWidth
        >
          Simulate API Call
        </Button>
        <Button
          variant="outlined"
          color="secondary"
          onClick={showCustomContent}
          fullWidth
        >
          Show Custom Content
        </Button>
        <Button
          variant="outlined"
          color="warning"
          onClick={showActionNotification}
          fullWidth
        >
          Show Action Notification
        </Button>
      </Stack>
    </Box>
  );
}

export default AdvancedNotificationSystem;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

// src/App.jsx
import React from 'react';
import { SnackbarProvider } from 'notistack';
import AdvancedNotificationSystem from './AdvancedNotificationSystem';
import './App.css';

function App() {
  return (
    <SnackbarProvider
      maxSnack={3}
      anchorOrigin={{
        vertical: 'top',
        horizontal: 'right'
      }}
    >
      <div className="App">
        <AdvancedNotificationSystem />
      </div>
    </SnackbarProvider>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Common Issues / Troubleshooting

  1. Notifications not displaying: Ensure SnackbarProvider wraps your app at the root level. The provider must be a parent of components using useSnackbar.

  2. Material-UI theme not applied: Notistack uses Material-UI components, so make sure you have a ThemeProvider wrapping your app if you're using custom themes.

  3. Action buttons not working: Make sure action functions receive the snackbarId parameter and use closeSnackbar(snackbarId) to close the notification.

  4. Notifications stacking incorrectly: Use the maxSnack prop on SnackbarProvider to control how many notifications show at once. Adjust anchorOrigin for positioning.

  5. Custom content not rendering: When using the content option, the function receives (key, message) parameters. Return a valid React element.

  6. Persistent notifications: Set persist: true in options to prevent auto-dismissal. Always provide a close button for persistent notifications.

Next Steps

Now that you have an advanced understanding of Notistack:

  • Explore Material-UI theme customization for notifications
  • Learn about notification queues and limits
  • Implement notification persistence and storage
  • Add sound effects or other interactive features
  • Integrate with React Context for global notification management
  • Learn about other notification libraries (react-toastify, react-notifications-component)
  • Check the official documentation: https://iamhosseindhv.com/notistack
  • Look for part 34 of this series for more advanced topics

Summary

You've successfully integrated Notistack into your React application with advanced features including custom hooks, action buttons, custom content, and async operation handling. Notistack provides a powerful, Material-UI-based solution for displaying snackbar notifications with extensive customization options.

SEO Keywords

notistack
React Material-UI notifications
notistack tutorial
React snackbar library
notistack installation
React toast notifications
notistack example
React notification system
notistack setup
React Material-UI snackbar
notistack hooks
React notification queue
notistack customization
React notification library
notistack getting started

Top comments (0)