DEV Community

James Parker
James Parker

Posted on

Building Toast Notification Systems with react-hot-toast in React

react-hot-toast is a lightweight, customizable, and beautiful toast notification library for React applications. It provides a simple API with zero configuration needed, beautiful animations, and extensive customization options. This guide walks through setting up and creating toast notification systems using react-hot-toast with React, from installation to a working implementation. This is part 38 of a series on using react-hot-toast 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
  • Basic knowledge of React hooks (useState, useCallback)
  • Familiarity with JavaScript/TypeScript
  • Understanding of CSS for styling

Installation

Install react-hot-toast using your preferred package manager:

npm install react-hot-toast
Enter fullscreen mode Exit fullscreen mode

Or with yarn:

yarn add react-hot-toast
Enter fullscreen mode Exit fullscreen mode

Or with pnpm:

pnpm add react-hot-toast
Enter fullscreen mode Exit fullscreen mode

After installation, your package.json should include:

{
  "dependencies": {
    "react-hot-toast": "^2.4.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Project Setup

Set up the Toaster component in your main entry file or App component:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Toaster } from 'react-hot-toast';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
    <Toaster />
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

Or add it to your App component:

// src/App.jsx
import React from 'react';
import { Toaster } from 'react-hot-toast';
import './App.css';

function App() {
  return (
    <div className="App">
      <Toaster />
      {/* Your app content */}
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

First Example / Basic Usage

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

// src/ToastExample.jsx
import React from 'react';
import toast from 'react-hot-toast';

function ToastExample() {
  const showSuccess = () => {
    toast.success('Operation completed successfully!');
  };

  const showError = () => {
    toast.error('Something went wrong!');
  };

  const showInfo = () => {
    toast('Here is some information.', {
      icon: 'ℹ️'
    });
  };

  const showLoading = () => {
    toast.loading('Processing...');
  };

  return (
    <div style={{ padding: '20px' }}>
      <h2>Toast Notification Examples</h2>
      <div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
        <button onClick={showSuccess}>Success</button>
        <button onClick={showError}>Error</button>
        <button onClick={showInfo}>Info</button>
        <button onClick={showLoading}>Loading</button>
      </div>
    </div>
  );
}

export default ToastExample;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

// src/App.jsx
import React from 'react';
import { Toaster } from 'react-hot-toast';
import ToastExample from './ToastExample';
import './App.css';

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

export default App;
Enter fullscreen mode Exit fullscreen mode

This creates basic toast notifications that appear when you click the buttons and automatically disappear after a few seconds.

Understanding the Basics

react-hot-toast provides several key features:

  • toast.success: Show a success toast
  • toast.error: Show an error toast
  • toast.loading: Show a loading toast
  • toast.custom: Show a custom toast with JSX
  • toast.promise: Handle promises with automatic loading/success/error states
  • Toaster: Component that renders all toasts
  • Positioning: Customizable toast positions
  • Customization: Extensive styling and behavior options

Key concepts:

  • Toaster Component: Toaster must be rendered in your app to display toasts
  • Toast API: Use the toast object to show different types of toasts
  • Automatic Dismissal: Toasts automatically disappear after a set duration
  • Positioning: Toasts can be positioned in different corners
  • Customization: Toast appearance and behavior can be customized with options

Here's an example with custom options and promise handling:

// src/AdvancedToastExample.jsx
import React from 'react';
import toast from 'react-hot-toast';

function AdvancedToastExample() {
  const showCustomToast = () => {
    toast('Custom toast message', {
      duration: 5000,
      position: 'top-center',
      style: {
        border: '1px solid #713200',
        padding: '16px',
        color: '#713200',
      },
      icon: '👏',
    });
  };

  const showPromiseToast = () => {
    const myPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        Math.random() > 0.5 ? resolve('Success!') : reject('Error!');
      }, 2000);
    });

    toast.promise(myPromise, {
      loading: 'Processing...',
      success: 'Operation completed!',
      error: 'Operation failed!',
    });
  };

  const showCustomJSX = () => {
    toast.custom((t) => (
      <div
        style={{
          background: 'white',
          padding: '16px',
          borderRadius: '8px',
          boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
          opacity: t.visible ? 1 : 0,
          transition: 'opacity 0.3s',
        }}
      >
        <strong>Custom JSX Toast</strong>
        <p>This is a custom toast with JSX content.</p>
        <button onClick={() => toast.dismiss(t.id)}>Close</button>
      </div>
    ));
  };

  return (
    <div style={{ padding: '20px' }}>
      <h2>Advanced Toast Examples</h2>
      <div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
        <button onClick={showCustomToast}>Custom Toast</button>
        <button onClick={showPromiseToast}>Promise Toast</button>
        <button onClick={showCustomJSX}>Custom JSX</button>
      </div>
    </div>
  );
}

