【React Native】 react-hook-form と yupを使ってフォームのバリデーションチェック

2022.07.31

こんにちは、こんばんわ。 「クラスメソッドにジョインして、知識をいっぱい吸収出来て喜びを感じています」 CX 事業本部 Delivery 部 MAD グループ@札幌の hiro です。

今回も、前回の React Native 記事に味を占めて、React Native 連投します。

はじめに

React Native でネイティブアプリを作成し、フォーム画面を作った時にフォームのバリデーション処理を入れなければ、 と思ったときにやった内容を記述します。 フォームのバリデーション処理は、 Formikなどもありますが、

今回は、 react-hook-formyupを 組み合わせてバリデーション処理を実施していきました。

内容

React Native の環境は良しなに、作成よろしくお願いいたします! 以下記事など参考にしつつ、React Native for Webを利用して環境を構築しました。 大変参考になるので、一読を!

Reactが分かれば難しくない! React Native for Web 入門

ライブラリのインストール

今回必要になるライブラリのインストールをします。

  • React Hook Form
yarn add react-hook-form
  • yup
yarn add yup

動作確認

以下のように、条件を入力するとバリデーションチェックをしてくれるようになっています。

フォーム部分

今回は、ログインフォームのバリデーション処理を加える前提で記述します。

◇ ファイル構成

以下のファイル構成で、記述します。

src
┗ App.tsx・・・・・・・・ログインなどをする際のフォーム画面の表示
┣ component/
┃ ┗ TestInputComponent.tsx・・・フォーム入力部分の表示
┗ schema/
      ┗ testInputSchema.ts・・・・・バリデーション処理管理部分

◇ フォーム画面部分

簡単にログイン画面表示部分を以下のように記述しています。 hudleSumitが実行されることでバリデーションチェック処理が走ります。

ここで使用されている「react-hook-form」の「Control」や「useForm」などは、 以下のリンクにより詳細に記述されています。

react-hook-formのAPI一覧

以下が実際のファイル内容になります。

App.tsx

import React from "react";
import { View, Text, StyleSheet, Pressable } from "react-native";
import { useForm, Control, FieldValues, SubmitHandler, SubmitErrorHandler } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { TestInputComponent } from "./component/TestInputComponent";
import { testInputSchema } from "./schema/testInputSchema";

type FormDataInfo = {
  email: string;
  password: string;
};

export const App = () => {
  const { control, handleSubmit } = useForm<FormDataInfo>({
    resolver: yupResolver(testInputSchema),
  });
  const onSubmit: SubmitHandler<FormDataInfo> = (data: any, e: any) => console.log(data, e);
  const onError: SubmitErrorHandler<FormDataInfo> = (errors: any, e: any) => console.log(errors, e);

  return (
    <View style={styles.container}>
      <TestInputComponent
        control={control as unknown as Control<FieldValues>}
        areaName="email"
        label="メールアドレス"
        placeholder="メールアドレス"
        autoCompleteType="email"
        autoCapitalize="none"
        style={styles.input}
      />
      <TestInputComponent
        control={control as unknown as Control<FieldValues>}
        areaName="password"
        label="パスワード"
        placeholder="パスワード"
        autoCompleteType="password"
        secureTextEntry
        style={styles.input}
      />
      <Pressable
        style={styles.button}
        onPress={() => handleSubmit(onSubmit, onError)()}
      >
        <Text style={styles.text}>BUTTON</Text>
      </Pressable>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginHorizontal: 100,
    paddingTop: 22,
  },
  input: {
    shadowOpacity: 0.5,
    shadowRadius: 3,
    shadowOffset: {
      height: 0,
      width: 0,
    },
    elevation: 2,
    marginTop: 30,
    paddingVertical: 20,
    paddingHorizontal: 20,
  },
  button: {
    marginTop: 30,
    paddingVertical: 20,
    alignItems: "center",
    justifyContent: "center",
    paddingHorizontal: 32,
    borderRadius: 4,
    elevation: 3,
    backgroundColor: "blue",
  },
  text: {
    fontSize: 16,
    lineHeight: 21,
    fontWeight: "bold",
    letterSpacing: 0.25,
    color: "white",
  },
});

