0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【React Native】react-hook-formでフォーム作成

Last updated at Posted at 2025-04-21

react-hook-formとは

  • 状態管理やバリデーションの実装がシンプル:
    フォームの状態管理やバリデーションを自分で管理する必要がなく、
    難しいコードを書かなくても、フォームの入力値やエラー管理が行えます。
    バリデーション(入力チェック)も簡単な設定だけで実現できます。
  • 高速で軽い:
    大きなフォームでも、入力した部分だけ更新されるので、
    アプリが重くなりにくい。
  • TypeScript対応:
    入力値の型(データの種類)をしっかり管理できるので、
    型安全にフォームを扱える。
  • 他のライブラリとの親和性
    React Native Paperなど、
    他のデザインライブラリとも一緒に使える。

記事のきっかけ

react-hook-formの記事は多数ありますが、
React Nativeでの実装例がある記事を見かけなかったのと、
職業訓練学校の卒業制作でReact Nativeのアプリ作成をした際、
react-hook-formでフォーム作成したので、
記事作成を行おうと思った次第です。
(公式ドキュメントもReact向けのサンプルコードがほとんどのため)

  • 本記事ではUIコンポーネントでreact-native-paperを使っております
    (卒業制作で利用していたので)
  • react-native-paperの詳細については、割愛させていただきます
    (時間があれば別記事で書きたいと思います)

フォーム作成準備

1.パッケージインストール

npm install react-hook-form

実装例

Reactでは主にRegisterを利用するケースが多いと思いますが、
React Nativeでは、主にControllerを利用します。

hook_form_test.tsx
import { router } from "expo-router"
import { useState } from "react"
import { Controller, useForm } from "react-hook-form"
import { Alert, StyleSheet, View } from "react-native"
import { Button, HelperText, Text, TextInput } from "react-native-paper"

// フォームデータの型定義(TypeScriptの型安全)
type FormData = {
    email: string;
    password: string;
    confirmPassword: string;
}

/**
 * HookFormTestコンポーネント
 * 
 * このコンポーネントは、React Hook Formを使用してユーザー登録フォームを提供します。
 * フォームには、メールアドレス、パスワード、パスワード確認の3つのフィールドが含まれています。
 * - メールアドレス:必須、メールアドレス形式である必要があります。
 * - パスワード:必須、8文字以上で英大文字、英小文字、数字を含む必要があります。
 * - パスワード確認:必須、パスワードと一致する必要があります。
 * 
 * フォーム送信時には、入力データがコンソールに出力され、
 * `Alert`で送信成功メッセージが表示されます。
 * また、`reset`関数でフォームがリセットされます。
 * 
 * @returns ユーザー登録フォームを含むReact要素
 */
