関数をグローバルなステートで管理できる?(React18+TypeScript+MUI)
解決したいこと
アラートダイアログをuseContextを用いて、共通化を試みる。
ダイアログを表示させる呼び出し元から、アラートダイアログのOKを押下した時の処理を渡す手段はあるのか。
現状の実装
以下の削除処理や保存処理で確認メッセージ(アラートダイアログ)を表示させている。
アラートダイアログの表示内容やボタン押下時の処理を、propsで渡してアラートダイアログを共通化している。
ダイアログの開閉ステートは、useContextでグローバル化している。
(1)保存処理
保存ページの「保存」ボタンを押下→【保存しますか?】のアラートダイアログを表示→【削除】ボタン押下で【保存処理】実行
(2)削除処理
削除ページの「削除」ボタンを押下→【削除しますか?】のアラートダイアログを表示→【保存】ボタン押下で【削除処理】実行
App.tsx(react-routerでルーティング)
import React from "react";
import { Route, BrowserRouter as Router, Routes } from "react-router-dom";
// ------- 中略 -------//
function App() {
return (
<AppContextProvider>
<Router basename="/">
<Routes>
<Route path="/" element={<Layout />}>
<Route path="/save" element={<SavePage />} />
<Route path="/delete" element={<DeletePage />} />
</Route>
</Routes>
</Router>
</AppContextProvider>
);
}
Layout.tsx(各ページをラップする)
import React from "react";
import { Outlet } from "react-router-dom";
// ------- 中略 -------//
function Layout() {
return (
<Box>
<Outlet />
</Box>
);
}
SavePage.tsx(保存を行うページ)※一部抜粋
function SavePage(){
// コンテキストからグローバルな値を取得
const { setAlertOpen } = useAppContext();
// 「保存」ボタン押下時処理(アラートダイアログを表示する)
const handleClickSave = () => {
setAlertOpen(true);
}
const handleSave = () => {
//保存処理
}
return(
<Box>
<Box>
{form} //保存するデータのフォーム
<Button onClick={handleClickSave}>
保存
</Button>
</Box>
<AlertDialog
title="保存"
content="保存しますか?"
btnText="保存"
handleClickAgree={handleSave}
/>
</Box>
);
}
DeletePage.tsx(削除を行うページ)※一部抜粋
function DeletePage(){
// コンテキストからグローバルな値を取得
const { setAlertOpen } = useAppContext();
// 「削除」ボタン押下時処理(アラートダイアログを表示する)
const handleClickDelete = () => {
setAlertOpen(true);
}
const handleDelete = () => {
//保存処理
}
return(
<Box>
<Box>
{form} //削除するデータのフォーム
<Button onClick={handleClickDelete}>
削除
</Button>
</Box>
<AlertDialog
title="削除"
content="削除しますか?"
btnText="削除"
handleClickAgree={handleDelete}
/>
</Box>
);
}
AlertDialog.tsx(アラートダイアログ)
import * as React from "react";
import Button from "@mui/material/Button";
import Dialog from "@mui/material/Dialog";
// ------- 中略 ------- //
interface AlertDialogProps {
title: string;
content: string;
btnText: string;
handleClickAgree: () => void;
}
export default function AlertDialog({
title,
content,
btnText,
handleClickAgree,
}: AlertDialogProps) {
const { alertOpen, setAlertOpen } = useAppContext();
// アラートダイアログを閉じる処理
const handleClose = () => {
setAlertOpen(false);
};
return (
<React.Fragment>
<Dialog
open={alertOpen}
onClose={handleClose}
>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<DialogContentText>
{content}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClickAgree} autoFocus>
{btnText}
</Button>
<Button onClick={handleClose}>キャンセル</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
AppContext.tsx(アラートダイアログの開閉状態をグローバルに管理)
import { createContext, useContext, useState } from "react";
interface AppContextType {
alertOpen: boolean;
setAlertOpen: React.Dispatch<React.SetStateAction<boolean>>;
}
export const AppContext = createContext<AppContextType | undefined>(undefined);
export const AppContextProvider = ({ children }: { children: ReactNode }) => {
// アラートの表示状態を管理するstate
const [alertOpen, setAlertOpen] = useState(false);
return (
<AppContext.Provider
value={{
alertOpen,
setAlertOpen,
}}
>
{children}
</AppContext.Provider>
);
};
export const useAppContext = () => {
const context = useContext(AppContext);
return context;
};
解決したい課題
各ページにAlertDialogコンポーネントを実装している状態。
各ページに1メッセージのダイアログであれば、この実装でも耐えられるが、別内容のダイアログを表示することがこの実装では不可。また、各ページにコンポーネントを呼んでいるため、可読性・メンテナンス性が難あり。各ページをラップしているLayout.tsxでAlertDialogコンポーネントを実装した方がよい。
試したこと
アラートダイアログに渡しているpropsをすべてuseAppContextでグローバルに管理する。
<AlertDialog
title="削除"
content="削除しますか?"
btnText="削除"
handleClickAgree={handleDelete}
/>
propsで渡している以下4つの項目について、AppContext.tsxでグローバルに管理してみる。
- title:アラートダイアログのタイトル(string)
- content:アラートダイアログに表示する内容(string)
- btnText:アラートダイアログのAgreeボタンのテキスト(string)
- handleClickAgree:Agreeボタンを押下した時の処理(void?)
上記のステートに値をセットするhandleOpenAlertDialog()をAppContextに追加
AppContext.tsx
// OK押下時のデフォルトの処理
const defaultAgreeHandle = () => {
// アラートを閉じる
setAlertOpen(false);
}
const [alertTitle, setAlertTitle] = useState<string>("");
const [alertContent, setAlertContent] = useState<string>("");
const [alertBtnText, setAlertBtnText] = useState<string>("");
const [onClickAgree, setOnClickAgree] = useState<() => void>(defaultAgreeHandle);
// AlertDialogを開く
const handleOpenAlertDialog = (alertTitle: string, alertContent: string, alertBtnText: string, onClickAgree: () => void) => {
setAlertTitle(alertTitle);
setAlertContent(alertContent);
setAlertBtnText(alertBtnText);
setOnClickAgree(onClickAgree);
setAlertOpen(true);
}
呼び出し元からは、以下でアラートを表示させる
// 「保存」ボタン押下時処理(アラートダイアログを表示する)
const handleClickSave = () => {
handleOpenAlertDialog("保存", "保存しますか?", "保存", handleSave);
}
上から3つは、string型なので、AppContext内のuseStateで管理ができたが、voidの関数は、handleClickAgreeを格納することがうまくできなかった。(関数が動かない)
ステートに関数処理は格納できないのだろうか...それともセットの仕方が違うのか...
もっといい方法があるのかも...?
解決策
調査中。