【Typescript + React】ZodとMaterial UIを使って入力のバリデーションを実装する

## 今回実装するもの
![画面収録-2023-10-29-19.46.33.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576870/9829a268-d259-056a-329a-48a24a386026.gif)

validationはzodで、UIはmaterial UIを使っていきます。

## ライブラリインストール
- [zod](https://github.com/colinhacks/zod)(型検証ライブラリ)
- [Material UI](https://mui.com/material-ui/getting-started/installation/)(UIフレームワーク)
```
npm install zod
npm install @mui/material @emotion/react @emotion/styled
```

Material UIはreactのUIフレームワークです。
[公式ドキュメント](https://mui.com/material-ui/getting-started/installation/)に色々UIコンポーネントの例が掲載されていて、コピペするだけできれいなUIが実装できてとても便利です。

zodは型検証ライブラリです。
zodに関して詳しく知りたい方はこの記事が勉強になると思います。

https://zenn.dev/uttk/articles/bd264fa884e026

## 実装
### UIの実装
まずはUIを実装してしまいましょう。
material UIコンポーネントを使って雑に実装します。

```App.tsx
import { Box, Button, TextField } from '@mui/material';

function App() {

  return (
    <>
      <Box textAlign={'center'} margin={'20px 0'}>
        <TextField
          label="名前"
          variant="outlined"
          required
        />
        <Box>
          <Button>提出</Button>
        </Box>
      </Box>
      
    </>
  );
}
```

これで以下のようになると思います。

![スクリーンショット 2023-10-29 20.07.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576870/ce3cc3ae-007b-fc77-b8de-5f8c66d49688.png)

以下がそれぞれのオプションの説明です。

 | オプション | 概要 |
 | --- | --- | 
 | label | 表示するラベルの設定 | 
 | variant | 表示デザインの設定(詳しくは公式ドキュメント参照) | 
 | required | 必須項目かの設定(これをtrueにすると * がつきます)

 ### user変数の定義

 それではここで入力した値を変数に束縛します。


 ```App.tsx
 import { Box, Button, TextField } from '@mui/material';
import { ChangeEvent, useState } from 'react';
import {z} from 'zod'

const User = z.object({
  name: z.string().min(1)
})
type User = z.infer<typeof User>

function App() {

  const inputHandler = (event: ChangeEvent<HTMLInputElement>) => {
    setUser({
      name: event.target.value
    })
  }

  const [user, setUser] = useState<User>()


  return (
    <>
      <Box textAlign={'center'} margin={'20px 0'}>
        <TextField
          autoComplete='off'
          label="名前"
          variant="outlined"
          required
          onChange={inputHandler}
        />
        <Box>
          <Button onClick={submitHandler}>提出</Button>
        </Box>
      </Box>
      
    </>
  );
}

export default App;

 ```

 ここで、zodがでてきました。
 今はnameしかないですが、実際使う時はemailやpasswordなどの他の要素もあるかもなので、User型はobject型にしてみています。
 そして、nameは1文字以上のstring型に設定しています。

 あとは onChangeでTextFieldに値が入力されたら発火するhandlerを作ってその中でsetUserを用いて`e.target.value`をuserに束縛しました。

 ### バリデーションの実装
 最後にバリデーションを実現します。
 先ほど、User型に変数を束縛したのであとは先ほど入れた値がUser型に一致しているか検証してあげればいいだけです。

 ```App.tsx
 import { Box, Button, TextField } from '@mui/material';
import { ChangeEvent, useState } from 'react';
import {z} from 'zod'

const User = z.object({
  name: z.string().min(1)
})
type User = z.infer<typeof User>

function App() {
  const inputHandler = (event: ChangeEvent<HTMLInputElement>) => {
    setUser({
      name: event.target.value
    })
  }

  const submitHandler = () => {
    const parseResult = User.safeParse(user)
    if (!parseResult.success) {
      setError(true)
    }else{
      setError(false)
    }
  }

  const [user, setUser] = useState<User>()
  const [error, setError] = useState<boolean>()

  return (
    <>
      <Box textAlign={'center'} margin={'20px 0'}>
        <TextField
          autoComplete='off'
          label="名前"
          variant="outlined"
          required
          name="name"
          onChange={inputHandler}
          helperText={error ? '1文字以上入力してください' : ''}
          error={error}

        />
        <Box>
          <Button onClick={submitHandler}>提出</Button>
        </Box>
      </Box>
      
    </>
  );
}

export default App;
 ```

 buttonをクリックするとsubmitHandlerが発火して、zodの型検証をしてくれます。
 
 zodで定義した型に対して`safeParse`してあげると、パース結果が`success`にてbooleanで渡されます。
 (User.parseとすることもできますが、これはパースに失敗するとエラーを投げるのでtry, catchなどで例外処理が必要になります)

 こうすることによって、パースに失敗したらerrorフラグをtrueにします。

 そして、TextFieldの中でerrorがtrueのときにhelperTextを表示して、errorオプションをtrueに設定します。

 errorオプションをtreuにすることで枠が赤くなってくれます。Material UI様様です。

 helperTextは追加テキストを表示してくれます。Material UI様様です。

 これで、validationを実装することができました！

 ## 終わりに
 今回はこんな感じでバリデーションを実装してみました。
 こんな書き方のほうがかっこいいとか、こんな方法もあるとか、ここ間違えてるよとかご意見ある方いらっしゃったらコメントください。