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