🥷

[React Native] rect-hook-formとvalibotでバリデーションを実装する

に公開

はじめに

この記事では、React Nativeでreact-hook-formとvalibotを使用して簡単にフォームバリデーションを実装する方法について解説します。
今回はよくあるログイン画面で使われそうなバリデーションを実装していきます。

完成した動画

バリデーションエラーの例 成功した例

プロジェクトのセットアップ

まず、必要なパッケージをインストールします。

  • valibot
npm install valibot
  • react-hook-formとresolvers
npm install @hookform/resolvers react-hook-form

Schemaの作成

Schemaを作成します。
名前とメールとパスワードを入力するフォームのバリデーションを作成します。

import * as v from "valibot";

export const LoginSchema = v.object({
	name: v.pipe(v.string(), v.nonEmpty("This field is required")),
	email: v.pipe(v.string(), v.nonEmpty("This field is required"), v.email("The email address is not valid")),
	password: v.pipe(
		v.string(),
		v.nonEmpty("This field is required"),
		v.minLength(8, "The password must be at least 8 characters long"),
	),
});

このスキーマでは以下のバリデーションルールを定義しています:

  • name: 文字列で、空文字列は許可しない
  • email: 文字列で、空文字列は許可せず、有効なメールアドレス形式であることを確認
  • password: 文字列で、空文字列は許可せず、最小8文字以上であることを確認

これだけでバリデーションを行うことができます。

フォームコンポーネントの作成

フォームコンポーネントを作成します。

全体のコード
import { LoginSchema } from "@/schema/form";
import { valibotResolver } from "@hookform/resolvers/valibot";
import { Controller, useForm } from "react-hook-form";
import { KeyboardAvoidingView, StyleSheet, Text, TextInput, TouchableOpacity, View } from "react-native";

export default function Login() {
	const {
		control,
		handleSubmit,
		formState: { errors, isValid },
	} = useForm({
		resolver: valibotResolver(LoginSchema),
		mode: "onBlur",
		defaultValues: {
			name: "",
			email: "",
			password: "",
		},
	});
	const onSubmit = (data: any) => {
		console.log(data);
	};
	return (
		<KeyboardAvoidingView style={styles.container} behavior="padding">
			<View style={styles.form}>
				<Text>Name</Text>
				<Controller
					control={control}
					name="name"
					render={({ field: { onChange, onBlur, value } }) => (
						<TextInput
							onChangeText={onChange}
							onBlur={onBlur}
							value={value}
							placeholder="Name"
							style={styles.input}
						></TextInput>
					)}
				/>
				<Text style={styles.errorText}>{errors.name?.message}</Text>
			</View>
			<View style={styles.form}>
				<Text>Email</Text>
				<Controller
					control={control}
					name="email"
					render={({ field: { onChange, onBlur, value } }) => (
						<TextInput
							keyboardType="email-address"
							onChangeText={onChange}
							onBlur={onBlur}
							value={value}
							placeholder="Email"
							style={styles.input}
						></TextInput>
					)}
				/>
				<Text style={styles.errorText}>{errors.email?.message}</Text>
			</View>
			<View style={styles.form}>
				<Text>Password</Text>
				<Controller
					control={control}
					name="password"
					render={({ field: { onChange, onBlur, value } }) => (
						<TextInput
							secureTextEntry
							onChangeText={onChange}
							onBlur={onBlur}
							value={value}
							placeholder="Password"
							style={styles.input}
						></TextInput>
					)}
				/>
				<Text style={styles.errorText}>{errors.password?.message}</Text>
			</View>
			<TouchableOpacity
				onPress={handleSubmit(onSubmit)}
				disabled={!isValid}
				style={[styles.button, isValid ? styles.buttonEnabled : styles.buttonDisabled]}
				activeOpacity={0.7}
			>
				<Text style={[styles.buttonText, isValid ? styles.buttonTextEnabled : styles.buttonTextDisabled]}>
					Submit
				</Text>
			</TouchableOpacity>
		</KeyboardAvoidingView>
	);
}

