Typescript×React×Hooksで会員管理③Formik とYupでフォームバリデーション

前回はTypescript×React×Hooks 及び Firebase Authentication を用いて作成している会員管理アプリに、Contextを活用した状態管理を導入し、アプリの規模拡大に対する耐性を強化しました。今回はFormikとYupを活用してフォームバリデーションを追加していきます。

全 3 回の内容は下記です。

1. [Firebase Auth で認証基盤外出し](https://qiita.com/mtitg/items/f725026e844ce84115ef)
2. [Context でアプリの状態管理](https://qiita.com/mtitg/items/3f1b6e61cee2f01f04a8)
3. **Formik と Yup でフォームバリデーション**

# 利用している技術要素

- Firebase Authentication
- Typescript
- React
- React Hooks
  - Context
- Material UI
- Formik 　**←NEW**
- Yup 　**←NEW**

# ソースコード

https://github.com/motoitanigaki/typescript-react-hooks-firebase_authentication-boilerplate/tree/%2Bvalidation

[前回との差分](https://github.com/motoitanigaki/typescript-react-hooks-firebase_authentication-boilerplate/commit/b540c07062326a4bc4eacea5ef762221d411e264) も一応貼っておきます。

# デモ

フォームバリデーションにより下記のような機能が追加されています。

- 必須項目のバリデーション
- 最小文字数のバリデーション
- バリデーションを通った（Valid）場合Submitボタンをクリックできるようになる

![demo2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113133/d6fc8104-61eb-45da-a783-98072d5a96e5.gif)

# React アプリのポイント解説

前回との差分中心に説明します。

いくつかライブラリが追加されていますが、これまで同様ソースコードからそのまま`yarn start`すれば動くと思います。

会員登録用の`Signup.tsx`とログイン用の`Login.tsx`それぞれほとんど同じような変更を加えているので、`Signup.tsx`を中心に見ていきます。

```tsx:Signup.tsx
import { Field, Form, Formik } from "formik";
// Material UIのために、formikのFormコンポーネントに渡す必要がある。 @material_ui/core にも同名のものがあり注意
import { TextField } from "formik-material-ui";
import React, { Fragment, useContext, useEffect } from "react";
import * as Yup from "yup";

import {
  Button,
  Container,
  FormControl,
  Grid,
  Link,
  Typography,
  LinearProgress
} from "@material-ui/core";

import { AuthContext } from "../Auth";
import auth from "../firebase";

// Yupでバリデーション用のスキーマを定義。Login.tsxでも利用するのでexportしている
export const AuthSchema = Yup.object().shape({
  email: Yup.string()
    .email()
    .required(),
  password: Yup.string()
    .min(6)
    .required()
});

const Signup = (props: any) => {
  const { currentUser } = useContext(AuthContext);

  useEffect(() => {
    currentUser && props.history.push("/");
  }, [currentUser]);

  return (
    <Fragment>
      <Container>
        <Grid container>
          <Grid item md={4}></Grid>
          <Grid item md={4}>
            <Formik
              // フォームの初期値を定義
              initialValues={{ email: "", password: "" }}
              // Yupで定義したスキーマを利用
              validationSchema={AuthSchema}
　　　　　　　　 // フォームSubmit時のイベントを定義。Firebase APIを呼んでいる
              onSubmit={async value => {
                try {
                  await auth.createUserWithEmailAndPassword(
                    value.email,
                    value.password
                  );
                  props.history.push("/login");
                } catch (error) {
                  alert(error.message);
                }
              }}
              // フォームの描画を定義。今回はFormikで持つオブジェクトを3つ渡してフォーム内で利用している
　　　　　　　　 // submitForm：上記で定義したフォームSubmit時のイベント
　　　　　　　　 // isSubmitting：Submit中かどうかの状態判定
　　　　　　　　 // isValid：フォームがValidでSubmit可能化の状態判定
              render={({ submitForm, isSubmitting, isValid }) => (
                <Form>
                  // Submit中にプログレスバーを表示
                  {isSubmitting && <LinearProgress />}
                  <FormControl margin="normal" fullWidth>
                    <Field
                      style={{ marginTop: "0.5em", marginBottom: "0.5em" }}
                      name="email"
                      label="E-mail"
                      fullWidth
                      variant="outlined"
                      component={TextField}
                    />
                  </FormControl>
                  <FormControl fullWidth>
                    <Field
                      style={{ marginTop: "0.5em", marginBottom: "0.5em" }}
                      name="password"
                      label="Password"
                      fullWidth
                      variant="outlined"
                      type="password"
                      component={TextField}
                    />
                  </FormControl>
                  <FormControl fullWidth>
                    <Button
                      fullWidth
                      // Submit時のイベントを紐付け
                      onClick={submitForm}
                      style={{ marginTop: "0.5em", marginBottom: "0.5em" }}
                      type="submit"
                      // フォームがValidでない or Submit中の場合、SubmitボタンをDisabledにする
                      disabled={!isValid || isSubmitting}
                    >
                      Sign up
                    </Button>
                    <Typography align="center">
                      <Link href="/login">to login</Link>
                    </Typography>
                  </FormControl>
                </Form>
              )}
            />
          </Grid>
          <Grid item md={4}></Grid>
        </Grid>
      </Container>
    </Fragment>
  );
};

export default Signup;
```

コード内にコメントを書いたので、読めば何をしているかわかるかと思います。

Material UIとFormikをあわせて利用している例があまりなかったので、動かすまで苦労しました。特にフォームの各フィールドにはformikが提供するFormを利用し、component属性にformik-material-uiが提供するTextFieldを渡してあげる部分がわかりにくかったのを記憶しています。

# 最後に

3回にわけて、Typescript×React×Firebase Authenticationで会員管理ができるアプリを作り、その上に機能追加をしていけるような骨組みを構築しました。

より本格的なアプリケーションにするためには、例えば下記要素なんかが考慮されると思います。

- APIコール
- 複数言語対応
- ヘッダーやフッターといったパーツの設計
- ロギング
- テスト
- ...

こう考えるとやはりアプリケーション開発は奥が深いですね。日々精進です。