はじめに
- React、next.jsでのWebアプリ開発者向けの内容(本記事の環境はnext.js/TypeScript)
- 何か操作した時の結果(成功、失敗、警告など)をポップアップメッセージで表示したい、そのための処理を簡単に書きたい人向け
- Material UIのSnackbarコンポーネントの描画処理を、React Contextでどのコンポーネントからでも簡単に呼び出せるようにする
Snackbar(スナックバー)
- 短いメッセージをポップアップ表示する
- Material UIのコンポーネントで提供されており、severity(重要度)のpropsを指定すれば様々なスタイルのSnackbarを表示できる
やりたいこと
- Webアプリ上で何かイベントが起きた際にsnackbarを表示したい
- データの追加操作の成功時に成功メッセージを表示
- 操作に失敗したらエラーメッセージを表示 など
- snackbarを表示したい全てのコンポーネントで以下のようなコードを書くのは面倒、共通化したい
const [open, setOpen] = React.useState(false);
const handleClick = () => {
setOpen(true);
};
const handleClose = (event?: React.SyntheticEvent | Event, reason?: string) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
return (
<Stack spacing={2} sx={{ width: '100%' }}>
<Button variant="outlined" onClick={handleClick}>
Open success snackbar
</Button>
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity="success" sx={{ width: '100%' }}>
This is a success message!
</Alert>
</Snackbar>
<Alert severity="error">This is an error message!</Alert>
</Stack>
);
React ContextでSnackbar表示関数をどこからでも呼び出せるようにする
SnackbarProviderコンポーネントを作る
- 今回は表示する種類(severity)、表示するメッセージ(message)をイベントに応じて変化させたいので、contextに格納する関数 showSnackbarの引数にこの2つを指定。表示位置、表示秒数など他のパラメータを変えたければ引数を増やす。
context-provider.ts
import React, { createContext, useState, useContext, FC } from 'react';
import Snackbar from "@mui/material/Snackbar";
import Alert from '@mui/material/Alert';
type SnackbarSeverity = "error" | "warning" | "info" | "success";
interface ISnackbarContext {
//snackbarを表示するときに呼び出す関数
showSnackbar: ((type: SnackbarSeverity, message: string) => void) | undefined;
}
const SnackbarContext = createContext<ISnackbarContext>({showSnackbar: undefined});
export function useSnackbarContext() {
return useContext(SnackbarContext);
}
export const SnackbarProvider: FC = ({ children }) => {
//Snackbarに与えるパラメータをstateで管理
const [open, setOpen] = useState<boolean>(false);
const [severity, setSeverity] = useState<SnackbarSeverity>("info");
const [message, setMessage] = useState<string>("");
//showSnackbarの実体。各stateをセットし、snackbarを表示する
const showSnackbar= (type: SnackbarSeverity, message: string): void => {
setOpen(true);
setSeverity(type);
setMessage(message);
};
//snackbarのxボタンが押された時のコールバック関数
const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
return (
<React.Fragment>
{/* 子コンポーネントの描画 */}
<SnackbarContext.Provider value={{showSnackbar: showSnackbar}}>{children}</SnackbarContext.Provider>
{/* Snackbarの描画 */}
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity={severity} sx={{ width: '100%' }}>
{message}
</Alert>
</Snackbar>
</React.Fragment>
);
};
SnackbarProviderでラップする
- 今回はNext.jsで開発している環境のため、_app.tsxでアプリのコンポーネント全体にラップする
pages/_app.tsx
import { SnackbarProvider } from '../components/snackbar-provider';
const App = ({ Component, pageProps }: AppProps) => (
<div>
<Head>
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
</Head>
<SnackbarProvider>
<Component {...pageProps} />
</SnackbarProvider>
</div>
);
export default App
showSnackbarを呼び出す
- 各コンポーネントでuseSnackbarContextをインポートし、Snackbarを表示したいタイミングでshowSnackbarを呼び出す。
- 以下はDBへのアイテム新規作成するボタンが押された場合に成功、失敗メッセージを表示する例
- context-provider.tsxのcreateContextを通すためにshowSnackbarの型にundefinedを許容しているため、呼び出し前にif文でshowSnackbarの存在確認が必要
create-item.tsx
import { useSnackbarContext } from "../../components/snackbar-provider";
export const CreatePage: React.FC = () => {
const { showSnackbar } = useSnackbarContext();
const onCreateClick = (item: Item) => {
addItem(item)
.then(() => {
if(showSnackbar){
//成功のSnackbar表示
showSnackbar("success", "アイテムが新規作成されました")
}
})
.catch((error) => {
if(showSnackbar){
//エラーメッセージのSnackbar表示
showSnackbar("error", error.message)
}
}
};
return (
<div>
<InputItem onSubmit={onCreateClick} />
</div>
);
};
まとめ
- React ContextでSnackbar表示関数をどのコンポーネントからも簡単に呼び出せるようにした
- 表示位置、サイズ、自動非表示の有無などもshowSnackbarの引数で制御できるようにすれば、重要メッセージは画面中央、大きく、消えずに表示させるなど、汎用性を高めることもできそう