النماذج مع React Hook Form
React Hook Form هي مكتبة أداء ومرنة لبناء النماذج في React. تقلل من إعادة التصيير، وتبسط التحقق، وتوفر دعمًا ممتازًا لـ TypeScript. إنها مبنية على المكونات غير المتحكم بها لأداء أفضل مع الحفاظ على تجربة مطور رائعة.
لماذا React Hook Form؟
الفوائد الرئيسية:
- إعادة تصيير أقل - تعيد فقط تصيير الحقول التي تحتاج تحديثات
- واجهة برمجة تطبيقات بسيطة مع ()register لتسجيل الحقل
- التحقق المدمج مع HTML5 والقواعد المخصصة
- تكامل سهل مع مكتبات واجهة المستخدم (Material-UI، Ant Design)
- يعمل مع Zod و Yup و Joi للتحقق من المخطط
- حجم حزمة صغير (~9KB مضغوط)
الإعداد الأساسي والاستخدام
ثبت وأنشئ أول نموذج:
// التثبيت
npm install react-hook-form
// نموذج أساسي
import { useForm } from 'react-hook-form';
function LoginForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting }
} = useForm();
const onSubmit = async (data) => {
console.log(data); // { email: "...", password: "..." }
// محاكاة استدعاء API
await new Promise(resolve => setTimeout(resolve, 2000));
alert('تم تسجيل الدخول بنجاح!');
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="email">البريد الإلكتروني</label>
<input
id="email"
type="email"
{...register('email', {
required: 'البريد الإلكتروني مطلوب',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'عنوان بريد إلكتروني غير صالح'
}
})}
/>
{errors.email && (
<span className="error">{errors.email.message}</span>
)}
</div>
<div>
<label htmlFor="password">كلمة المرور</label>
<input
id="password"
type="password"
{...register('password', {
required: 'كلمة المرور مطلوبة',
minLength: {
value: 8,
message: 'يجب أن تكون كلمة المرور 8 أحرف على الأقل'
}
})}
/>
{errors.password && (
<span className="error">{errors.password.message}</span>
)}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'جاري تسجيل الدخول...' : 'تسجيل الدخول'}
</button>
</form>
);
}
عامل نشر Register: صيغة {...register('fieldName')} تنشر خصائص ref و onChange و onBlur و name إلى الإدخال الخاص بك. هذا يربط الإدخال بإدارة الحالة الداخلية لـ React Hook Form دون مكونات متحكم بها.
قواعد التحقق المتقدمة
يدعم React Hook Form قواعد التحقق الشاملة:
// نموذج تسجيل مع التحقق المعقد
function RegisterForm() {
const {
register,
handleSubmit,
watch,
formState: { errors }
} = useForm({
mode: 'onBlur', // التحقق عند فقدان التركيز
defaultValues: {
username: '',
email: '',
password: '',
confirmPassword: '',
age: '',
terms: false
}
});
// مراقبة كلمة المرور للتحقق من التأكيد
const password = watch('password');
const onSubmit = (data) => {
console.log('بيانات النموذج:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* اسم المستخدم */}
<div>
<label>اسم المستخدم</label>
<input
{...register('username', {
required: 'اسم المستخدم مطلوب',
minLength: {
value: 3,
message: 'يجب أن يكون اسم المستخدم 3 أحرف على الأقل'
},
maxLength: {
value: 20,
message: 'لا يمكن أن يتجاوز اسم المستخدم 20 حرفًا'
},
pattern: {
value: /^[a-zA-Z0-9_]+$/,
message: 'يمكن أن يحتوي اسم المستخدم على أحرف وأرقام وشرطات سفلية فقط'
}
})}
/>
{errors.username && <span>{errors.username.message}</span>}
</div>
{/* البريد الإلكتروني */}
<div>
<label>البريد الإلكتروني</label>
<input
type="email"
{...register('email', {
required: 'البريد الإلكتروني مطلوب',
validate: {
// التحقق غير المتزامن المخصص
checkDomain: async (value) => {
const domain = value.split('@')[1];
if (domain === 'tempmail.com') {
return 'عناوين البريد الإلكتروني المؤقتة غير مسموح بها';
}
return true;
},
// عدة محققات
noSpaces: (value) =>
!value.includes(' ') || 'لا يمكن أن يحتوي البريد الإلكتروني على مسافات'
}
})}
/>
{errors.email && <span>{errors.email.message}</span>}
</div>
{/* كلمة المرور */}
<div>
<label>كلمة المرور</label>
<input
type="password"
{...register('password', {
required: 'كلمة المرور مطلوبة',
validate: {
hasUpperCase: (value) =>
/[A-Z]/.test(value) || 'يجب أن تحتوي كلمة المرور على حرف كبير',
hasLowerCase: (value) =>
/[a-z]/.test(value) || 'يجب أن تحتوي كلمة المرور على حرف صغير',
hasNumber: (value) =>
/[0-9]/.test(value) || 'يجب أن تحتوي كلمة المرور على رقم',
hasSpecial: (value) =>
/[!@#$%^&*]/.test(value) || 'يجب أن تحتوي كلمة المرور على حرف خاص'
}
})}
/>
{errors.password && <span>{errors.password.message}</span>}
</div>
{/* تأكيد كلمة المرور */}
<div>
<label>تأكيد كلمة المرور</label>
<input
type="password"
{...register('confirmPassword', {
required: 'يرجى تأكيد كلمة المرور',
validate: (value) =>
value === password || 'كلمات المرور غير متطابقة'
})}
/>
{errors.confirmPassword && (
<span>{errors.confirmPassword.message}</span>
)}
</div>
{/* العمر */}
<div>
<label>العمر</label>
<input
type="number"
{...register('age', {
required: 'العمر مطلوب',
min: {
value: 18,
message: 'يجب أن تكون 18 عامًا على الأقل'
},
max: {
value: 120,
message: 'يرجى إدخال عمر صالح'
},
valueAsNumber: true // التحويل إلى رقم
})}
/>
{errors.age && <span>{errors.age.message}</span>}
</div>
{/* خانة اختيار الشروط */}
<div>
<label>
<input
type="checkbox"
{...register('terms', {
required: 'يجب قبول الشروط والأحكام'
})}
/>
أوافق على الشروط والأحكام
</label>
{errors.terms && <span>{errors.terms.message}</span>}
</div>
<button type="submit">تسجيل</button>
</form>
);
}
التحقق من المخطط مع Zod
للنماذج المعقدة، استخدم مكتبات التحقق من المخطط مثل Zod:
// التثبيت
npm install zod @hookform/resolvers
// التحقق من المخطط مع Zod
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
// تعريف المخطط
const userSchema = z.object({
firstName: z.string()
.min(2, 'يجب أن يكون الاسم الأول حرفين على الأقل')
.max(50, 'لا يمكن أن يتجاوز الاسم الأول 50 حرفًا'),
lastName: z.string()
.min(2, 'يجب أن يكون اسم العائلة حرفين على الأقل')
.max(50, 'لا يمكن أن يتجاوز اسم العائلة 50 حرفًا'),
email: z.string()
.email('عنوان بريد إلكتروني غير صالح')
.toLowerCase(),
phone: z.string()
.regex(/^\+?[1-9]\d{1,14}$/, 'رقم هاتف غير صالح')
.optional(),
age: z.number()
.int()
.min(18, 'يجب أن تكون 18 على الأقل')
.max(120, 'يرجى إدخال عمر صالح'),
website: z.string()
.url('رابط غير صالح')
.optional()
.or(z.literal('')),
role: z.enum(['user', 'admin', 'moderator'], {
errorMap: () => ({ message: 'يرجى اختيار دور صالح' })
}),
bio: z.string()
.max(500, 'لا يمكن أن تتجاوز السيرة 500 حرف')
.optional(),
terms: z.boolean()
.refine(val => val === true, {
message: 'يجب قبول الشروط'
})
});
type UserFormData = z.infer<typeof userSchema>;
function UserForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting }
} = useForm<UserFormData>({
resolver: zodResolver(userSchema),
defaultValues: {
firstName: '',
lastName: '',
email: '',
age: 18,
role: 'user',
terms: false
}
});
const onSubmit = async (data: UserFormData) => {
console.log('بيانات صالحة:', data);
// البيانات مكتوبة ومحققة بالكامل
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register('firstName')}
placeholder="الاسم الأول"
/>
{errors.firstName && <span>{errors.firstName.message}</span>}
<input
{...register('lastName')}
placeholder="اسم العائلة"
/>
{errors.lastName && <span>{errors.lastName.message}</span>}
<input
{...register('email')}
type="email"
placeholder="البريد الإلكتروني"
/>
{errors.email && <span>{errors.email.message}</span>}
<input
{...register('age', { valueAsNumber: true })}
type="number"
placeholder="العمر"
/>
{errors.age && <span>{errors.age.message}</span>}
<select {...register('role')}>
<option value="user">مستخدم</option>
<option value="admin">مسؤول</option>
<option value="moderator">مشرف</option>
</select>
{errors.role && <span>{errors.role.message}</span>}
<label>
<input type="checkbox" {...register('terms')} />
قبول الشروط
</label>
{errors.terms && <span>{errors.terms.message}</span>}
<button type="submit" disabled={isSubmitting}>
إرسال
</button>
</form>
);
}
ملاحظة الأداء: React Hook Form غير متحكم به افتراضيًا، مما يعني أنه لا يعيد التصيير في كل ضغطة مفتاح. استخدم ()watch فقط عندما تحتاج قيمًا في الوقت الفعلي، لأنه يسبب إعادة تصيير. للحصول على ملاحظات التحقق، اعتمد على errors بدلاً من ذلك.
الحقول الديناميكية ومصفوفات الحقول
تعامل مع حقول النموذج الديناميكية مثل إضافة/إزالة العناصر:
// حقول النموذج الديناميكية
import { useForm, useFieldArray } from 'react-hook-form';
function ContactForm() {
const { register, control, handleSubmit } = useForm({
defaultValues: {
name: '',
phoneNumbers: [{ number: '' }]
}
});
const { fields, append, remove } = useFieldArray({
control,
name: 'phoneNumbers'
});
const onSubmit = (data) => {
console.log(data);
// { name: "...", phoneNumbers: [{ number: "..." }, ...] }
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register('name', { required: true })}
placeholder="الاسم"
/>
<h3>أرقام الهواتف</h3>
{fields.map((field, index) => (
<div key={field.id}>
<input
{...register(`phoneNumbers.${index}.number`, {
required: 'رقم الهاتف مطلوب'
})}
placeholder={`هاتف #${index + 1}`}
/>
{index > 0 && (
<button
type="button"
onClick={() => remove(index)}
>
إزالة
</button>
)}
</div>
))}
<button
type="button"
onClick={() => append({ number: '' })}
>
إضافة رقم هاتف
</button>
<button type="submit">إرسال</button>
</form>
);
}
تكامل المكونات المتحكم بها
استخدم Controller لمكونات الطرف الثالث المتحكم بها:
// التكامل مع Material-UI أو مكونات متحكم بها أخرى
import { useForm, Controller } from 'react-hook-form';
import Select from 'react-select'; // مثال على مكون طرف ثالث
function ControlledForm() {
const { control, handleSubmit } = useForm({
defaultValues: {
country: null,
tags: []
}
});
const onSubmit = (data) => console.log(data);
const countryOptions = [
{ value: 'us', label: 'الولايات المتحدة' },
{ value: 'uk', label: 'المملكة المتحدة' },
{ value: 'ca', label: 'كندا' }
];
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="country"
control={control}
rules={{ required: 'الدولة مطلوبة' }}
render={({ field, fieldState: { error } }) => (
<div>
<Select
{...field}
options={countryOptions}
placeholder="اختر الدولة"
/>
{error && <span>{error.message}</span>}
</div>
)}
/>
<Controller
name="tags"
control={control}
render={({ field }) => (
<Select
{...field}
isMulti
options={[
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue' },
{ value: 'angular', label: 'Angular' }
]}
placeholder="اختر الوسوم"
/>
)}
/>
<button type="submit">إرسال</button>
</form>
);
}
تمرين عملي 1: نموذج استطلاع متعدد الخطوات
ابنِ نموذج استطلاع من 3 خطوات:
- الخطوة 1: معلومات شخصية (الاسم، البريد، العمر) مع التحقق
- الخطوة 2: التفضيلات (الاهتمامات كخانات اختيار، اختيار الدولة)
- الخطوة 3: مراجعة جميع البيانات قبل الإرسال
- استخدم ()watch لإظهار شريط التقدم بناءً على الحقول المملوءة
- احفظ حالة النموذج في localStorage بين الخطوات
تمرين عملي 2: منشئ الفاتورة
أنشئ نموذج فاتورة ديناميكي:
- قسم تفاصيل العميل (الاسم، البريد، العنوان)
- مصفوفة عناصر السطر الديناميكية (الوصف، الكمية، السعر)
- احسب تلقائيًا المجموع الفرعي، الضريبة (8%)، والإجمالي
- أضف/أزل عناصر السطر مع useFieldArray
- تحقق: الحد الأدنى عنصر واحد، جميع الأسعار > 0
تمرين عملي 3: نموذج طلب وظيفة مع رفع الملفات
ابنِ طلب وظيفة شامل:
- التفاصيل الشخصية مع التحقق من مخطط Zod
- قسم الخبرة: مصفوفة ديناميكية للوظائف (الشركة، الدور، التواريخ)
- المهارات: اختيار متعدد مع Controller
- رفع السيرة الذاتية مع التحقق من الملف (PDF فقط، حد أقصى 5MB)
- منطقة نص خطاب التقديم مع عداد الأحرف (حد أقصى 1000 حرف)
الخلاصة
توفر React Hook Form واجهة برمجة تطبيقات قوية ولكن بسيطة لإدارة النماذج. استخدم ()register للإدخالات الأساسية، والتحقق من المخطط مع Zod للنماذج المعقدة، useFieldArray للحقول الديناميكية، و Controller لمكونات الطرف الثالث. نهجها غير المتحكم به يضمن أداءً ممتازًا حتى مع النماذج الكبيرة.