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


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

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

  1. Firebase Auth で認証基盤外出し
  2. Context でアプリの状態管理
  3. Formik と Yup でフォームバリデーション

利用している技術要素

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

ソースコード

前回との差分 も一応貼っておきます。

デモ

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

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

React アプリのポイント解説

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

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

会員登録用のSignup.tsxとログイン用のLogin.tsxそれぞれほとんど同じような変更を加えているので、Signup.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コール
  • 複数言語対応
  • ヘッダーやフッターといったパーツの設計
  • ロギング
  • テスト
  • ...

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