React hookで気持ちよく入力チェック〜エラー判定を行う

突然ですがこういうフォームっていいですよね！

- 入力が終わると、エラーかどうかを判定してエラー文を表示してくれる
- 正しい入力に戻ると、エラー文が消える

gifにするとこういう感じ！

![comp.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106711/71df1ef6-9fa2-83ed-deee-4775af4b0589.gif)

シンプルですが、「どういう風に書けばこうなるか」は案外知らないと作れないと思います

今回はこういうフォームの作り方をさくっとまとめます

# 要点をまとめると
- `onChange`で、「入力値」を`useState`へ保存する
- `onBlur`で、「入力値を判定したエラー文」を`useState`へ保存する

これがわかればできます！

# 実装

では書いていきます

まずHTMLですが、「ラベル（はなくてもいいけど）」「入力欄」「エラー文を出す場所」があればいいです！

```html
<div>
  <label>unique name</label>
  <input
    type="text"
    name="name"
    placeholder="Enter your name"
  />
  <p>error</p>
</div>
```

次にここへuseStateのロジックを混ぜていきましょう！

```jsx
const [name, setName] = useState('')
const [nameError, setNameError] = useState('')

return (
  <div>
    <label>unique name</label>
    <input
      type="text"
      name="name"
      placeholder="Enter your name"
      value={name}
      onChange={(e) => {
        setName(e.target.value)
      }}
    />
    {nameError && <p>{nameError}</p>}
  </div>
)
```

一気に増えましたが、大事なところだけ解説します！

まずuseStateで、必要なパラメーター「入力値」「入力値エラー」を作ります

```javascript
const [name, setName] = useState('')
const [nameError, setNameError] = useState('')
```

inputタグのvalueとonChange属性にnameとsetNameを渡すことで、useStateの値と、入力フォームの中身を一致させることができます

```jsx
<input
  type="text"
  name="name"
  placeholder="Enter your name"
  value={name}
  onChange={(e) => {
    setName(e.target.value)
  }}
/>
```

次にエラー文。ちょっと慣れない書き方かもしれませんが、こういう風に書くと「nameErrorがfalseじゃない時」つまり、エラー文が存在する時だけ、うしろのpタグの中身を出してくれます

jsxならではの書き方ですね

```jsx
{nameError && <p>{nameError}</p>}
```

最後の仕上げです！

onBlur属性の関数を定義して、入力フォームから離れた時に、入力値を判定して、エラー文を表示できるようにします

```jsx
const [name, setName] = useState('')
const [nameError, setNameError] = useState('')

const handleBlur = (e) => {
  const name = e.target.value
  if (!name) {
    setNameError('required')
  } else if (name.length < 5) {
    setNameError('should longer than 5')
  } else {
    setNameError()
  }
}

return (
  <div>
    <label>unique name</label>
    <input
      type="text"
      name="name"
      placeholder="Enter your name"
      value={name}
      onChange={(e) => {
        setName(e.target.value)
      }}
      onBlur={handleBlur}
    />
    {nameError && <p>{nameError}</p>}
  </div>
)
```

onBlurは他のところをクリックした時など、入力を中断した時だけ走ってくれるので、入力中には動作しません

また嬉しい点として、入力途中でボタンをクリックしても、ボタンのクリックイベントより先にonBlurが走ってくれるので、うっかり変なデータを送信することもありません！

![main2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106711/f3abed3f-0f20-9bf1-f52a-828f084c1e96.gif)

ボタンも一緒に作る場合は、disabledを設定しておくと良いでしょう。（nameErrorが存在する時にdisabledがtrueになるようにする）

```jsx
const [name, setName] = useState('')
const [nameError, setNameError] = useState('')

const handleBlur = (e) => {
  const name = e.target.value
  if (!name) {
    setNameError('required')
  } else if (name.length < 5) {
    setNameError('should longer than 5')
  } else {
    setNameError()
  }
}

return (
  <div>
    <label>unique name</label>
    <input
      type="text"
      name="name"
      placeholder="Enter your name"
      value={name}
      onChange={(e) => {
        setName(e.target.value)
      }}
      onBlur={handleBlur}
    />
    {nameError && <p>{nameError}</p>}
  </div>
  <div>
    <button disabled={nameError}>Save</button>
  </div>
)
```

# おまけ：入力値が空の時の`e.target.value`

...へは「空文字」が渡ってきます。

というより基本的には`e.target.value`は、数字を書いたとしても、基本全て文字列で渡ってきます（`e.target.valueAsNumber`というのもありますが、それはまた別の話ということで...）

つまり「文字列型」なので、今回のようなnameと行った文字列の入力値なら良いのですが、数字を扱いたい場合は都合が悪いです

なので、そういう場合はonChangeで仲介してあげましょう！

入力値が数字の場合の「空」をどう表現するかは人によると思うのですが、私は`undefined`を使っています！

```jsx
// 年齢のフォーム

<input
  type="number"
  name="age"
  placeholder="Enter your age"
  value={age}
  onChange={(e) => {
    if (e.target.value === '') {
      setAge(undefined)
    } else {
      // 入力値を数値へ変換！
      setAge(Number(e.target.value))
    }
  }}
/>
```

# 終わりに

今回はuseStateとinputのイベントハンドラを使って、エラー文を綺麗に出すフォームを作ってみました！

CSSやアニメーションをもっと付ければ、より洗練された感じになるので、是非やってみてください！