DEV Community

Matan Shaviro
Matan Shaviro

Posted on

System Design - Notification Component

Notifications are essential for improving user experience in modern applications. They keep users informed about events like successful actions, errors, or warnings. In this post, I'll show you how to build a reusable and customizable React Notification System from scratch.

Key Features of Our Notification System

  • Multiple Types: Supports success, error, warning, and info.

  • Auto-dismiss: Notifications disappear after a set duration.

  • Customizable Positions: Place notifications in any corner of the screen.

  • Stylish Animations: Smooth fade-in and fade-out effects.

  • Reusable Hook: Simple integration using a custom React hook.

By the end of this guide, you'll have a fully functional notification system that's lightweight and easy to integrate into your React projects.

Flow

Image description

Creating the Custom Hook

import { useState, useEffect } from 'react';
import Notification from '../components/Notification';

const useNotification = (position = 'bottom-left') => {
  const [notifications, setNotifications] = useState([]);

  const addNotification = (notification) => {
    const id = Math.random().toString(36).substring(7);
    setNotifications((prev) => [...prev, { ...notification, id }]);
  };

  const removeNotification = (id) => {
    setNotifications((prev) => prev.filter((notif) => notif.id !== id));
  };

  useEffect(() => {
    const timers = notifications.map((notification) =>
      notification.duration
        ? setTimeout(() => removeNotification(notification.id), notification.duration)
        : null
    );

    return () => timers.forEach((timer) => timer && clearTimeout(timer));
  }, [notifications]);

  const NotificationComponent = (
    <div className={`notification-container ${position}`}>
      {notifications.map((notification) => (
        <Notification
          key={notification.id}
          {...notification}
          onClose={() => removeNotification(notification.id)}
        />
      ))}
    </div>
  );

  return { triggerNotification: addNotification, NotificationComponent };
};

export default useNotification;

Enter fullscreen mode Exit fullscreen mode
  • State Management: The hook uses the useState hook to maintain an array of notifications, where each notification includes an id for tracking.

  • Adding Notifications: The addNotification function accepts a notification object, generates a unique id, and adds it to the notifications list.

  • Removing Notifications: The removeNotification function removes a notification from the list by its id.

  • Automatic Dismissal: Using useEffect, the hook automatically removes notifications after their specified duration by setting a setTimeout.

  • Rendering Notifications: The NotificationComponent maps through the notifications and renders a Notification component for each one, passing the necessary props and an onClose callback to dismiss them.

  • Return Value: The hook returns a triggerNotification function to trigger new notifications and the NotificationComponent to render the notifications on the screen.

Building the Notification Component

import { useEffect, useState } from 'react';
import { AiOutlineClose } from 'react-icons/ai';
import './Notification.css';

const Notification = ({ type = 'info', message, onClose, duration = 5000 }) => {
  const [isExiting, setIsExiting] = useState(false);

  const handleClose = () => {
    setIsExiting(true);
    setTimeout(() => onClose && onClose(), 300);
  };

  useEffect(() => {
    const timer = setTimeout(handleClose, duration);
    return () => clearTimeout(timer);
  }, [duration]);

  return (
    <div className={`notification ${type} ${isExiting ? 'exit' : ''}`}>
      <span>{message}</span>
      <AiOutlineClose className="close-button" onClick={handleClose} />
    </div>
  );
};

export default Notification;

Enter fullscreen mode Exit fullscreen mode

Fancy Styling

.notification {
  display: flex;
  align-items: center;
  padding: 10px 15px;
  border-radius: 5px;
  color: white;
  animation: fadeIn 0.3s ease-out;
  transition: opacity 0.3s, transform 0.3s;
}

.notification.exit {
  opacity: 0;
  transform: translateY(20px);
}

.notification.info {
  background-color: #2196f3;
}

.notification.success {
  background-color: #4caf50;
}

.notification.warning {
  background-color: #ff9800;
}

.notification.error {
  background-color: #f44336;
}

.close-button {
  margin-left: auto;
  cursor: pointer;
}

.notification-container {
  position: fixed;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.notification-container.bottom-left {
  bottom: 10px;
  left: 10px;
}

.notification-container.bottom-right {
  bottom: 10px;
  right: 10px;
}

.notification-container.top-left {
  top: 10px;
  left: 10px;
}

.notification-container.top-right {
  top: 10px;
  right: 10px;
}

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(-20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

Enter fullscreen mode Exit fullscreen mode


javascript

Using the Notification System

Now, integrate the hook into your app.

import useNotification from './hooks/useNotification';

function App() {
  const { NotificationComponent, triggerNotification } = useNotification('top-right');

  return (
    <div>
      <button
        onClick={() =>
          triggerNotification({
            type: 'success',
            message: 'This is a success message!',
            duration: 3000,
          })
        }
      >
        Trigger Success
      </button>
      <button
        onClick={() =>
          triggerNotification({
            type: 'error',
            message: 'This is an error message!',
            duration: 3000,
          })
        }
      >
        Trigger Error
      </button>
      {NotificationComponent}
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

useNotification vs. useContext

While the custom useNotification hook is a great solution for managing notifications, React’s useContext hook is a common alternative for global state management. Let’s compare the two approaches.

1. Use Case

  • useNotification: Best for specific, isolated functionality like notifications.

  • useContext: Ideal for sharing global state or logic across the entire application.

2. Simplicity

  • useNotification: Simple and lightweight, requiring no additional setup.

  • useContext: Requires setting up a context provider and consumers, which can be verbose for isolated features.

3. Flexibility

  • useNotification: Limited to notifications; not a global state manager.

  • useContext: Flexible for managing any kind of shared state, including notifications.

4. Performance

  • useNotification: Localized state ensures updates only affect components related to notifications.

  • useContext: Context updates can trigger unnecessary re-renders across all consuming components unless carefully optimized.

The choice between useNotification and useContext depends on your application’s needs. If you’re building a feature-specific system like notifications, useNotification provides a lightweight and elegant solution. However, for managing broader state across multiple components, useContext is a better choice.

Happy Coding!

Top comments (0)