Skip to content

React-hot-toast

The Best React Notifications in Town

Simple usage

Install package: It weighs less than 5kb

yarn add react-hot-toast

Add Toaster to your app. Make sure it's placed at the top

<div><Toaster/></div>

Tip

NextJS의 경우 Top-Level 의 Server Side Component Layout 에 배치해도 된다. 나의 경우 Top Level 의

<body>

의 마지막에

<Script>

를 추가하는 느낌으로 추가함.

Start toasting! Call it from anywhere

toast("Hello World")

Tailwind Custom example

공식 홈페이지의 예제를 따라하면 팝업 애니메이션이 안나온다. 따라서 아래와 같이 tailwind 테마 확장을 tailwind.config.js 파일에 추가해야 한다:

/** @type {import('tailwindcss').Config} */
module.exports = {
  ...
  theme: {
    extend: {
      animation: {
        enter: 'enter 200ms ease-out',
        'slide-in': 'slide-in 1.2s cubic-bezier(.41,.73,.51,1.02)',
        leave: 'leave 150ms ease-in forwards',
      },
      keyframes: {
        enter: {
          '0%': {transform: 'scale(0.9)', opacity: 0},
          '100%': {transform: 'scale(1)', opacity: 1},
        },
        leave: {
          '0%': {transform: 'scale(1)', opacity: 1},
          '100%': {transform: 'scale(0.9)', opacity: 0},
        },
        'slide-in': {
          '0%': {transform: 'translateY(-100%)'},
          '100%': {transform: 'translateY(0)'},
        },
      },
    },
  },
  ...
};

Button Component TypeScript:

'use client';

import type {HTMLAttributes, PropsWithChildren} from 'react';
import toast from 'react-hot-toast';
import {CheckmarkIcon, ErrorIcon} from 'react-hot-toast';

interface CopyUrlButtonProps
  extends PropsWithChildren<HTMLAttributes<HTMLButtonElement>> {
  successLabel?: string;
  errorLabel?: string;
}

export default function CopyUrlButton(props: CopyUrlButtonProps) {
  const {successLabel, errorLabel, children, ...attrs} = props;
  const handlerClick = async () => {
    try {
      await navigator.clipboard.writeText(window.location.href);
      if (successLabel) {
        toast.custom(t => {
          return (
            <div
              className={`${
                t.visible ? 'animate-enter' : 'animate-leave'
              } max-w-md w-full pointer-events-auto alert alert-success hover:cursor-pointer`}
              onClick={() => toast.dismiss(t.id)}
            >
              <CheckmarkIcon />
              <p className="text-base-content">{successLabel}</p>
            </div>
          );
        });
      }
    } catch (e) {
      if (errorLabel) {
        toast.custom(t => {
          return (
            <div
              className={`${
                t.visible ? 'animate-enter' : 'animate-leave'
              } max-w-md w-full pointer-events-auto alert alert-error hover:cursor-pointer`}
              onClick={() => toast.dismiss(t.id)}
            >
              <ErrorIcon />
              <p>{errorLabel}</p>
            </div>
          );
        });
      }
    }
  };

  return (
    <button type="button" role="button" onClick={handlerClick} {...attrs}>
      {children}
    </button>
  );
}

See also

Favorite site