ReactのFormikを実装する

## はじめに
表題通り、ReactでFormikを実装します。

## 要件
・Validationチェックが実施
・必須項目が未入力の場合、送信ボタンが非活性
・送信ボタン押下後、POST通信が走る。

## 成果物

![スクリーンショット 2023-11-01 23.17.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/3014bbda-ea4a-793a-91c3-21ed24f23546.png)

https://github.com/kouji0705/react_formik

```shell:ディレクトリ
├── src
│   ├── App.tsx
│   ├── Form
│   │   ├── Field.tsx
│   │   ├── UserFormFileds.tsx
│   │   ├── const.ts
│   │   ├── hooks.ts
│   │   ├── index.tsx
│   │   ├── types.ts
│   │   └── validation.ts
│   ├── api
│   │   └── api.ts
│   ├── index.tsx
│   └── logo.svg
```

### プログラム
```tsx:App.tsx
import React from 'react';
import { UserForm } from './Form';
import { useMyFormik } from './Form/hooks';
import { UserFormFields } from './Form/UserFormFileds';

function App() {
  // Form情報を取得
  const formik = useMyFormik();
  return (
    <div>
      <h1>Formikサンプル</h1>
      <UserForm formik={formik}>
        <UserFormFields formik={formik} />
      </UserForm>
    </div>
  );
}

export default App;
```

```tsx:src/Form/index.tsx
import { FormikProps } from 'formik';
import React from 'react';
import { UserFormValues } from './types';

export const UserForm: React.FC<UserFormProps> = (props) => {
  const { formik, children } = props;
  return (
    <form onSubmit={formik.handleSubmit}>
      {children}
      <button type="submit" disabled={!formik.isValid}>
        送信
      </button>
    </form>
  );
};

type UserFormProps = {
  formik: FormikProps<UserFormValues>; // UserFormValuesは入ってくる型を定義
  children: React.ReactNode;
};

```

```tsx:src/Form/UserFormFileds.tsx
import React from 'react';
import { FormikProps } from 'formik';
import { UserFormValues } from './types';
import { Field } from './Field';

type UserFormFieldsProps = {
  formik: FormikProps<UserFormValues>;
};

export const UserFormFields: React.FC<UserFormFieldsProps> = ({ formik }) => {
  return (
    <div>
      <Field formik={formik} fieldName="name" label="名前" type="text" />
      <Field
        formik={formik}
        fieldName="email"
        label="メールアドレス"
        type="email"
      />
    </div>
  );
};

```

```tsx:src/Form/Field.tsx
import React from 'react';
import { FormikProps } from 'formik';
import { UserFormValues } from './types';

type FieldProps = {
  formik: FormikProps<UserFormValues>;
  fieldName: keyof UserFormValues; // keyofでUserFormValuesのプロパティ名を取得
  label: string;
  type: string;
};

export const Field: React.FC<FieldProps> = ({
  formik,
  fieldName,
  label,
  type,
}) => {
  const fieldError = formik.errors[fieldName];
  const fieldValue = formik.values[fieldName];
  const fieldTouched = formik.touched[fieldName];

  return (
    <div>
      <label htmlFor={fieldName}>{label}</label>
      <input
        type={type}
        id={fieldName as string}
        name={fieldName as string}
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
        value={fieldValue}
      />
      {fieldTouched && fieldError && <div>{fieldError}</div>}
    </div>
  );
};

```

```tsx:src/Form/hooks.ts
import { useFormik } from 'formik';
import { useEffect } from 'react';
import { postApi } from '../api/api';
import { validationSchema } from './validation';
import { UserInitialValues } from './const';

export function useMyFormik() {
  const formik = useFormik({
    initialValues: UserInitialValues,
    validationSchema,
    onSubmit: async (values) => {
      try {
        const response = await postApi({
          title: values.name, // 名前をタイトルとして使用
          body: values.email, // メールアドレスを本文として使用
          userId: 1, // ユーザーIDを適切に設定
        });
        // レスポンスをコンソールに出力
        console.log('APIレスポンス:', response.data);
      } catch (error) {
        console.error('APIエラー:', error);
      }
    },
  });

  useEffect(() => {
    formik.validateForm(); // フォームが最初にロードされたときにバリデーションを実行
  }, []); // 空の依存リストを指定して一度だけ実行されるように

  return formik;
}

```

```tsx:src/Form/const.ts
export const UserInitialValues = {
  name: '',
  email: '',
};
```

```tsx:src/Form/types.ts
export type UserFormValues = {
    name: string;
    email: string;
};
```

```tsx:src/Form/validation.ts
import * as Yup from 'yup';

export const validationSchema = Yup.object({
  name: Yup.string().required('名前は必須項目です'),
  email: Yup.string()
    .email('有効なメールアドレスを入力してください')
    .required('メールアドレスは必須項目です'),
});
```

```ts:src/api/api.ts
import axios from "axios"


const apiUrl = "https://jsonplaceholder.typicode.com/posts";

export const postApi = async (payload:any) => {
    const response = await axios.post(apiUrl, payload);
    return response;
}
```

→Formikの難しいと感じる点は、`useFormik`。onSubmitが結局いつ発火するのか。submitされた時に発火するのだが、フロント実装した事がないとonSubmitを追うけど、どこで使ってるの？ってところがわからない。上のサンプル実装だと以下の`submit`された時に発火する。

```tsx:
      <button type="submit" disabled={!formik.isValid}>
        送信
      </button>
```