frontend

TanStack Form + Zod Validation في React (2026)

٢٢ يونيو ٢٠٢٦

TanStack Form + Zod Validation in React (2026)

للتحقق من صحة نموذج TanStack Form باستخدام Zod، قم بتمرير مخطط Zod مباشرة إلى المدقق — validators: { onChange: schema } — في خطاف useForm. يدعم TanStack Form الإصدار الأول (v1) نظام Standard Schema بشكل أصلي، لذا لم تعد بحاجة لتثبيت أو استيراد محول Zod (adapter). يقوم المخطط بالتحقق من الصحة واستنتاج أنواع (types) النموذج في آن واحد.1

ملخص

يدعم TanStack Form إصدار v1.33.0 نظام Standard Schema، لذا فإن Zod (و Valibot أو ArkType أو Effect/Schema) يتصلون مباشرة — ولم تعد هناك حاجة للمحولات القديمة @tanstack/zod-form-adapter و zodValidator.1 يبني هذا الدليل العملي نموذج React حقيقيًا من مكون فارغ: مثيل useForm يتم التحقق منه بواسطة مخطط Zod 4، وعرض الأخطاء لكل حقل، والتحقق غير المتزامن مع تأخير (debounced async validation)، وحقول المصفوفات الديناميكية، وزر إرسال متصل بـ canSubmit. تم فحص كل قصاصة كود برمجياً باستخدام tsc --noEmit مقابل @tanstack/React-form@1.33.0 و zod@4.4.3 و React 19 في 22 يونيو 2026.

ما ستتعلمه

  • كيفية التحقق من صحة TanStack Form باستخدام Zod (بدون محول، فقط المخطط)
  • كيفية إعداد useForm و form.Field في React من الصفر
  • كيفية إظهار أخطاء التحقق بشكل صحيح (أخطاء v1 هي كائنات وليست سلاسل نصية)
  • كيفية إضافة التحقق غير المتزامن مع التأخير (debounced async validation) على مستوى الحقل
  • كيفية التعامل مع حقول المصفوفات الديناميكية باستخدام mode="array"
  • متى تختار TanStack Form بدلاً من React Hook Form

كيف يمكنني التحقق من صحة TanStack Form باستخدام Zod؟

قم بتمرير مخطط Zod مباشرة إلى مفتاح validators في useForm. لا توجد خطوة محول في الإصدار v1 — يعامل TanStack Form أي مكتبة تتبع Standard Schema، بما في ذلك Zod، كمدقق من الدرجة الأولى ويستخدمها للتحقق من القيم وتحديد أنواع النموذج.1

قم بتثبيت الحزمتين (يجلب TanStack Form نواته الخاصة؛ ما عليك سوى إضافة Zod):

npm install @tanstack/React-form@1.33.0 zod@4.4.3

الآن حدد مخططًا وقم بتزويد النموذج به. لاحظ أسلوب Zod 4 في z.email() — المساعد عالي المستوى الذي يحل محل سلسلة z.string().email() القديمة:

import { useForm } from '@tanstack/React-form'
import { z } from 'zod'

const schema = z.object({
  email: z.email('Enter a valid email'),
  password: z.string().min(8, 'At least 8 characters'),
})

export function SignupForm() {
  const form = useForm({
    defaultValues: { email: '', password: '' },
    validators: { onChange: schema },
    onSubmit: async ({ value }) => {
      // value is fully typed: { email: string; password: string }
      console.log('submit', value)
    },
  })

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault()
        e.stopPropagation()
        form.handleSubmit()
      }}
    >
      {/* fields go here */}
    </form>
  )
}

يقوم validators.onChange بتشغيل المخطط عند كل تغيير؛ استبدله بـ onBlur أو onSubmit للتحقق لاحقًا. نظرًا لأن المخطط يصف أيضًا شكل البيانات، فإن value داخل onSubmit يتم تعريف نوعها كـ { email: string; password: string } دون الحاجة إلى generics إضافية.2

كيف يمكنني إعداد form.Field في React؟

form.Field هو مكون يعتمد على render-prop: تمنحه name ودالة تستقبل حالة الحقل ومعالجاته. هذا يجعل كل مدخل مشتركًا فقط في الجزء الخاص به من حالة النموذج، لذا فإن الكتابة في حقل واحد لا تؤدي إلى إعادة رندر الحقول الأخرى.2

أضف حقلاً داخل <form> من القسم السابق:

<form.Field name="email">
  {(field) => (
    <div>
      <label htmlFor={field.name}>Email</label>
      <input
        id={field.name}
        value={field.state.value}
        onBlur={field.handleBlur}
        onChange={(e) => field.handleChange(e.target.value)}
      />
    </div>
  )}