export default AdvancedToastExample;
Enter fullscreen mode Exit fullscreen mode

Practical Example / Building Something Real

Let's build a comprehensive notification system with custom hooks and different notification patterns:

// src/hooks/useToastNotifications.js
import { useCallback } from 'react';
import toast from 'react-hot-toast';

export const useToastNotifications = () => {
  const showSuccess = useCallback((message, options = {}) => {
    toast.success(message, {
      duration: 3000,
      position: 'top-right',
      ...options
    });
  }, []);

  const showError = useCallback((message, options = {}) => {
    toast.error(message, {
      duration: 5000,
      position: 'top-right',
      ...options
    });
  }, []);

  const showLoading = useCallback((message) => {
    return toast.loading(message, {
      position: 'top-right'
    });
  }, []);

  const showPromise = useCallback((promise, messages) => {
    return toast.promise(promise, messages, {
      position: 'top-right'
    });
  }, []);

  const dismiss = useCallback((toastId) => {
    toast.dismiss(toastId);
  }, []);

  return {
    showSuccess,
    showError,
    showLoading,
    showPromise,
    dismiss
  };
};
Enter fullscreen mode Exit fullscreen mode

Now create an advanced notification system component:

// src/AdvancedToastSystem.jsx
import React, { useState } from 'react';
import { useToastNotifications } from './hooks/useToastNotifications';
import toast from 'react-hot-toast';

