【React/TypeScript】map関数で出力した入力フォームに対して動的バリデーションをかける

# 目次
1. はじめに
1. コード
1. 解説
1. 最後に

# はじめに
今回は下記のように問題がmap関数で表示されていて、数値の入力が想定されている場合に数値以外が入力された時に動的にエラーメッセージを出すことを想定しています。
![サンプル.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1549822/84a76b82-47a6-695f-bdcf-d2900c0d7b95.gif)

# コード
実装コードは下記になります。
```tsx
import React, { useState } from "react";
import { QUESTIONS3 } from "../const/question";
import { QuestionType } from "../types/question";

const QuestionPage = () => {
  const [errorIds, setErrorIds] = useState<number[]>([]);
  const questions: QuestionType[] = QUESTIONS3;

  function checkIsNumber(id: number, inputValue: number | string) {
    if (!inputValue) {
      return setErrorIds(errorIds.filter((errorId) => errorId !== id));
    }
    if (isNaN(Number(inputValue))) {
      return setErrorIds([...errorIds, id]);
    }
  }
  return (
    <form>
      <div className="form-group" style={{ display: "flex", justifyContent: "space-between" }}>
        {questions.map((q: QuestionType, index: number) => {
          return (
            <div key={index} style={{ width: "30%" }}>
              <div>
                問題{q.id}
                <br />
                {q.title}
              </div>
              {errorIds.includes(q.id) && (
                <div style={{ color: "red" }}>数値を入力してください</div>
              )}
              <input
                type="text"
                onChange={(e) => {
                  checkIsNumber(q.id, e.currentTarget.value);
                }}
              />
            </div>
          );
        })}
      </div>
    </form>
  );
};

export default QuestionPage;


```


# 解説

## useState
数値の配列で定義します。バリデーションエラーになっている要素番号(今回は問題番号)を格納します。
```react
const [errorIds, setErrorIds] = useState<number[]>([]);
```

## バリデーション用の関数
要素番号と入力された値を受け取り、制約を満たしているか判定します。
今回は数値であればOK、数値以外はNGという制約をとります。

```react
  function checkIsNumber(id: number, inputValue: number | string) {
    if (!inputValue) {
      return setErrorIds(errorIds.filter((errorId) => errorId !== id));
    }
    if (isNaN(Number(inputValue))) {
      return setErrorIds([...errorIds, id]);
    }
  }
```

その判定に今回は下記の判定式を利用します。これはNumber(value)でvalueを数値型に変換をする際に、値が変換できない場合はNaNを返す性質を利用しています。
今回は、引数の「inputValue」が数値か文字列を想定しているので、これがTrueになることは数値以外の文字列が入力されていることになります。
```ts
 isNaN(Number(inputValue)
```

また、下記の条件式は入力値を削除して空欄にした際に「errorIds」から対象要素番号を削除するものになります。
この条件がないと空欄にした際にもエラーメッセージが表示されたままになってしまいます。
```ts
    if (!inputValue) {
      return setErrorIds(errorIds.filter((errorId) => errorId !== id));
    }
```

## エラーメッセージ
問題の要素番号にerrorIdsが含まれている場合にエラーメッセージを表示させます。
```tsx
{
  errorIds.includes(q.id) && (
    <div style={{ color: "red" }}>数値を入力してください</div>
  );
}
```

## フォームイベント
onChangeイベントで入力内容が変化したときに、バリデーション用の関数を呼びます。
```tsx
<input
  type="text"
  onChange={(e) => {
    checkIsNumber(q.id, e.currentTarget.value);
  }}
/>
```

# 最後に
今回はuseStateを用いて、動的にバリデーションチェックを行いエラーメッセージを表示させる機能を実装しました。


調べている最中に[react-hook-form](https://react-hook-form.com/)というものを見つけたのでこちらを利用したらもっと簡単に実装できるかもしれません。

より良い方法や認識違いがありましたらコメントいただけると幸いです。

<a id="#reference"></a>
# 参考文献
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number

https://qiita.com/itachi/items/4184b2afc35b55b45568