📝

Reactでゼロからトーストを実装してみた

2024/01/21に公開

はじめに

今回はReactでトーストを実装しました。トーストのライブラリはいくつもありますが、今回はゼロから実装してみました。完成イメージは以下の通りです。この中から今回実装した部分についてご紹介します。

対象の読者

useState, useContextなどの使い方を把握している方

実装内容

トースト本体は以下のように実装しました。途中に出てくるVisibleコンポーネントはisOpenがtrueの際に<Visible></Visible>で囲った部分を表示するコンポーネントです。Toastコンポーネントにtextが渡された際はトーストを表示、textが''の際はトーストが非表示になります。

Toast.tsx
export const Toast: React.FC<IProps> = ({
  text,
  displayTime,
}) => {

  return (
    <Visible isOpen={!!text}>
      <div
        style={{
          minHeight: '40px',
          width: '400px',
          boxSizing: 'border-box',
          position: 'absolute',
          bottom: '15px',
          right: '15px',
          display: 'flex',
          flexDirection: 'column',
          color: Color.WHITE_DARK,
          backgroundColor: Color.GREEN_LIGHT,
          borderRadius: '4px',
          overflow: 'hidden',
          zIndex: '11',
        }}
      >
        <div
          style={{
            margin: '16px',
            fontSize: '14px',
            whiteSpace: 'pre',
          }}
        >
          {text}
        </div>
        <style>
          {`@keyframes toShort {
            0%{
              width: 100%;
            }
            100%{
              width: 0%;
            }
          }`}
        </style>
        <div
          style={{
            height: '4px',
            backgroundColor: Color.GRAY_LIGHT,
            animation: `toShort ${displayTime}ms`,
            opacity: '0.9',
          }}
        />
      </div>
    </Visible>
  );
};

アニメーション

よくあるトーストのアニメーションで時間経過に合わせて下のバーが短くなっていくものがあります。せっかくなのでそちらのアニメーションも実装しました。高さ4pxのdivタグを配置して、@keyframesで長さを短くしています。背景色と馴染ませるため透明度を0.9にしています。displayTimeで任意の表示時間を設定できます。

Toast.tsxより抜粋
<style>
  {`@keyframes toShort {
    0%{
      width: 100%;
    }
    100%{
      width: 0%;
    }
  }`}
</style>
<div
  style={{
    height: '4px',
    backgroundColor: Color.GRAY_LIGHT,
    animation: `toShort ${displayTime}ms`,
    opacity: '0.9',
  }}
/>

呼び出し方

トーストを使用する際には以下のToastContextを介して呼び出します。試作段階ではトーストを使用するコンポーネント内でsetToastの部分を制御していましたが、使用箇所が増えるたびに同じ処理を何度も書くことになるため、Contextを作成してそれらの処理を1つにまとめました。

ToastProvider.tsx
export const ToastProvider: React.FC<IProps> = ({children}) => {

  const displayTime = 3500;
  const [toast, setToast] = useState({
    text: '',
    type: ToastType.SUCCESS,
  });

  const handleToast =  (
    text: string,
    type: ToastType = ToastType.SUCCESS,
  ): void => {
    setToast({
      text: text,
      type: type,
    });

    setTimeout(() => {
      setToast({
        text: '',
        type: ToastType.SUCCESS,
      });
    }, displayTime)
  }

  return (
    <ToastContext.Provider value={{handleToast}}>
      <Toast
        text={toast.text}
        displayTime={toast.type === ToastType.SUCCESS ? displayTime : 5000}
      />
      {children}
    </ToastContext.Provider>
  );
};

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

おわりに

今回はライブラリを使わずにゼロからトーストを実装しました。業務で使用する際はMUIなどを使用する方がより速く開発を進めることができますが、たまに趣味で作る分には勉強になっていいですね。

Discussion