【ReactNative】react-hook-formでFCでフォームを使う

5 min read読了の目安(約5200字

はじめに

ReactReactNativeを開発していて避けられないのがFormだと思います。
このあたりはいくつかライブラリがあり、有名どころを使っておけばそんなに苦労はしない印象ですが、Hooksをゴリゴリ使っているとなると選択肢は狭まってきます。
今回はHooksを中心にコンポーネントを作成している場合に組み込みやすいreact-hook-formを紹介していきます。

react-hook-formとは

https://react-hook-form.com/jp/

公式を参照すると

高性能で柔軟かつ拡張可能な使いやすいフォームバリデーションライブラリ。

と謳われており、少ない記述量で高速で動作するフォームライブラリです。
フォーム関連の他ライブラリと動作速度やマウント数の比較があります。

React Hook Form

  • マウント数: 1
  • 変更コミット数: 1
  • 合計時間: 1800ms

Formik

  • マウント数: 6
  • 変更コミット数: 1
  • 合計時間: 2070ms

Redux Form

  • マウント数: 17
  • 変更コミット数: 2
  • 合計時間: 2380ms

たしかにこれを見る分には最小限のマウントで高速に動作するようです。

インストール

下記コマンドでインストールを行います。

yarn add react-hook-form

使い方

最もオーソドックな使い方は以下の通りです。
useFormが用意されているため、useStateuseEffectあたりをよく使っている場合は挙動が理解しやすいのかなと思います。

App.tsx
import React from "react";
import { useForm } from "react-hook-form";

// フォーム内のフィールド情報を定義
type Inputs = {
  example: string,
  exampleRequired: string,
};

const App: React.FC = () => {
  const { register, handleSubmit, watch, errors } = useForm<Inputs>();
  const onSubmit = data => console.log(data);

  // watch()で監視したいフィールドの値を指定
  console.log(watch("example"));

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* registerを使うことで入力した値をフックに登録 */}
      <input name="example" defaultValue="test" ref={register} />
      {/* 必須や入力ルールを指定することも可能 */}
      <input name="exampleRequired" ref={register({ required: true })} />
      {/* errorsには入力チェックで失敗した値が入ってくる */}
      {errors.exampleRequired && <span>This field is required</span>}
      <input type="submit" />
    </form>
  );
}

上記にある通り、関数コンポーネントの中にすんなり組み込むことができるため使い勝手がいい印象です。

ReactNativeにも対応

さらにreact-hook-formReactNativeで使用することもできます。
Reactの場合と異なりinputに対応するコンポーネントを自由に指定できますが、記述量は多くなります。
下記サンプルのようにControllerコンポーネントを配置し、renderに実際に表示させるコンポーネントを指定します。

App.tsx
import React from 'react';
import {Button, Text, TextInput, View} from 'react-native';
import {useForm, Controller} from 'react-hook-form';

// フォームの値を定義
type FormData = {
  name: string;
  zipCode: string;
};

const App: React.FC = () => {
  const {control, handleSubmit, errors} = useForm<FormData>();

  // submit時処理
  const onSubmit = (data: FormData) => {
    console.log(data);
  };

  return (
    <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
      <View
        style={{
          flexDirection: 'row',
          justifyContent: 'center',
          alignItems: 'center',
        }}>
        <View style={{flex: 0.2, alignItems: 'flex-end'}}>
          <Text>名前</Text>
        </View>
        <View style={{flex: 0.8}}>
          <Controller
            control={control}
            render={({onChange, onBlur, value}) => (
              <TextInput
                style={{
                  borderBottomWidth: 1,
                  borderBottomColor: '#ccc',
                  width: '80%',
                  fontSize: 20,
                  margin: '4%',
                }}
                placeholder="○○太郎"
                onBlur={onBlur}
                onChangeText={(value) => onChange(value)}
                value={value}
              />
            )}
            name="name"
            rules={{
              required: true,
              maxLength: 10,
            }}
            defaultValue=""
          />
          {errors.name && errors.name.type === 'required' && (
            <Text style={{color: 'red'}}>Nameは必須です。</Text>
          )}
          {errors.name && errors.name.type === 'maxLength' && (
            <Text style={{color: 'red'}}>
              Nameは10文字以内で入力してください。
            </Text>
          )}
        </View>
      </View>
      <View
        style={{
          flexDirection: 'row',
          justifyContent: 'center',
          alignItems: 'center',
        }}>
        <View style={{flex: 0.2, alignItems: 'flex-end'}}>
          <Text>郵便番号</Text>
        </View>
        <View style={{flex: 0.8}}>
          <Controller
            control={control}
            render={({onChange, onBlur, value}) => (
              <TextInput
                style={{
                  borderBottomWidth: 1,
                  borderBottomColor: '#ccc',
                  width: '80%',
                  fontSize: 20,
                  margin: '4%',
                }}
                placeholder="000-0000"
                onBlur={onBlur}
                onChangeText={(value) => onChange(value)}
                value={value}
              />
            )}
            name="zipCode"
            rules={{
              pattern: /^\d{3}-\d{4}$/,
            }}
            defaultValue=""
          />
          {errors.zipCode && errors.zipCode.type === 'pattern' && (
            <Text style={{color: 'red'}}>
              郵便番号のフォーマットが不正です。
            </Text>
          )}
        </View>
      </View>
      <Button title="Submit" onPress={handleSubmit(onSubmit)} />
    </View>
  );
};

export default App;

実行結果

react-hook-form

validationもしっかりかかってることが分かりますね。

まとめ

今回はreact-hook-formについて紹介しました。
Hooksが追加されて以降、新しく作るアプリはなるべくHooksを使ってコンポーネントを書くようにしていますが、そういった場合に扱いやすいライブラリとなっています。