【React】Reduxを使用して画面遷移後もスナックバーを表示させ続ける方法
スナックバーとは、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秒まで)。