function AdvancedToastSystem() {
  const { showSuccess, showError, showLoading, showPromise, dismiss } = useToastNotifications();
  const [formData, setFormData] = useState({
    message: '',
    type: 'success'
  });

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
  };

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

    switch (formData.type) {
      case 'success':
        showSuccess(formData.message);
        break;
      case 'error':
        showError(formData.message);
        break;
      default:
        showSuccess(formData.message);
    }

    setFormData({ message: '', type: 'success' });
  };

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

    try {
      await new Promise(resolve => setTimeout(resolve, 2000));
      dismiss(loadingId);
      showSuccess('Operation completed successfully!');
    } catch (error) {
      dismiss(loadingId);
      showError('Failed to complete operation. Please try again.');
    }
  };

  const handlePromiseOperation = () => {
    const myPromise = fetch('https://api.example.com/data')
      .then(response => {
        if (!response.ok) throw new Error('Network error');
        return response.json();
      });

    showPromise(myPromise, {
      loading: 'Loading data...',
      success: 'Data loaded successfully!',
      error: 'Failed to load data.'
    });
  };

  const showCustomStyled = () => {
    toast('Custom styled toast', {
      style: {
        border: '2px solid #007bff',
        padding: '16px',
        color: '#007bff',
        borderRadius: '8px',
        fontSize: '16px',
        fontWeight: '500'
      },
      icon: '🎨',
      duration: 4000
    });
  };

  return (
    <div style={{ padding: '20px', maxWidth: '600px', margin: '0 auto' }}>
      <h1>Advanced Toast System</h1>

      <form onSubmit={handleSubmit} style={{ marginBottom: '20px', padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
        <div style={{ marginBottom: '16px' }}>
          <label style={{ display: 'block', marginBottom: '4px', fontWeight: '500' }}>
            Message *
          </label>
          <input
            type="text"
            name="message"
            value={formData.message}
            onChange={handleInputChange}
            style={{
              width: '100%',
              padding: '8px',
              border: '1px solid #ddd',
              borderRadius: '4px',
              boxSizing: 'border-box'
            }}
            required
          />
        </div>

        <div style={{ marginBottom: '16px' }}>
          <label style={{ display: 'block', marginBottom: '4px', fontWeight: '500' }}>
            Type
          </label>
          <select
            name="type"
            value={formData.type}
            onChange={handleInputChange}
            style={{
              width: '100%',
              padding: '8px',
              border: '1px solid #ddd',
              borderRadius: '4px',
              boxSizing: 'border-box'
            }}
          >
            <option value="success">Success</option>
            <option value="error">Error</option>
          </select>
        </div>

        <button
          type="submit"
          style={{
            width: '100%',
            padding: '10px',
            backgroundColor: '#007bff',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer',
            fontSize: '16px'
          }}
        >
          Show Toast
        </button>
      </form>

      <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
        <button
          onClick={simulateApiCall}
          style={{
            padding: '10px',
            backgroundColor: '#28a745',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          Simulate API Call
        </button>
        <button
          onClick={handlePromiseOperation}
          style={{
            padding: '10px',
            backgroundColor: '#ffc107',
            color: 'black',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          Promise Operation
        </button>
        <button
          onClick={showCustomStyled}
          style={{
            padding: '10px',
            backgroundColor: '#6c757d',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          Custom Styled Toast
        </button>
      </div>
    </div>
  );
}

export default AdvancedToastSystem;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx with custom Toaster configuration:

// src/App.jsx
import React from 'react';
import { Toaster } from 'react-hot-toast';
import AdvancedToastSystem from './AdvancedToastSystem';
import './App.css';

function App() {
  return (
    <div className="App">
      <AdvancedToastSystem />
      <Toaster
        position="top-right"
        toastOptions={{
          duration: 4000,
          style: {
            background: '#363636',
            color: '#fff',
          },
          success: {
            duration: 3000,
            iconTheme: {
              primary: '#4ade80',
              secondary: '#fff',
            },
          },
          error: {
            duration: 5000,
            iconTheme: {
              primary: '#ef4444',
              secondary: '#fff',
            },
          },
        }}
      />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Common Issues / Troubleshooting

  1. Toasts not displaying: Ensure Toaster component is rendered in your app. It should be placed at the root level, typically in your main App component or index file.

  2. Toasts appearing in wrong position: Use the position prop on the Toaster component or pass it in toast options. Available positions include 'top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right'.

  3. Toasts not auto-dismissing: Check the duration option. Set a duration in milliseconds (e.g., 3000 for 3 seconds). Set duration: Infinity for persistent toasts.

  4. Custom styling not working: Use the style or className options when calling toast functions. For global styles, use toastOptions on the Toaster component.

  5. Promise toasts not working: Make sure the promise is properly structured. toast.promise automatically handles loading, success, and error states based on the promise outcome.

  6. Multiple toasts stacking: react-hot-toast handles multiple toasts automatically. They will stack based on the position setting. Use toast.dismiss() to dismiss all toasts or pass a specific toast ID.

Next Steps

Now that you have an understanding of react-hot-toast:

  • Explore advanced customization options and themes
  • Learn about different toast positions and animations
  • Implement toast queues and limits
  • Add custom toast components with JSX
  • Integrate with React Context for global toast management
  • Learn about other toast libraries (notistack, react-toastify)
  • Check the official repository: https://github.com/timolins/react-hot-toast
  • Look for part 39 of this series for more advanced topics

Summary

You've successfully set up react-hot-toast in your React application and created a toast notification system with custom hooks, promise handling, and advanced features. react-hot-toast provides a lightweight, beautiful solution for displaying temporary messages with extensive customization options.

SEO Keywords

react-hot-toast
React toast notifications
react-hot-toast tutorial
React notification library
react-hot-toast installation
React toast messages
react-hot-toast example
React alert notifications
react-hot-toast setup
React toast hooks
react-hot-toast customization
React notification system
react-hot-toast promise
React toast library
react-hot-toast getting started

Top comments (0)