export default App;

◇ フォーム入力部分

次にフォーム入力表示部分を以下のように記述しています。 入力された値を参考に、バリデーションエラーが発生した際の状況などを表示するようにしています。

以下が実際のファイル内容になります。

TestInputComponent.tsx

import React from "react";
import {
  TextInput,
  TextInputProps,
  Text,
  View,
  StyleSheet,
} from "react-native";
import {
  Controller,
  Control,
  DeepMap,
  FieldValues,
  FieldError,
} from "react-hook-form";

interface Props extends TextInputProps {
  control: Control<any>;
  areaName: string;
  defaultValue?: any;
  label: string;
  autoCompleteType: string;
}

export const TestInputComponent: React.FC<Props> = ({
  control,
  areaName,
  defaultValue,
  ...props
}) => {
  return (
    <Controller
      control={control}
      name={areaName}
      defaultValue={defaultValue}
      render={({
        field: { onChange, value, onBlur, name },
        formState: { errors },
      }) => (
        <View>
          <TextInput
            // このpropsにautoCompleteTypeなど諸々乗っかってくる
            {...props}
            value={value || ""}
            onBlur={onBlur}
            onChangeText={onChange}
          />
          {/* バリエーションエラー表示 */}
          {errors[name] && (
            <Text style={styles.text}>
              {(errors[name] as DeepMap<FieldValues, FieldError>)?.message}
            </Text>
          )}
        </View>
      )}
    />
  );
};

const styles = StyleSheet.create({
  text: {
    color: "red",
  },
});

◇ バリデーション処理部分

次にバリデーション処理部分を以下のように記述しています。 よくあるバリデーションの内容もコメントアウトして記述しています。 パスワードが一致しないことなどなど。

yup.object()と.shape()は以下のような感じです。

- yup.object()
バリデーション判定対象の入力値が、オブジェクトで提供されることを期待する定義

- .shape()
バリデーションしたいデータ構造を.shape()で定義

以下が実際のファイル内容になります。

testInputSchema.ts

import * as yup from "yup";

// スキーマを定義
// inputのvalue値のエラー条件を定義
export const testInputSchema = yup.object().shape({
  email: yup
    .string()
    .lowercase()
    .email("正しいメールアドレスを入力してください。")
    .required("メールアドレスは必須項目です。"),
  password: yup
    .string()
    .matches(/(?=.*[a-z])/, "小文字を含めてください。")
    .matches(/(?=.*[A-Z])/, "大文字を含めてください。")
    .matches(/(?=.*[0-9])/, "数字を含めてください。")
    .min(8, "最低8文字含めてください。")
    .required("パスワードは必須項目です。"),

   /**
    * よくある他のバリデーションチェック例
    */
   // メールアドレス確認用の入力をチェックする用
   //checkEmail: yup
   // .string()
   // .lowercase()
   // .email('正しいメールアドレスを入力してください。')
   // .test('emails-match', '入力されたメールアドレスが一致しません。', function (value) {
   //   return value === this.parent.email;
   // })
   // .required('メールアドレス確認は必須項目です。'),

   // かな入力をチェックする用
   // nameKana: yup
   // .string()
   // .max(25, '最大25文字です。')
   // .test('katakana-checker', 'カタカナで入力して下さい。', function (value: any) {
   //   return !!value.match(/^[ァ-ヶー ]*$/);
   // })
   // .required('氏名(カナ)は必須項目です。'),
});

おわりに

バリデーションチェックのライブラリはいくつかありますが、今回は react-hook-formyupを選択して、組み合わせた内容を記述しました。

ありがとうございました。