مقدمة
أي تطبيق حقيقي تقريبا يحتوي على Forms: تسجيل دخول، إنشاء حساب، صفحة تواصل، بحث، إعدادات، أو لوحة تحكم. لذلك فهم طريقة التعامل مع Forms في React مهارة أساسية لأي Frontend Developer.
التحدي ليس في كتابة input، بل في جعل النموذج واضحا، موثوقا، وسهل الصيانة عندما تزيد الحقول والقواعد.
Controlled و Uncontrolled Inputs
في React يوجد طريقتان للتعامل مع الحقول.
الـ controlled input يعني أن React تتحكم في قيمة الحقل من خلال state:
const [email, setEmail] = useState("");
return (
<input
value={email}
onChange={(event) => setEmail(event.target.value)}
/>
);
هذه الطريقة مفيدة عندما تحتاج تحديث الواجهة مباشرة بناء على قيمة input، مثل إظهار validation أو تفعيل زر submit.
أما uncontrolled input فيترك القيمة داخل DOM وتقرأها عند الحاجة باستخدام ref أو مكتبة Forms. هذا مفيد عندما تريد أداء أفضل أو Forms كبيرة بدون إعادة تصيير مع كل حرف.
متى تستخدم useState فقط؟
لو عندك form صغير مثل search input أو newsletter signup، قد يكون useState كافيا.
function SearchForm() {
const [query, setQuery] = useState("");
function handleSubmit(event) {
event.preventDefault();
console.log("Search:", query);
}
return (
<form onSubmit={handleSubmit}>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<button type="submit">بحث</button>
</form>
);
}
لكن عندما يزيد عدد الحقول أو تصبح قواعد التحقق كثيرة، ستحتاج تنظيما أفضل.
التحقق من البيانات Validation
الـ validation يجب أن يكون واضحا ومفهوما للمستخدم. لا يكفي أن تمنع الإرسال، يجب أن تخبره ما المشكلة وكيف يصلحها.
أمثلة لقواعد شائعة:
- البريد الإلكتروني مطلوب ويجب أن يكون بصيغة صحيحة.
- كلمة المرور لا تقل عن 8 أحرف.
- تأكيد كلمة المرور يطابق كلمة المرور.
- رقم الهاتف يحتوي على أرقام فقط.
- حقل الرسالة لا يقل عن عدد معين من الأحرف.
تجنب رسائل عامة مثل "حدث خطأ". الأفضل أن تكون الرسالة مرتبطة بالحقل نفسه.
حالات النموذج المهمة
النموذج الجيد يتعامل مع أكثر من حالة:
idle: المستخدم لم يبدأ بعد.dirty: المستخدم عدل حقلا واحدا على الأقل.invalid: توجد أخطاء.submitting: الطلب قيد الإرسال.success: تم الإرسال بنجاح.error: فشل الإرسال من السيرفر.
هذه الحالات تجعل تجربة المستخدم واضحة. مثلا لا تترك زر submit قابلا للضغط أكثر من مرة أثناء الإرسال.
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "جاري الإرسال..." : "إرسال"}
</button>
لماذا react-hook-form اختيار قوي؟
مكتبة react-hook-form من أفضل الاختيارات في React لأنها تقلل إعادة التصيير، وتتعامل جيدا مع Forms الكبيرة، وتوفر API واضحة.
مثال مبسط:
import { useForm } from "react-hook-form";
function LoginForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm();
async function onSubmit(values) {
console.log(values);
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register("email", { required: "البريد الإلكتروني مطلوب" })}
/>
{errors.email && <p>{errors.email.message}</p>}
<input
type="password"
{...register("password", { required: "كلمة المرور مطلوبة" })}
/>
{errors.password && <p>{errors.password.message}</p>}
<button disabled={isSubmitting}>تسجيل الدخول</button>
</form>
);
}
الكود يصبح أوضح من إدارة state منفصل لكل input.
استخدام Zod لتنظيم قواعد التحقق
عندما تكبر الـ validation، من الأفضل فصلها في schema. هنا يأتي دور zod.
import { z } from "zod";
const loginSchema = z.object({
email: z.string().email("البريد الإلكتروني غير صحيح"),
password: z.string().min(8, "كلمة المرور يجب ألا تقل عن 8 أحرف"),
});
الميزة أنك تستطيع استخدام نفس schema في أكثر من مكان، وتستفيد من TypeScript لاستخراج نوع البيانات:
type LoginValues = z.infer<typeof loginSchema>;
هذا يقلل التكرار ويجعل شكل البيانات واضحا.
أخطاء شائعة في Forms
أول خطأ هو إخفاء رسائل الأخطاء إلى أن يضغط المستخدم submit. أحيانا الأفضل إظهار الخطأ بعد لمس الحقل touched حتى لا تكون التجربة مزعجة.
ثاني خطأ هو عدم التعامل مع أخطاء السيرفر. قد تكون البيانات صحيحة من ناحية الواجهة، لكن السيرفر يرفضها لأن البريد مستخدم بالفعل مثلا.
ثالث خطأ هو السماح بإرسال النموذج أكثر من مرة. هذا قد يسبب طلبات مكررة أو إنشاء بيانات مكررة.
رابع خطأ هو عدم الاهتمام بالـ accessibility. يجب ربط label بالحقل، وتوضيح الأخطاء لقارئات الشاشة قدر الإمكان.
مثال عملي: Login Form
نموذج تسجيل الدخول الجيد يجب أن يحتوي على:
- حقل بريد إلكتروني.
- حقل كلمة مرور.
- رسائل validation واضحة.
- حالة loading أثناء الإرسال.
- رسالة خطأ إذا فشل تسجيل الدخول.
- منع submit المتكرر.
الفكرة ليست أن يكون النموذج طويلا، بل أن يغطي سيناريوهات الاستخدام الحقيقية.
متى أستخدم مكتبة Forms؟
استخدم useState عندما يكون النموذج بسيطا جدا. استخدم react-hook-form عندما يكون لديك عدة حقول، validation، أو أداء مهم. استخدم zod عندما تريد schema واضحة وقابلة لإعادة الاستخدام، خاصة في مشاريع TypeScript.
الخلاصة العملية
الـ Forms الناجحة ليست التي تجمع البيانات فقط، بل التي تقود المستخدم بسلاسة من الإدخال إلى الإرسال. فكر في القيم، الأخطاء، حالات التحميل، وتجربة المستخدم قبل اختيار المكتبة.
لو بنيت النموذج بهذه الطريقة، سيصبح أسهل في التطوير، أوضح في الصيانة، وأكثر احترافية في عين المستخدم.