const styles = StyleSheet.create({
	container: {
		flex: 1,
		backgroundColor: "#FFFFFF",
		padding: 20,
	},
	form: {
		gap: 10,
	},
	input: {
		width: "100%",
		height: 40,
		borderWidth: 1,
		borderColor: "gray",
		borderRadius: 5,
		padding: 10,
	},
	errorText: {
		color: "red",
	},
	button: {
		paddingVertical: 12,
		paddingHorizontal: 24,
		borderRadius: 8,
		alignItems: "center",
		marginTop: 20,
	},
	buttonEnabled: {
		backgroundColor: "#007AFF",
		shadowColor: "#000",
		shadowOffset: {
			width: 0,
			height: 2,
		},
		shadowOpacity: 0.25,
		shadowRadius: 3.84,
		elevation: 5,
	},
	buttonDisabled: {
		backgroundColor: "#CCCCCC",
		shadowOpacity: 0,
		elevation: 0,
	},
	buttonText: {
		fontSize: 16,
		fontWeight: "600",
	},
	buttonTextEnabled: {
		color: "white",
	},
	buttonTextDisabled: {
		color: "#666666",
	},
});

ポイント

1. useFormの設定

まず、useFormフックを使用してフォームの状態管理を設定します:

const {
	control,
	handleSubmit,
	formState: { errors, isValid },
} = useForm({
	resolver: valibotResolver(LoginSchema),
	mode: "onBlur",
	defaultValues: {
		name: "",
		email: "",
		password: "",
	},
});

主な設定項目:

  • resolver: valibotResolverを使用して先ほど作成したSchemaを設定
  • mode: "onBlur"でフォーカスが外れた時にバリデーションを実行
  • defaultValues: フォームの初期値を設定

2. フォームフィールドの実装

各フィールドはControllerコンポーネントを使用して実装します:

<View style={styles.form}>
	<Text>Name</Text>
	<Controller
		control={control}
		name="name"
		render={({ field: { onChange, onBlur, value } }) => (
			<TextInput onChangeText={onChange} onBlur={onBlur} value={value} placeholder="Name" style={styles.input} />
		)}
	/>
	<Text style={styles.errorText}>{errors.name?.message}</Text>
</View>

ポイント:

  • react-hook-formのControllerコンポーネントでフォームフィールドをラップ
  • fieldオブジェクトから必要な関数と値を取得
  • エラーメッセージを表示するためのerrorsオブジェクトの使用

エラーはerrorsオブジェクトに格納されています。
Schemaで定義したバリデーションルールに違反した場合、エラーメッセージが表示されます。

<Text style={styles.errorText}>{errors.name?.message}</Text>

3. 送信ボタンの実装

最後に送信ボタンは、バリデーションの状態に応じて見た目を変更します:

<TouchableOpacity
	onPress={handleSubmit(onSubmit)}
	disabled={!isValid}
	style={[styles.button, isValid ? styles.buttonEnabled : styles.buttonDisabled]}
	activeOpacity={0.7}
>
	<Text style={[styles.buttonText, isValid ? styles.buttonTextEnabled : styles.buttonTextDisabled]}>Submit</Text>
</TouchableOpacity>

ポイント:

  • isValidの状態に応じてボタンのスタイルを変更
  • handleSubmitでフォームの送信を処理
  • disabledプロパティでバリデーションエラー時にボタンを無効化

参考

まとめ

この記事では、React Nativeでのフォームバリデーションの実装方法について解説しました。
実際のコードはGitHubリポジトリで確認できます。

宣伝👻

福岡を拠点にフリーランスとしてアプリ開発をしています。
一番得意なのはネイティブiOSアプリ開発ですが、FlutterやReact Nativeアプリ開発もやっています。

お仕事のご相談などはお気軽にXのDMこちらのフォームからお願いします。

Discussion