アプリ開発を楽しむ【#14:カスタムフック】
前回までの記事 アプリ開発を楽しむ【#1:アプリの概要】 アプリ開発を楽しむ【#2:環境構築1(React+TypeScript)】 アプリ開発を楽しむ【#3:環境構築2 (ESLint+Prettier)】 アプリ開発を楽しむ【#4:ヘッダー】 アプリ開発を楽しむ【#5:MyTogoList】 アプリ開発を楽しむ【#6:Reduxで状態管理1】 アプリ開発を楽しむ【#7:Reduxで状態管理2】 アプリ開発を楽しむ【#8:Google Map API】 アプリ開発を楽しむ【#9:新規追加モーダルUI(togo)】 アプリ開発を楽しむ【#10:Google Mapから座標を取得】 アプリ開発を楽しむ【#11:Togoの追加】 アプリ開発を楽しむ【#12:Togoの編集1】 アプリ開発を楽しむ【#13:Togoの編集2】
今回は、前回と前々回でつくったAddTogoModal.tsxとUpdateTogoModal.tsxに同じ処理が書かれているので、これをカスタムフックを使ってコードをすっきりさせていきたいと思います。
AddTogoModal.tsxとUpdateTogoModal.tsxには、同じコードが記載されている。(下記)
~~~省略~~~
const [mapCenterPosition, setMapCenterPosition] = useState<MapPosition>(togoData.position);
const [location, setLocation] = useState<string>(togoData.location);
const [tag, setTag] = useState<string>(togoData.tag);
const [mapMarkerPosition, setMapMarkerPosition] = useState<MapPosition>(togoData.position);
const initializeTogoState = (isOpen: boolean) => {
if (isOpen) {
setLocation(togoData.location);
setTag(togoData.tag);
setMapMarkerPosition(togoData.position);
setMapCenterPosition(togoData.position);
}
};
useEffect(() => {
initializeTogoState(isOpenAddTogoModal);
}, [isOpenAddTogoModal]);
const handleChangeLocation = (e: React.ChangeEvent<HTMLInputElement>) => {
setLocation(e.target.value);
};
const handleChangeTag = (e: React.ChangeEvent<HTMLInputElement>) => {
setTag(e.target.value);
};
const handleChangeMapMarkerPosition = (position: MapPosition) => {
setMapMarkerPosition(position);
};
const handleChangeMapCenterPosition = (position: MapPosition) => {
setMapCenterPosition(position);
};
~~~省略~~~
1.カスタムフックの作成
frontend/app/src/hooks/togo/useSetTogoFormValue.tsを次のようにつくります。
import { useState, useEffect } from 'react';
import { Togo } from '../../types/togo';
import { MapPosition } from '../../types/map';
type TogoFormValue = {
mapCenterPosition: MapPosition;
location: string;
tag: string;
mapMarkerPosition: MapPosition;
};
type SetTogoFormValue = {
handleChangeMapCenterPosition: (position: MapPosition) => void;
handleChangeLocation: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleChangeTag: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleChangeMapMarkerPosition: (position: MapPosition) => void;
};
const useSetTogoFormValue = (
togoData: Togo,
isOpenModal: boolean
): [TogoFormValue, SetTogoFormValue] => {
const [mapCenterPosition, setMapCenterPosition] = useState<MapPosition>(togoData.position);
const [location, setLocation] = useState<string>(togoData.location);
const [tag, setTag] = useState<string>(togoData.tag);
const [mapMarkerPosition, setMapMarkerPosition] = useState<MapPosition>(togoData.position);
const initializeTogoState = (isOpen: boolean) => {
if (isOpen) {
setLocation(togoData.location);
setTag(togoData.tag);
setMapMarkerPosition(togoData.position);
setMapCenterPosition(togoData.position);
}
};
useEffect(() => {
initializeTogoState(isOpenModal);
}, [isOpenModal]);
const handleChangeLocation = (e: React.ChangeEvent<HTMLInputElement>) => {
setLocation(e.target.value);
};
const handleChangeTag = (e: React.ChangeEvent<HTMLInputElement>) => {
setTag(e.target.value);
};
const handleChangeMapMarkerPosition = (position: MapPosition) => {
setMapMarkerPosition(position);
};
const handleChangeMapCenterPosition = (position: MapPosition) => {
setMapCenterPosition(position);
};
return [
{
mapMarkerPosition,
location,
tag,
mapCenterPosition,
},
{
handleChangeLocation,
handleChangeTag,
handleChangeMapCenterPosition,
handleChangeMapMarkerPosition,
},
];
};
export default useSetTogoFormValue;
2.AddTogoModalとUpdateTogoModalの修正
①frontend/app/src/components/togo/AddTogoModal.tsxの修正
// ここから修正
import React from 'react';
// ここから修正
import { useDispatch, useSelector } from 'react-redux';
import { Button, Dialog, DialogActions, DialogTitle } from '@mui/material';
import TogoForm from './TogoForm';
import { RootState, AppDispatch } from '../../redux/store';
import { addTogo, initialState } from '../../redux/togoSlice';
// ここから追加
import useSetTogoFormValue from '../../hooks/togo/useSetTogoFormValue';
// ここから追加
// ここから削除
import type { MapPosition } from '../../types/map';
// ここから削除
import type { Togo } from '../../types/togo';
type AddTogoModalProps = {
togoData: Togo;
isOpenAddTogoModal: boolean;
handleCloseAddTogoModal: () => void;
};
const AddTogoModal: React.FC<AddTogoModalProps> = ({
togoData,
isOpenAddTogoModal,
handleCloseAddTogoModal,
}) => {
const dispatch: AppDispatch = useDispatch();
// ここから追加
const [
{ mapMarkerPosition, location, tag, mapCenterPosition },
{
handleChangeLocation,
handleChangeTag,
handleChangeMapCenterPosition,
handleChangeMapMarkerPosition,
},
] = useSetTogoFormValue(togoData, isOpenAddTogoModal);
// ここまで追加
const { togoList } = useSelector((state: RootState) => state.togo || initialState);
// ここから削除(カスタムフックuseSetTogoFormValue.tsxへ)
const [mapCenterPosition, setMapCenterPosition] = useState<MapPosition>(togoData.position);
const [location, setLocation] = useState<string>(togoData.location);
const [tag, setTag] = useState<string>(togoData.tag);
const [mapMarkerPosition, setMapMarkerPosition] = useState<MapPosition>(togoData.position);
const initializeTogoState = (isOpen: boolean) => {
if (isOpen) {
setLocation(togoData.location);
setTag(togoData.tag);
setMapMarkerPosition(togoData.position);
setMapCenterPosition(togoData.position);
}
};
useEffect(() => {
initializeTogoState(isOpenAddTogoModal);
}, [isOpenAddTogoModal]);
const handleChangeLocation = (e: React.ChangeEvent<HTMLInputElement>) => {
setLocation(e.target.value);
};
const handleChangeTag = (e: React.ChangeEvent<HTMLInputElement>) => {
setTag(e.target.value);
};
const handleChangeMapMarkerPosition = (position: MapPosition) => {
setMapMarkerPosition(position);
};
const handleChangeMapCenterPosition = (position: MapPosition) => {
setMapCenterPosition(position);
};
// ここまで削除
const handleAddTogo = () => {
if (!location || !tag) {
return;
}
const addTogoData: Togo = {
id: togoList.length,
done: false,
location,
tag,
position: mapMarkerPosition,
};
dispatch(addTogo(addTogoData));
handleCloseAddTogoModal();
};
~~~省略~~~
②frontend/app/src/components/togo/UpdateTogoModal.tsxの修正
// ここから修正
import React from 'react';
// ここまで修正
import { useDispatch } from 'react-redux';
import { Button, Dialog, DialogActions, DialogTitle } from '@mui/material';
import TogoForm from './TogoForm';
import { AppDispatch } from '../../redux/store';
import { updateTogo } from '../../redux/togoSlice';
// ここから追加
import useSetTogoFormValue from '../../hooks/togo/useSetTogoFormValue';
// ここから追加
// ここから削除
import type { MapPosition } from '../../types/map';
// ここから削除
import type { Togo } from '../../types/togo';
type UpdateTogoModalProps = {
togoData: Togo;
isOpenUpdateTogoModal: boolean;
handleCloseUpdateTogoModal: () => void;
};
const UpdateTogoModal: React.FC<UpdateTogoModalProps> = ({
togoData,
isOpenUpdateTogoModal,
handleCloseUpdateTogoModal,
}) => {
const dispatch: AppDispatch = useDispatch();
// ここから追加
const [
{ mapMarkerPosition, location, tag, mapCenterPosition },
{
handleChangeLocation,
handleChangeTag,
handleChangeMapCenterPosition,
handleChangeMapMarkerPosition,
},
] = useSetTogoFormValue(togoData, isOpenUpdateTogoModal);
// ここまで追加
// ここから削除(カスタムフックuseSetTogoFormValue.tsxへ移動)
const [mapCenterPosition, setMapCenterPosition] = useState<MapPosition>(togoData.position);
const [location, setLocation] = useState<string>(togoData.location);
const [tag, setTag] = useState<string>(togoData.tag);
const [mapMarkerPosition, setMapMarkerPosition] = useState<MapPosition>(togoData.position);
const initializeTogoState = (isOpen: boolean) => {
if (isOpen) {
setLocation(togoData.location);
setTag(togoData.tag);
setMapMarkerPosition(togoData.position);
setMapCenterPosition(togoData.position);
}
};
useEffect(() => {
initializeTogoState(isOpenUpdateTogoModal);
}, [isOpenUpdateTogoModal]);
const handleChangeLocation = (e: React.ChangeEvent<HTMLInputElement>) => {
setLocation(e.target.value);
};
const handleChangeTag = (e: React.ChangeEvent<HTMLInputElement>) => {
setTag(e.target.value);
};
const handleChangeMapMarkerPosition = (position: MapPosition) => {
setMapMarkerPosition(position);
};
const handleChangeMapCenterPosition = (position: MapPosition) => {
setMapCenterPosition(position);
};
// ここまで削除
const handleUpdateTogo = () => {
if (!location || !tag) {
return;
}
const updateTogoData: Togo = {
id: togoData.id,
done: togoData.done,
location,
tag,
position: mapMarkerPosition,
};
dispatch(updateTogo(updateTogoData));
handleCloseUpdateTogoModal();
};
AddTogoModal.tsxとUpdateTogoModal.tsxがすっきりして、コードの見通しもよくなったと思います。 今回は以上です。
これで、ToGoのリストの表示、追加、編集、削除が可能となったので、次回からは、Togo以外のところをつくっていきたいと思います。
コードはGitHubに置いてありますのでよければ参考にしてください。 mainブランチは常に最新のものになります。 今回の内容はblog_14のブランチを参照してください。 https://github.com/KINE-M/togo_app