Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

シンプルにToastを実装する in react

使い方

使いたい箇所より上のコンポーネントに <ToastProvider> を追加して(↓こんな感じ)、コンポーネント内で const toast = useToast(); で使う感じです。

createRoot(document.getElementById('root')!).render(
  <ToastProvider>
    <App />
  </ToastProvider>
);
const App = () => {
  const toast = useToast();
  return <button onClick={() => toast('clicked!')}>toast</button>
};

実装

import clsx from 'clsx';
import { PropsWithChildren, createContext, useContext, useState } from 'react';

const ToastContext = createContext<(message: string) => void>(() => {});

export const useToast = () => useContext(ToastContext);

export const ToastProvider = ({ children }: PropsWithChildren) => {
  const [showsToast, setShowsToast] = useState(false);
  const [fadesOutToast, setFadesOutToast] = useState(false);
  const [toastMessage, setToastMessage] = useState('');
  let toastHideTimerId: number | undefined = undefined;

  const toast = (message: string) => {
    clearTimeout(toastHideTimerId);
    setToastMessage(message);
    setShowsToast(true);
    toastHideTimerId = window.setTimeout(() => {
      setFadesOutToast(true);
    }, 3000);
  };

  return (
    <ToastContext.Provider value={toast}>
      {children}
      {showsToast && (
        <div
          className={clsx('toast', {
            ['opacity-0 transition-all duration-300']: fadesOutToast,
          })}
          onTransitionEnd={() => {
            if (!fadesOutToast) {
              return;
            }
            setShowsToast(false);
            setFadesOutToast(false);
          }}
        >
          <div className="alert">
            <span>{toastMessage}</span>
          </div>
        </div>
      )}
    </ToastContext.Provider>
  );
};