const HookFormTest = () => {
    // パスワードのマスク表示
    // 初期表示時ではマスク表示
    const [secureTextEntry, setSecureTextEntry] = useState(true)
    const [confirmSecureTextEntry, setConfirmSecureTextEntry] = useState(true)

    // React-Hook-Form初期設定
    const {
        control,   // 各入力値(TextInputなど)とフォームの状態を繋ぐ
        handleSubmit,  // フォーム送信時の処理をまとめてくれる。バリデーションも自動で実施
        watch,  // フォームの入力値をリアルタイムで監視。(例:パスワード確認欄でパスワードの入力値と一致しているかチェックしたい時に使います。)
        formState: { errors },  // 入力ミスやバリデーションエラーの情報が格納されている。どの入力欄でどんなエラーが起きているかを簡単に取得可能
        reset  // フォームの入力値を初期状態に戻す関数。送信後やリセットボタンで使います。
    } = useForm<FormData>({  // フォーム全体を管理するための関数。フォームで使うデータ型を指定(TypeScriptの型安全のため)
        defaultValues: {  // フォームの初期値を設定
            email: '',
            password: '',
            confirmPassword: ''
        }
    })

    // confirmPasswordのバリデーションのためにpasswordフィールドを監視
    const password = watch('password')

    /**
     * フォーム送信時の処理
     * フォームに入力された情報をconsoleに出力し、
     * `Alert`で送信成功メッセージを表示します。
     * また、`reset`関数でフォームを初期状態にリセットします。
     * 
     * @param data - フォームに送信されたデータ
     */
    const onSubmit = (data: FormData) => {
        console.log('フォーム送信データ:', data)
        // APIなどで呼び出してユーザー情報を登録
        Alert.alert('送信成功しました。')
        // 送信後にフォームの入力値をリセット
        reset()
    }

    return (
        <View style={styles.container}>
            <Text style={styles.title}>ユーザー登録</Text>
            {/* React NativeではControllerを利用する */}
            <Controller
                control={control}  // controlは「フォーム全体の状態管理」を担うオブジェクト。Controllerコンポーネントに渡すことで、フォームの入力値やエラーを管理できる。
                name='email'
                // バリデーションのルール設定(必須と正規表現)
                rules={{
                    required: 'メールアドレスは必須項目です',
                    pattern: {
                        value: /\S+@\S+\.\S+/,
                        message: 'メールアドレスの形式で入力してください'
                    }
                }}
                // 入力欄をどのように表示するかを定義する
                // field:フォームの入力欄(例:TextInput)と
                // react-hook-formの状態管理をつなぐための情報や関数がまとまったオブジェクト
                render={({ field: { onChange, onBlur, value } }) => (
                    <View style={styles.inputContainer}>
                        <TextInput
                            label='email'
                            mode='outlined'
                            keyboardType="email-address"
                            value={value}
                            onChangeText={onChange}
                            onBlur={onBlur}
                            error={!!errors.email}
                            autoCapitalize="none"
                            left={<TextInput.Icon icon="email" />}
                        />
                        {errors.email && (
                            <HelperText type="error">{errors.email.message}</HelperText>
                        )}
                    </View>
                )}
            />
            <Controller
                control={control}
                name="password"
                rules={{
                    required: 'パスワードは必須です',
                    minLength: {
                        value: 8,
                        message: 'パスワードは8文字以上入力してください'
                    },
                    pattern: {
                        value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/,
                        message: 'パスワードは英大文字、英小文字、数字を含む必要があります'
                    }
                }}
                render={({ field: { onChange, onBlur, value } }) => (
                    <View style={styles.inputContainer}>
                        <TextInput
                            label="password"
                            mode="outlined"
                            secureTextEntry={secureTextEntry}
                            value={value}
                            onChangeText={onChange}
                            onBlur={onBlur}
                            error={!!errors.password}
                            left={<TextInput.Icon icon='lock' />}
                            right={
                                <TextInput.Icon
                                    icon={secureTextEntry ? 'eye-off' : 'eye'}
                                    onPress={() => setSecureTextEntry(!secureTextEntry)}
                                />
                            }
                        />
                        {errors.password && (
                            <HelperText type="error">{errors.password.message}</HelperText>
                        )}
                    </View>
                )}
            />
            <Controller
                control={control}
                name="confirmPassword"
                rules={{
                    required: 'パスワード(確認)は必須です',
                    validate: (value) => value === password || 'パスワードが一致してません'
                }}
                render={({ field: { onChange, onBlur, value } }) => (
                    <View style={styles.inputContainer}>
                        <TextInput
                            label="password(confirm)"
                            mode="outlined"
                            secureTextEntry={confirmSecureTextEntry}
                            value={value}
                            onChangeText={onChange}
                            onBlur={onBlur}
                            error={!!errors.confirmPassword}
                            left={<TextInput.Icon icon='lock' />}
                            right={
                                <TextInput.Icon
                                    icon={confirmSecureTextEntry ? 'eye-off' : 'eye'}
                                    onPress={() => setConfirmSecureTextEntry(!confirmSecureTextEntry)}
                                />
                            }
                        />
                        {errors.confirmPassword && (
                            <HelperText type="error">{errors.confirmPassword.message}</HelperText>
                        )}
                    </View>
                )}
            />
            <View style={styles.buttonContainer}>
                <Button
                    mode='contained'
                    // handleSubmitでバリデーションチェックを行い、
                    // バリデーションOKならonSubmit関数が実行される。
                    // NGの場合はrulesで定義したメッセージが画面に表示されます。
                    onPress={handleSubmit(onSubmit)}
                    style={styles.button}
                >
                    送信
                </Button>
                <Button
                    mode='outlined'
                    onPress={() => router.back()}
                    style={styles.button}
                >
                    戻る
                </Button>
            </View>
        </View>
    )
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        paddingVertical: 48,
        paddingHorizontal: 16

    },
    title: {
        fontSize: 20,
        fontWeight: 'bold',
        marginBottom: 16
    },
    inputContainer: {
        marginBottom: 8
    },
    buttonContainer: {
        marginTop: 16
    },
    button: {
        marginBottom: 16,
        padding: 4
    }
})

export default HookFormTest

画面起動(Androidエミュレータ)

初期表示

スクリーンショット 2025-04-21 23.45.40.png

エラー発生時

スクリーンショット 2025-04-21 23.47.16.png

送信成功時

スクリーンショット 2025-04-21 23.49.59.png

送信後(リセット)

スクリーンショット 2025-04-21 23.52.01.png

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?