実装

【React】Reduxを使用して画面遷移後もスナックバーを表示させ続ける方法

nanoha.creator@gmail.com

スナックバーとは、Webアプリで、ユーザーが操作した内容に対する処理結果などを通知するUIパーツです。
ReactとReduxを使って実装してみましたので、ご紹介します。

リポジトリ:https://github.com/nanoha94/react-redux-snackbar

仕様

今回実装するスナックバーの仕様についてまとめます。

  • 画面遷移してもスナックバーを表示し続ける
  • スナックバーを表示してから2秒後に非表示にする

ユーザー操作後に画面遷移をしたい場合も多いと思うので、画面遷移しても表示し続けるような仕様にしました。
この仕様を満たすために、Reduxを使用します。

デモアプリの内容

今回は、トップページ(ホーム)とマイページを作成して、マイページに「スナックバーを表示」ボタンを設置して、クリックされたら、トップページに遷移&スナックバーの表示をしたいと思います。

ページ遷移やアプリ全体の説明は割愛するので、詳細なコードを見たい方は、githubのページをご確認ください。

https://github.com/nanoha94/react-redux-snackbar

ファイル構成

以下のようなファイル構成で進めます。
すべてのファイルを抽出した訳ではないので、参考程度にしてください。
環境構築は、create-react-appを使用しました。

スナックバーの実装に関係するファイルを印付け(*)しています。

root/
 ├ node_modules/
 ├ public/
 ├ src/
 │ ├ components/
 │ │ ├ Header.tsx
 │ │ └ OpenSnackbar.tsx (*)
 │ │
 │ ├ pages/
 │ │ ├ Home.tsx
 │ │ └ Mypage.tsx (*)
 │ │ 
 │ ├ redux/
 │ │ ├ snackbar.tsx (*)
 │ │ └ store.tsx (*)
 │ │ 
 │ ├ types/
 │ │ └ Snackbar.tsx (*)
 │ │ 
 │ ├ App.tsx (*)
 │ ├ index.tsx (*)
 │ └ Router.tsx
 │ 
 ├ package-lock.json
 ├ package.json
 └ tsconfig.json

実装

スナックバーコンポーネントの作成

スナックバーの通知種別を定義しておきます。
複数のファイルから参照したいので、src/types/にファイルを作成しました。

export type SnackbarType = 'error' | 'warning' | 'info' | 'success';

以下のプロパティを受け取って、スナックバーを制御するコンポーネントを作成します。

  • open:スナックバーの開閉
  • alertType:スナックバーの通知種別(error/warning/info/success)
  • message:スナックバーに表示するメッセージ

MUIというコンポーネントライブラリを使用しました。

https://mui.com/material-ui/react-snackbar/

import React from 'react';
import Alert from '@mui/material/Alert';
import Snackbar from '@mui/material/Snackbar';
import { SnackbarType } from '@/types/Snackbar';

interface Props {
  open: boolean;
  alertType: SnackbarType;
  message?: string;
}

const OpenSnackbar: React.FC<Props> = ({ open, alertType, message }) => {
  return (
    <Snackbar
      anchorOrigin={{
        vertical: 'bottom',
        horizontal: 'left',
      }}
      open={open}
      key={alertType}
    >
      <Alert severity={alertType}>{message ?? (open ? alertType : '')}</Alert>
    </Snackbar>
  );
};

export default OpenSnackbar;

スナックバーコンポーネントの組み込み

スナックバーコンポーネントは、ページが切り替わっても表示し続けてほしいので、App.tsxに組み込みます。

import Router from './Router';
import Header from './components/Header';
import OpenSnackbar from './components/OpenSnackbar';
import { useSelector } from 'react-redux';
import { snackbarData } from './redux/store';

function App() {
  const snackbarSelector = useSelector(snackbarData);

  return (
    <>
    <Header />
    <main>
      <Router/>
    </main>
    <OpenSnackbar
        open={snackbarSelector.isOpen}
        alertType={snackbarSelector.alertType}
        message={snackbarSelector.message}
      />
    </>
  );
}

export default App;

スナックバーコンポーネントに渡す引数の管理

コンポーネントに渡す引数は、Reduxで管理します。
どのページからでもスナックバーコンポーネントを表示/非表示に切り替えたいためです。

Reduxストアにアクセスできるように、<Provider store={store}></Provider>でラッピングします。

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from "react-redux";
import { store } from "./redux/store";

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </Provider>
  </React.StrictMode>
);

reduxの設定をします。

import { configureStore } from '@reduxjs/toolkit';
import snackbarReducer from './snackbar';

export const store = configureStore({
  reducer: { snackbar: snackbarReducer },
});

export type RootState = ReturnType<typeof store.getState>;
export const snackbarData = (state: RootState) => state.snackbar;

ユーザーアクション後に、openSnackbar関数を呼び出すと、スナックバーが表示され、20秒後に非表示になるようにしています。

関数を呼び出すときに、スナックバーの通知種別とメッセージを引数に指定します。
表示/非表示の指示と一緒に、Reduxストアに保持され、それらの値を用いてスナックバーコンポーネントを制御します。(src/App.tsx)

import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
import { Dispatch } from 'redux';
import { SnackbarType } from '../types/Snackbar';

type SnackbarStateType = {
  isOpen: boolean;
  alertType: SnackbarType;
  message?: string;
};

const initialState: SnackbarStateType = {
  isOpen: false,
  alertType: 'success',
};

export const openSnackbar = (alertType: SnackbarType, message: string) => {
  return (dispatch: Dispatch) => {
    dispatch(snackbarSlice.actions.open({ alertType, message }));
    setTimeout(() => {
      dispatch(snackbarSlice.actions.close());
    }, 2000);
  };
};

const snackbarSlice = createSlice({
  name: 'snackbar',
  initialState,
  reducers: {
    open(
      state,
      action: PayloadAction<{ alertType: SnackbarType; message?: string }>,
    ) {
      state.isOpen = true;
      state.alertType = action.payload.alertType;
      state.message = action.payload.message;
    },
    close(state) {
      state.isOpen = false;
      state.message = undefined;
    },
  },
});

export default snackbarSlice.reducer;

openSnackbar関数の呼び出し

ボタンクリックのイベント関数から、openSnackbar関数を呼び出すことにより、スナックバーが表示されます。
20秒後に非表示にする制御は、openSnackbar関数がやっています。

import { useNavigate } from "react-router";
import { Button } from "@mui/material";
import { openSnackbar } from '../redux/snackbar';
import { store } from '../redux/store';

const Mypage = () => {

    const navigate = useNavigate();

    const onOpenSnackBar = () => {
        store.dispatch(openSnackbar('success', '完了しました'));
        navigate('/');
      };

  return (
    <>
    <h1>Mypage</h1>
    <Button variant="contained" onClick={onOpenSnackBar}>スナックバーを表示</Button>
    </>
  )
}

export default Mypage

さいごに

openSnackbar関数を呼び出したタイミングで、スナックバーが表示され、そこから2秒経過すると、非表示になるようなスナックバーコンポーネントが作成できました。
App.tsxにコンポーネントを組み込むことにより、ページが切り替わっても、スナックバーが表示され続けます(表示後2秒まで)。

ABOUT ME
Haruna Abe
Haruna Abe
Webエンジニア
フロントエンドエンジニア2年目|ReactやTypeScriptなど、フロントエンドの情報を発信していきます
CONTACT

お問い合わせ

制作のお見積もりやご依頼、その他ご相談等ございましたら
お気軽にお問い合わせください。

    記事URLをコピーしました