前回までの記事 アプリ開発を楽しむ【#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