</form.Field>

الأجزاء الثلاثة التي تربطها في كل مدخل هي field.state.value (القيمة الحالية)، و field.handleChange (لتحديثها)، و field.handleBlur (لتمييزها كـ "touched"). يتم فحص name برمجياً مقابل defaultValues الخاصة بك، لذا فإن أي خطأ إملائي مثل name="emial" سيؤدي لخطأ أثناء الكومبايل، وليس خطأ صامتاً أثناء التشغيل.

كيف يمكنني إظهار أخطاء التحقق في TanStack Form؟

اقرأ field.state.meta.errors — ولكن في الإصدار v1، كل خطأ من مدقق Standard Schema هو كائن مشكلة (issue object)، لذا قم برندر error.message، وليس القيمة الخام. هذا خطأ شائع عند الانتقال من الأمثلة القديمة القائمة على المحولات، والتي كانت تعرض الأخطاء كسلاسل نصية بسيطة.1

<form.Field name="email">
  {(field) => (
    <div>
      <input
        value={field.state.value}
        onBlur={field.handleBlur}
        onChange={(e) => field.handleChange(e.target.value)}
      />
      {!field.state.meta.isValid && (
        <em role="alert">
          {field.state.meta.errors.map((err) => err?.message).join(', ')}
        </em>
      )}
    </div>
  )}
</form.Field>

استخدم field.state.meta.isValid لتحديد ما إذا كنت ستعرض الرسالة، وقم بعمل map على errors باستخدام err?.message لأن المصفوفة تحتوي على كائنات. (إذا كتبت بدلاً من ذلك مدقق دالة بسيط يرجع سلسلة نصية، فستكون تلك السلسلة هي الخطأ مباشرة — انظر القسم التالي. ينتج عن أسلوبي المدقق شكلين مختلفين للأخطاء، وهو ما يربك البعض.)

كيف يمكنني تعطيل زر الإرسال حتى يصبح النموذج صالحًا؟

قم بلف الزر في form.Subscribe واختر canSubmit. يقوم form.Subscribe بإعادة الرندر فقط عندما يتغير الجزء الذي اخترته، لذا يتفاعل الزر مع حالة الصلاحية والإرسال دون إعادة رندر النموذج بالكامل.2

<form.Subscribe selector={(state) => [state.canSubmit, state.isSubmitting]}>
  {([canSubmit isSubmitting]) => (
    <button type="submit" disabled={!canSubmit}>
      {isSubmitting ? 'Submitting…' : 'Create account'}
    </button>
  )}
</form.Subscribe>

يعكس canSubmit ما إذا كان يمكن إرسال النموذج — ويصبح false بمجرد فشل أحد المدققات أو إذا كان التحقق غير المتزامن لا يزال قيد التشغيل — و isSubmitting يكون true أثناء تنفيذ وعد (promise) onSubmit، وهو ما يكفي لتعطيل الزر وإظهار مؤشر تحميل دون تتبع تلك الحالة بنفسك.

كيف يمكنني إضافة التحقق غير المتزامن في TanStack Form؟

أضف مدقق onChangeAsync (بالإضافة إلى onChangeAsyncDebounceMs) على مستوى الحقل لعمليات التحقق التي تتطلب طلباً عبر الشبكة، مثل "هل اسم المستخدم هذا مأخوذ؟". يمكن تأخير المدققات غير المتزامنة — اضبط onChangeAsyncDebounceMs حتى لا تطلق طلباً مع كل ضغطة مفتاح — وهي تعمل فقط بعد اجتياز المدققات المتزامنة.1

<form.Field
  name="username"
  validators={{
    onChange: ({ value }) =>
      value.length < 3 ? 'Too short' : undefined,
    onChangeAsyncDebounceMs: 500,
    onChangeAsync: async ({ value }) => {
      const res = await fetch(`/API/username-available?u=${value}`)
      const { available } = await res.json()
      return available ? undefined : 'Username is taken'
    },
  }}
>
  {(field) => (
    <div>
      <input
        value={field.state.value}
        onChange={(e) => field.handleChange(e.target.value)}
      />
      {field.state.meta.isValidating && <span>Checking…</span>}
      {!field.state.meta.isValid && (
        <em>{field.state.meta.errors.join(', ')}</em>
      )}
    </div>
  )}
</form.Field>

هنا المدققات هي دوال بسيطة ترجع سلسلة نصية (أو undefined عندما تكون صالحة)، لذا فإن errors تحتوي على سلاسل نصية وتقوم برندرها مباشرة — بدون .message. يكون field.state.meta.isValidating هو true أثناء تشغيل التحقق غير المتزامن، وهو ما تربط به مؤشر "Checking…".

