React サクッと登録変更画面

よく登録、変更兼用フォーム画面を作成するのでメモしておく

#### フォームのベース

```ScheduleForm.tsx
import './App.css';
import React, { useEffect, useState } from 'react';
import DatePickerComponent from './components/DatePickerComponent';
import { callGetScheduleApi } from './models/ApiClient';

/**
 * フォーム入力項目
 */
interface Form {
  id: number | null
  title: string
  place: string
  startDate: Date
  endDate: Date
  memo: string
}


const ScheduleForm = () => {
  // urlパラメータにidがある場合は更新画面、そうでない場合は登録画面とする
  const updateFlag = document.location.search.startsWith("?id=") ? true : false;

  // urlパラメータにid指定がある場合はidに紐づくスケジュールデータを取得しフォームにセットする
  useEffect(() => {
    if (updateFlag) {
      const id = document.location.search.replace("?id=", "");
      // 実際はAPIコールしてスケジュールデータを取得
      getSchedule(Number(id));
    }
  }, [])

  /**
   * スケジュール取得
   * @param id 
   */
  const getSchedule = async(id: number) => {
    const result = await callGetScheduleApi(Number(id));
      setFormValue({
        id: result.id,
        title: result.title,
        place: result.place,
        startDate: new Date(result.startDate),
        endDate: new Date(result.endDate),
        memo: result.memo
      });
      console.log(result)
  }
  

  /** 入力フォーム初期値 */
  const [formValue, setFormValue] = useState<Form>({
    id: null,
    title: "",
    place: "",
    startDate: new Date(),
    endDate: new Date(),
    memo: ""
  });

  /** 開始日コンポーネント引数 */
  const startDateProps = {
    date: formValue.startDate,
    onChange: (date: Date) => setFormValue({...formValue, startDate: date})
  }

    /** 終了日コンポーネント引数 */
    const endDateProps = {
      date: formValue.endDate,
      onChange: (date: Date) => setFormValue({...formValue, endDate: date})
    }


  return (
    <div className="container m-4">
      <div className="row mb-4">
        <div className="col fs-4">
          {updateFlag ? "スケジュール変更" : "スケジュール登録"}
        </div>
      </div>
      <div className="row mb-3">
        <div className="col-2 font_xxs">
          タイトル
        </div>
        <div className="col-4">
          <input className="form-control" type="text" defaultValue={formValue.title}/>
        </div>
      </div>
      <div className="row mb-3">
        <div className="col-2 font_xxs">
          場所
        </div>
        <div className="col-4">
          <input className="form-control" type="text" defaultValue={formValue.place}/>
        </div>
      </div>
      <div className="row mb-3">
        <div className="col-2 font_xxs">
          開始〜終了
        </div>
        <div className="col-2 me-0">
          <DatePickerComponent {...startDateProps}/>
        </div>
        <div className="col-1 mx-0 text-center">
          〜
        </div>
        <div className="col-2 ms-0">
          <DatePickerComponent {...endDateProps}/>
        </div>
      </div>
      <div className="row">
        <div className="col-2 font_xxs">
          メモ
        </div>
        <div className="col-4">
          <textarea defaultValue={formValue.memo}/>
        </div>
      </div>
    </div>
  );
}

export default ScheduleForm;
```

#### DatePickerコンポーネント

[先日作成したコンポーネント](https://qiita.com/buto/items/c6e875aeb4d2c7e25969)を呼び出し元から引数を渡せるよう改修しました

```DatePickerComponent.tsx
import React from "react";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css"

interface Props {
    date: Date
    onChange: (date: Date) => void
}

const DatePickerComponent = ({date = new Date(), onChange}: Props) => {

    return (
        <DatePicker 
            dateFormat="yyyy/MM/dd HH:mm"
            selected={date} 
            onChange={date =>{
                if (date) {
                    onChange(date);
                }
            }}/>
    )
}
export default DatePickerComponent;
```

#### APIクライアント

API呼び出し処理はtsxファイルに書くとゴチャゴチャしがちなので別ファイルに書きます

```ApiClient.ts
interface Schedule {
    id: number | null
    title: string
    place: string
    startDate: string
    endDate: string
    memo: string
  }

// テストデータ
const testSchedule: Schedule = {
    id: 1234,
    title: "アドベントカレンダー書く",
    place: "どこでも",
    startDate: "2022/12/10 19:00:00.000+09:00",
    endDate: "2022/12/15 23:00:00.000+09:00",
    memo: "メモメモ"
  }

/**
 * スケジュール取得API呼び出し
 * @param id 
 * @returns スケジュール
 */
export const callGetScheduleApi = async(id: number): Promise<Schedule> => {
    return await fetch(`https://sampleapi221206.com/api/schedules/${id}`).then(result => {
        return result.status === 200 ? result.json() : testSchedule;
    }).catch(() => {
        return testSchedule;
    });
}
```
#### こんな感じになります

ローカルで上記コードを起動すると登録画面が表示されます
![スクリーンショット 2022-12-06 21.20.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530205/c7fe0f32-c7c8-5193-3fe2-ed2f25d46fc3.png)

またURLパラメータ `?id=xxxx` を付与すると変更画面が表示されます
![スクリーンショット 2022-12-06 21.21.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530205/51a69425-2f8c-cf32-3acf-8ffa0a1eb59e.png)