كيف يمكنني التعامل مع حقول المصفوفات في TanStack Form؟

امنح الـ form.Field الأب الخاصية mode="array"، ثم قم برندر حقل ابن واحد لكل عنصر باستخدام اسم مفهرس مثل guests[${i}].name. أضف الصفوف وأزلها باستخدام field.pushValue() و field.removeValue() — ستقوم القائمة بإعادة رندر نفسها.3

<form.Field name="guests" mode="array">
  {(field) => (
    <div>
      {field.state.value.map((_, i) => (
        <form.Field key={i} name={`guests[${i}].name`}>
          {(sub) => (
            <input
              value={sub.state.value}
              onChange={(e) => sub.handleChange(e.target.value)}
            />
          )}
        </form.Field>
      ))}
      <button type="button" onClick={() => field.pushValue({ name: '' })}>
        Add guest
      </button>
      <button
        type="button"
        onClick={() => field.removeValue(field.state.value.length - 1)}
      >
        Remove last
      </button>
    </div>
  )}
</form.Field>

يقوم مخطط Zod مثل z.array(z.object({ name: z.string().min(1) })).min(1) بالتحقق من صحة المصفوفة بأكملها على مستوى النموذج، لذا فإن "أضف ضيفًا واحدًا على الأقل" و "كل ضيف يحتاج إلى اسم" كلاهما يأتي من نفس المخطط الذي كتبته بالفعل.

متى يجب أن أستخدم TanStack Form بدلاً من React Hook Form؟

اختر TanStack Form عندما تريد مسارات حقول آمنة برمجياً (type-safe)، وتحققاً غير متزامن مدمجاً، ونموذجاً ذهنياً واحداً مشتركاً مع TanStack Query أو Router؛ واستمر في استخدام React Hook Form عندما يكون لديك نماذج بسيطة إلى متوسطة وتُقدّر نضجها ونظامها البيئي الضخم.4 لا يوجد خيار "أفضل" بشكل مطلق — فكل منهما يقدم مقايضات مختلفة.

يعتمد React Hook Form على المراجع (ref-based) وغير خاضع للتحكم (uncontrolled)، مما يحافظ على انخفاض إعادة التصيير (re-renders) وصغر حجم الـ API، كما يمتلك سنوات من الاعتماد والتكاملات خلفه. أما TanStack Form فيستخدم مخزناً تفاعلياً (reactive store) مع اشتراكات دقيقة، وأسماء حقول مكتوبة بالكامل (fully typed)، ونواة مستقلة عن إطار العمل (React، Vue، Solid، Svelte، Angular)، وتحقق Standard Schema مدمج.4 إذا كانت نماذجك تعمل بشكل جيد ولا تواجه مشاكل في أمان الأنواع (type-safety) أو التحقق غير المتزامن، فلا يوجد داعٍ للاستعجال في التغيير.

الخلاصة

يجعل TanStack Form v1 من Zod مدققاً من سطر واحد: مرر المخطط، صير err.message، واترك canSubmit تتحكم في الزر — بدون محول، وبدون كود ربط إضافي. ابنِ النموذج المتزامن أولاً، ثم أضف طبقات الفحص غير المتزامن (debounced) وحقول المصفوفات مع نمو نموذجك. إذا كنت تعمل عبر نظام TanStack البيئي، فراجع دليل التجهيز المسبق (prefetch) لـ TanStack Query في Next.js App Router وشرحنا لـ بارامترات البحث الآمنة برمجياً مع TanStack Router و Zod. للحصول على رؤية أخرى حول إرسال النماذج وواجهة المستخدم قيد الانتظار، قارن هذا مع أفعال الخادم (server actions) في React 19 وواجهة المستخدم المتفائلة (optimistic UI).

Footnotes

  1. TanStack Form — Form and Field Validation (React). https://tanstack.com/form/latest/docs/framework/React/guides/validation 2 3 4 5 6 7 8 9

  2. TanStack Form — Quick Start (React). https://tanstack.com/form/latest/docs/framework/React/quick-start 2 3

  3. TanStack Form — Arrays (React). https://tanstack.com/form/v1/docs/framework/React/guides/arrays 2

  4. TanStack Form — Comparison. https://tanstack.com/form/latest/docs/comparison 2

الأسئلة الشائعة

لا. يدعم TanStack Form v1 نظام Standard Schema بشكل أصلي، لذا يمكنك تمرير مخطط Zod مباشرة إلى المدقق. حزمة @tanstack/zod-form-adapter المنفصلة ومدققها zodValidator لا يُستخدمان في الإصدار v1 — الأمثلة التي تستوردهما قديمة. 1