React Hook Form چیست؟ آموزش کامل ساخت و اعتبارسنجی فرم در React و Next.js

در این آموزش React Hook Form را از صفر یاد می‌گیرید؛ از ساخت و اعتبارسنجی فرم با TypeScript و Zod تا مدیریت خطا، فیلدهای پویا، اتصال به Next.js و ساخت فرم عملی برای برنامه هوش مصنوعی.

Share
React Hook Form چیست؟ آموزش کامل ساخت و اعتبارسنجی فرم در React و Next.js


ساخت فرم در React در نگاه اول ساده به نظر می‌رسد؛ اما وقتی اعتبارسنجی، نمایش خطا، مقدار اولیه، فیلدهای پویا، وضعیت Loading، ارسال Async و هماهنگی با کامپوننت‌های رابط کاربری به پروژه اضافه شوند، مدیریت فرم می‌تواند پیچیده شود.

React Hook Form یک کتابخانه محبوب برای مدیریت فرم و اعتبارسنجی ورودی‌ها در React است. این کتابخانه به شما اجازه می‌دهد فرم‌های کوچک و بزرگ را با کد کمتر، Type Safety مناسب و کنترل دقیق وضعیت فرم پیاده‌سازی کنید.

در این آموزش، React Hook Form را از نصب اولیه تا ساخت یک فرم واقعی متصل به API بررسی می‌کنیم. همچنین با استفاده از TypeScript و Zod، اعتبارسنجی قابل‌استفاده در Runtime ایجاد خواهیم کرد.

React Hook Form چیست؟

React Hook Form یک کتابخانه مدیریت فرم برای React و React Native است. مهم‌ترین Hook آن useForm نام دارد و قابلیت‌هایی مانند موارد زیر را فراهم می‌کند:

  • ثبت و مدیریت فیلدها
  • اعتبارسنجی اطلاعات
  • نمایش خطاهای هر فیلد
  • مدیریت Submit
  • تعیین مقدار اولیه
  • مشاهده تغییر مقادیر
  • پاک‌سازی یا Reset فرم
  • مدیریت فیلدهای پویا
  • اتصال به کتابخانه‌های اعتبارسنجی
  • مدیریت کامپوننت‌های Controlled
  • بررسی وضعیت Dirty، Touched و Valid
  • مدیریت خطاهای Backend

براساس وب‌سایت رسمی React Hook Form، این کتابخانه تلاش می‌کند تعداد Re-renderهای غیرضروری را کاهش دهد و از قابلیت‌های استاندارد فرم HTML استفاده کند.

چرا مدیریت فرم در React دشوار می‌شود؟

در فرم ساده React ممکن است برای هر فیلد یک State تعریف کنیم:

'use client'

import { useState } from 'react'

export function ContactForm() {
  const [name, setName] = useState('')
  const [email, setEmail] = useState('')
  const [message, setMessage] = useState('')
  const [errors, setErrors] = useState({
    name: '',
    email: '',
    message: ''
  })

  return (
    <form>
      <input
        value={name}
        onChange={(event) =>
          setName(event.target.value)
        }
      />

      <input
        value={email}
        onChange={(event) =>
          setEmail(event.target.value)
        }
      />

      <textarea
        value={message}
        onChange={(event) =>
          setMessage(event.target.value)
        }
      />
    </form>
  )
}

با افزایش تعداد فیلدها باید موارد زیر را نیز مدیریت کنیم:

  • Validation
  • پیام خطا
  • Submit
  • Loading
  • مقدار اولیه
  • Reset
  • خطای سرور
  • فیلدهای شرطی
  • فیلدهای Array
  • File Input
  • جلوگیری از Submit تکراری
  • تبدیل نوع داده

React Hook Form این منطق تکراری را در قالب API یکپارچه مدیریت می‌کند.

نصب React Hook Form

در پروژه React، Vite یا Next.js دستور زیر را اجرا کنید:

npm install react-hook-form

اگر قصد دارید از Zod نیز استفاده کنید:

npm install zod @hookform/resolvers

پکیج @hookform/resolvers امکان اتصال React Hook Form به کتابخانه‌هایی مانند Zod، Yup، Joi، Valibot و Ajv را فراهم می‌کند. فهرست Resolverهای پشتیبانی‌شده در Repository رسمی React Hook Form Resolvers موجود است.

ساخت اولین فرم با useForm

'use client'

import { useForm } from 'react-hook-form'

type LoginFormValues = {
  email: string
  password: string
}

export function LoginForm() {
  const {
    register,
    handleSubmit
  } = useForm<LoginFormValues>()

  function onSubmit(data: LoginFormValues) {
    console.log(data)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="email">
        ایمیل
      </label>

      <input
        id="email"
        type="email"
        {...register('email')}
      />

      <label htmlFor="password">
        رمز عبور
      </label>

      <input
        id="password"
        type="password"
        {...register('password')}
      />

      <button type="submit">
        ورود
      </button>
    </form>
  )
}

در این مثال:

  • useForm وضعیت فرم را مدیریت می‌کند.
  • register فیلد را در فرم ثبت می‌کند.
  • handleSubmit قبل از اجرای تابع Submit، اعتبار فرم را بررسی می‌کند.
  • data شامل مقادیر نهایی فرم است.
  • Generic مربوط به useForm نوع تمام فیلدها را برای TypeScript مشخص می‌کند.

register چگونه کار می‌کند؟

تابع register مشخصات لازم برای اتصال Input به فرم را برمی‌گرداند:

<input {...register('email')} />

این عبارت در عمل ویژگی‌هایی مانند موارد زیر را به Input متصل می‌کند:

  • name
  • ref
  • onChange
  • onBlur

می‌توان قوانین اعتبارسنجی را نیز داخل register تعریف کرد:

<input
  type="email"
  {...register('email', {
    required: 'ایمیل الزامی است',
    pattern: {
      value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
      message: 'فرمت ایمیل معتبر نیست'
    }
  })}
/>

نمایش خطاهای فرم

خطاها داخل formState.errors قرار می‌گیرند:

'use client'

import { useForm } from 'react-hook-form'

type RegisterFormValues = {
  name: string
  email: string
  password: string
}

export function RegisterForm() {
  const {
    register,
    handleSubmit,
    formState: {
      errors
    }
  } = useForm<RegisterFormValues>()

  function onSubmit(data: RegisterFormValues) {
    console.log(data)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label htmlFor="name">
          نام
        </label>

        <input
          id="name"
          {...register('name', {
            required: 'نام الزامی است',
            minLength: {
              value: 2,
              message:
                'نام باید حداقل دو کاراکتر داشته باشد'
            }
          })}
          aria-invalid={Boolean(errors.name)}
          aria-describedby={
            errors.name ? 'name-error' : undefined
          }
        />

        {errors.name ? (
          <p id="name-error" role="alert">
            {errors.name.message}
          </p>
        ) : null}
      </div>

      <div>
        <label htmlFor="email">
          ایمیل
        </label>

        <input
          id="email"
          type="email"
          {...register('email', {
            required: 'ایمیل الزامی است',
            pattern: {
              value:
                /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
              message: 'ایمیل معتبر نیست'
            }
          })}
          aria-invalid={Boolean(errors.email)}
        />

        {errors.email ? (
          <p role="alert">
            {errors.email.message}
          </p>
        ) : null}
      </div>

      <div>
        <label htmlFor="password">
          رمز عبور
        </label>

        <input
          id="password"
          type="password"
          {...register('password', {
            required: 'رمز عبور الزامی است',
            minLength: {
              value: 8,
              message:
                'رمز عبور باید حداقل هشت کاراکتر باشد'
            }
          })}
          aria-invalid={Boolean(errors.password)}
        />

        {errors.password ? (
          <p role="alert">
            {errors.password.message}
          </p>
        ) : null}
      </div>

      <button type="submit">
        ثبت‌نام
      </button>
    </form>
  )
}

استفاده از role="alert" و aria-invalid دسترس‌پذیری پیام‌های خطا را بهتر می‌کند.

قوانین اعتبارسنجی داخلی

React Hook Form تعدادی قانون Validation داخلی ارائه می‌دهد:

required

register('title', {
  required: 'عنوان الزامی است'
})

minLength و maxLength

register('description', {
  minLength: {
    value: 20,
    message:
      'توضیحات باید حداقل ۲۰ کاراکتر باشد'
  },
  maxLength: {
    value: 1000,
    message:
      'توضیحات نمی‌تواند بیشتر از ۱۰۰۰ کاراکتر باشد'
  }
})

min و max

register('temperature', {
  valueAsNumber: true,
  min: {
    value: 0,
    message: 'کمترین مقدار صفر است'
  },
  max: {
    value: 2,
    message: 'بیشترین مقدار دو است'
  }
})

pattern

register('username', {
  pattern: {
    value: /^[a-zA-Z0-9_]+$/,
    message:
      'نام کاربری فقط می‌تواند شامل حروف انگلیسی، عدد و زیرخط باشد'
  }
})

validate

برای Validation سفارشی:

register('prompt', {
  validate: {
    notOnlySpaces: (value) =>
      value.trim().length > 0 ||
      'متن نمی‌تواند فقط شامل فاصله باشد',

    enoughWords: (value) =>
      value.trim().split(/\s+/).length >= 3 ||
      'حداقل سه کلمه وارد کنید'
  }
})

تبدیل نوع داده ورودی

مقدار Inputهای HTML معمولاً String است؛ حتی اگر type="number" داشته باشند.

برای تبدیل عدد:

<input
  type="number"
  {...register('maxTokens', {
    valueAsNumber: true
  })}
/>

برای تاریخ:

<input
  type="date"
  {...register('publishDate', {
    valueAsDate: true
  })}
/>

برای تبدیل سفارشی:

<input
  {...register('title', {
    setValueAs: (value) =>
      typeof value === 'string'
        ? value.trim()
        : value
  })}
/>

دقت کنید setValueAs روی داده ورودی اثر می‌گذارد. اگر می‌خواهید نسخه اصلی متن کاربر حفظ شود، Trim کردن را هنگام Submit یا در Schema انجام دهید.

defaultValues چیست؟

برای تعیین مقادیر اولیه از defaultValues استفاده کنید:

const form = useForm<SettingsFormValues>({
  defaultValues: {
    model: '',
    temperature: 0.3,
    maxTokens: 1000,
    stream: true
  }
})

مقادیر اولیه را ترجیحاً در یک محل تعریف کنید:

const defaultValues: SettingsFormValues = {
  model: '',
  temperature: 0.3,
  maxTokens: 1000,
  stream: true
}

const form = useForm<SettingsFormValues>({
  defaultValues
})

این کار Reset کردن فرم را نیز ساده‌تر می‌کند.

formState شامل چه اطلاعاتی است؟

formState وضعیت‌های مهمی ارائه می‌دهد:

const {
  formState: {
    errors,
    isDirty,
    dirtyFields,
    touchedFields,
    isValid,
    isSubmitting,
    isSubmitted,
    isSubmitSuccessful,
    submitCount
  }
} = useForm<FormValues>()

کاربرد آن‌ها:

مقدارکاربرد
errorsخطاهای Validation
isDirtyآیا حداقل یک مقدار تغییر کرده است؟
dirtyFieldsکدام فیلدها تغییر کرده‌اند؟
touchedFieldsکاربر کدام فیلدها را لمس کرده است؟
isValidآیا فرم معتبر است؟
isSubmittingآیا Submit در حال اجراست؟
isSubmittedآیا فرم حداقل یک بار Submit شده است؟
isSubmitSuccessfulآیا Submit بدون خطای داخلی تمام شده است؟
submitCountتعداد دفعات Submit

مثال جلوگیری از Submit تکراری:

<button
  type="submit"
  disabled={isSubmitting}
>
  {isSubmitting
    ? 'در حال ارسال...'
    : 'ارسال'}
</button>

Validation Mode چیست؟

با گزینه mode مشخص می‌کنید Validation چه زمانی اجرا شود:

useForm<FormValues>({
  mode: 'onSubmit'
})

حالت‌های متداول:

  • onSubmit: هنگام Submit
  • onBlur: هنگام خروج کاربر از فیلد
  • onChange: با هر تغییر
  • onTouched: پس از اولین Blur
  • all: ترکیبی از Blur و Change

برای بسیاری از فرم‌ها، onSubmit یا onTouched انتخاب متعادلی است. استفاده از onChange در فرم‌های بزرگ می‌تواند Validation را بیش از حد تکرار کند.

مثال:

const form = useForm<FormValues>({
  mode: 'onTouched',
  reValidateMode: 'onChange'
})

در این حالت خطای اولیه پس از لمس فیلد نمایش داده می‌شود و پس از آن با تغییر مقدار بروزرسانی خواهد شد.

اعتبارسنجی فرم با Zod

Validationهای داخل register برای فرم‌های ساده مناسب‌اند. در فرم‌های بزرگ‌تر بهتر است قوانین در یک Schema مستقل تعریف شوند.

// src/schemas/assistant-form.ts

import { z } from 'zod'

export const assistantFormSchema = z.object({
  prompt: z
    .string()
    .trim()
    .min(1, 'متن درخواست الزامی است')
    .min(
      10,
      'متن درخواست باید حداقل ۱۰ کاراکتر باشد'
    )
    .max(
      4000,
      'متن درخواست نمی‌تواند بیشتر از ۴۰۰۰ کاراکتر باشد'
    ),

  model: z
    .string()
    .trim()
    .min(1, 'انتخاب مدل الزامی است'),

  temperature: z
    .number()
    .min(0, 'Temperature نمی‌تواند کمتر از صفر باشد')
    .max(2, 'Temperature نمی‌تواند بیشتر از دو باشد'),

  includeContext: z.boolean()
})

export type AssistantFormInput = z.input<
  typeof assistantFormSchema
>

export type AssistantFormOutput = z.output<
  typeof assistantFormSchema
>

اتصال Schema به React Hook Form:

'use client'

import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'

import {
  assistantFormSchema,
  type AssistantFormInput,
  type AssistantFormOutput
} from '@/schemas/assistant-form'

export function AssistantForm() {
  const {
    register,
    handleSubmit,
    formState: {
      errors
    }
  } = useForm<
    AssistantFormInput,
    unknown,
    AssistantFormOutput
  >({
    resolver: zodResolver(assistantFormSchema),
    defaultValues: {
      prompt: '',
      model: '',
      temperature: 0.3,
      includeContext: false
    }
  })

  async function onSubmit(
    data: AssistantFormOutput
  ) {
    console.log(data)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <textarea
        {...register('prompt')}
      />

      {errors.prompt ? (
        <p role="alert">
          {errors.prompt.message}
        </p>
      ) : null}

      <select {...register('model')}>
        <option value="">
          انتخاب مدل
        </option>

        <option value="YOUR_MODEL_ID">
          مدل انتخابی
        </option>
      </select>

      {errors.model ? (
        <p role="alert">
          {errors.model.message}
        </p>
      ) : null}

      <input
        type="number"
        step="0.1"
        {...register('temperature', {
          valueAsNumber: true
        })}
      />

      {errors.temperature ? (
        <p role="alert">
          {errors.temperature.message}
        </p>
      ) : null}

      <label>
        <input
          type="checkbox"
          {...register('includeContext')}
        />

        استفاده از Context
      </label>

      <button type="submit">
        ارسال
      </button>
    </form>
  )
}

Resolver رسمی می‌تواند در بسیاری از موارد Type خروجی را از Schema استنتاج کند. در مثال بالا Type ورودی و خروجی به‌صورت صریح تعریف شده‌اند تا تبدیل‌های احتمالی Zod نیز قابل‌کنترل باشند.

آیا Validation سمت Client کافی است؟

خیر. Validation مرورگر فقط تجربه کاربری را بهتر می‌کند. کاربر یا برنامه دیگر می‌تواند مستقیماً Endpoint را فراخوانی کرده و فرم Client را دور بزند.

Schema باید در سرور نیز اجرا شود:

// src/app/api/assistant/route.ts

import { assistantFormSchema } from '@/schemas/assistant-form'

export async function POST(request: Request) {
  const body: unknown = await request.json()
  const result =
    assistantFormSchema.safeParse(body)

  if (!result.success) {
    return Response.json(
      {
        error: 'اطلاعات واردشده معتبر نیست',
        fields: result.error.flatten().fieldErrors
      },
      {
        status: 400
      }
    )
  }

  const data = result.data

  return Response.json({
    success: true,
    data
  })
}

Validation Client برای UX است و Validation Server برای صحت و یکپارچگی داده ضروری است.

ارسال Async فرم

async function onSubmit(
  data: AssistantFormOutput
) {
  setServerError('')
  setAnswer('')

  try {
    const response = await fetch('/api/assistant', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    })

    const result = (await response.json()) as {
      answer?: string
      error?: string
    }

    if (!response.ok) {
      throw new Error(
        result.error || 'ارسال فرم ناموفق بود'
      )
    }

    if (!result.answer) {
      throw new Error('پاسخ معتبری دریافت نشد')
    }

    setAnswer(result.answer)
  } catch (error) {
    setServerError(
      error instanceof Error
        ? error.message
        : 'خطای پیش‌بینی‌نشده رخ داد'
    )
  }
}

خود handleSubmit از تابع Async پشتیبانی می‌کند و isSubmitting را هنگام اجرای Promise بروزرسانی می‌کند:

<button
  type="submit"
  disabled={isSubmitting}
>
  {isSubmitting
    ? 'در حال پردازش...'
    : 'ارسال درخواست'}
</button>

مدیریت خطاهای Backend با setError

اگر Backend خطای مربوط به یک فیلد برگرداند، می‌توان آن را با setError نمایش داد:

const {
  setError
} = useForm<FormValues>()

مثال:

if (response.status === 409) {
  setError('email', {
    type: 'server',
    message:
      'قبلاً حسابی با این ایمیل ثبت شده است'
  })

  return
}

برای خطای کلی فرم:

setError('root.server', {
  type: 'server',
  message:
    'ارسال اطلاعات ناموفق بود؛ دوباره تلاش کنید'
})

نمایش:

{errors.root?.server ? (
  <p role="alert">
    {errors.root.server.message}
  </p>
) : null}

خطای Server را به فیلد مرتبط نگاشت کنید، اما جزئیات داخلی Database، Stack Trace یا اطلاعات حساس را به کاربر نمایش ندهید.

Reset کردن فرم

برای بازگرداندن فرم به مقدار اولیه:

const {
  reset
} = useForm<FormValues>({
  defaultValues
})

پس از Submit موفق:

async function onSubmit(data: FormValues) {
  await saveData(data)
  reset()
}

Reset با مقدار جدید:

reset({
  name: 'امیر',
  email: 'user@example.com'
})

گاهی لازم است برخی وضعیت‌ها حفظ شوند:

reset(undefined, {
  keepErrors: false,
  keepDirtyValues: true
})

استفاده از این گزینه‌ها باید آگاهانه باشد؛ زیرا نگه داشتن Error یا Dirty State پس از بارگذاری داده جدید می‌تواند کاربر را سردرگم کند.

setValue و getValues

برای تغییر برنامه‌نویسی‌شده یک فیلد:

setValue('temperature', 0.2, {
  shouldDirty: true,
  shouldValidate: true
})

دریافت مقادیر فعلی بدون Subscription:

const values = getValues()
const prompt = getValues('prompt')

getValues باعث Re-render در اثر تغییر مقدار نمی‌شود و فقط مقدار لحظه‌ای را برمی‌گرداند.

watch و useWatch

برای مشاهده تغییر یک یا چند فیلد می‌توان از watch استفاده کرد:

const prompt = watch('prompt')

مثال شمارش کاراکتر:

<p>
  {prompt?.length ?? 0} از ۴۰۰۰ کاراکتر
</p>

مشاهده چند مقدار:

const [
  model,
  temperature
] = watch([
  'model',
  'temperature'
])

اگر فقط یک کامپوننت داخلی باید تغییرات را دنبال کند، useWatch می‌تواند Re-render را محدودتر کند:

import {
  useWatch,
  type Control
} from 'react-hook-form'

type PromptCounterProps = {
  control: Control<AssistantFormInput>
}

export function PromptCounter({
  control
}: PromptCounterProps) {
  const prompt = useWatch({
    control,
    name: 'prompt'
  })

  return (
    <p aria-live="polite">
      {(prompt?.length ?? 0).toLocaleString('fa-IR')}
      {' از '}
      ۴٬۰۰۰ کاراکتر
    </p>
  )
}

از watch() بدون نام در فرم‌های بزرگ با احتیاط استفاده کنید؛ زیرا تمام فرم را Subscribe می‌کند و می‌تواند Re-renderهای بیشتری ایجاد کند.

trigger برای اجرای دستی Validation

اعتبارسنجی یک فیلد:

const isValidPrompt = await trigger('prompt')

اعتبارسنجی چند فیلد:

const canContinue = await trigger([
  'prompt',
  'model'
])

اعتبارسنجی تمام فرم:

const isFormValid = await trigger()

این قابلیت برای فرم‌های چندمرحله‌ای مفید است:

async function goToNextStep() {
  const isStepValid = await trigger([
    'name',
    'email'
  ])

  if (isStepValid) {
    setStep(2)
  }
}

Controller چیست؟

register برای Inputهای استاندارد HTML مناسب است. برخی کتابخانه‌های UI کامپوننت‌های Controlled ارائه می‌دهند و به‌جای ref و name استاندارد، از Propsهایی مانند value و onValueChange استفاده می‌کنند.

در چنین شرایطی از Controller استفاده می‌شود:

import {
  Controller,
  useForm
} from 'react-hook-form'

type FormValues = {
  temperature: number
}

export function SettingsForm() {
  const {
    control,
    handleSubmit
  } = useForm<FormValues>({
    defaultValues: {
      temperature: 0.3
    }
  })

  return (
    <form
      onSubmit={handleSubmit(console.log)}
    >
      <Controller
        name="temperature"
        control={control}
        render={({ field, fieldState }) => (
          <div>
            <label>
              Temperature: {field.value}
            </label>

            <input
              type="range"
              min="0"
              max="2"
              step="0.1"
              value={field.value}
              onChange={(event) =>
                field.onChange(
                  Number(event.target.value)
                )
              }
              onBlur={field.onBlur}
              ref={field.ref}
            />

            {fieldState.error ? (
              <p role="alert">
                {fieldState.error.message}
              </p>
            ) : null}
          </div>
        )}
      />

      <button type="submit">
        ذخیره
      </button>
    </form>
  )
}

در Controller باید ویژگی‌های field را به کامپوننت رابط کاربری متصل کنید:

  • value
  • onChange
  • onBlur
  • name
  • ref

برای Input استاندارد، register ساده‌تر است. استفاده بی‌دلیل از Controller کد را پیچیده‌تر می‌کند.

useFieldArray برای فیلدهای پویا

فرض کنید کاربر می‌تواند چند پیام نمونه به فرم اضافه کند:

'use client'

import {
  useFieldArray,
  useForm
} from 'react-hook-form'

type ExampleFormValues = {
  examples: Array<{
    input: string
    output: string
  }>
}

export function DynamicExamplesForm() {
  const {
    control,
    register,
    handleSubmit,
    formState: {
      errors
    }
  } = useForm<ExampleFormValues>({
    defaultValues: {
      examples: [
        {
          input: '',
          output: ''
        }
      ]
    }
  })

  const {
    fields,
    append,
    remove
  } = useFieldArray({
    control,
    name: 'examples'
  })

  return (
    <form
      onSubmit={handleSubmit(console.log)}
    >
      {fields.map((field, index) => (
        <fieldset key={field.id}>
          <legend>
            نمونه {index + 1}
          </legend>

          <label
            htmlFor={`examples.${index}.input`}
          >
            ورودی
          </label>

          <textarea
            id={`examples.${index}.input`}
            {...register(
              `examples.${index}.input`,
              {
                required:
                  'ورودی نمونه الزامی است'
              }
            )}
          />

          {errors.examples?.[index]?.input ? (
            <p role="alert">
              {
                errors.examples[index]
                  ?.input?.message
              }
            </p>
          ) : null}

          <label
            htmlFor={`examples.${index}.output`}
          >
            خروجی مورد انتظار
          </label>

          <textarea
            id={`examples.${index}.output`}
            {...register(
              `examples.${index}.output`,
              {
                required:
                  'خروجی نمونه الزامی است'
              }
            )}
          />

          <button
            type="button"
            onClick={() => remove(index)}
            disabled={fields.length === 1}
          >
            حذف نمونه
          </button>
        </fieldset>
      ))}

      <button
        type="button"
        onClick={() =>
          append({
            input: '',
            output: ''
          })
        }
      >
        افزودن نمونه
      </button>

      <button type="submit">
        ذخیره
      </button>
    </form>
  )
}

برای key از field.id استفاده کنید، نه Index:

<div key={field.id}>

استفاده از Index به‌عنوان Key هنگام حذف یا جابه‌جایی فیلدها می‌تواند باعث نمایش مقدار اشتباه شود.

ساخت فرم چندمرحله‌ای

برای فرم چندمرحله‌ای می‌توان تمام داده‌ها را در یک useForm نگه داشت:

const form = useForm<OnboardingFormValues>({
  mode: 'onTouched',
  defaultValues: {
    name: '',
    email: '',
    company: '',
    useCase: '',
    expectedUsage: ''
  }
})

حرکت از مرحله اول:

async function goToStepTwo() {
  const valid = await trigger([
    'name',
    'email'
  ])

  if (valid) {
    setCurrentStep(2)
  }
}

حرکت از مرحله دوم:

async function goToStepThree() {
  const valid = await trigger([
    'company',
    'useCase'
  ])

  if (valid) {
    setCurrentStep(3)
  }
}

در فرم‌های چندمرحله‌ای:

  • داده مرحله قبلی را بی‌دلیل پاک نکنید.
  • Validation هر مرحله را جدا اجرا کنید.
  • پیشرفت کاربر را نمایش دهید.
  • دکمه بازگشت قرار دهید.
  • Submit نهایی را فقط در مرحله آخر انجام دهید.
  • اگر فرم طولانی است، ذخیره Draft را در نظر بگیرید.

FormProvider و useFormContext

اگر فرم از چند کامپوننت تو در تو تشکیل شده باشد، ارسال register و errors از طریق چندین لایه Props دشوار می‌شود.

import {
  FormProvider,
  useForm
} from 'react-hook-form'

export function ProfileForm() {
  const methods =
    useForm<ProfileFormValues>({
      defaultValues: {
        name: '',
        email: ''
      }
    })

  return (
    <FormProvider {...methods}>
      <form
        onSubmit={methods.handleSubmit(
          console.log
        )}
      >
        <PersonalFields />
        <ContactFields />

        <button type="submit">
          ذخیره
        </button>
      </form>
    </FormProvider>
  )
}

در کامپوننت داخلی:

import {
  useFormContext
} from 'react-hook-form'

export function PersonalFields() {
  const {
    register,
    formState: {
      errors
    }
  } = useFormContext<ProfileFormValues>()

  return (
    <div>
      <label htmlFor="name">
        نام
      </label>

      <input
        id="name"
        {...register('name', {
          required: 'نام الزامی است'
        })}
      />

      {errors.name ? (
        <p role="alert">
          {errors.name.message}
        </p>
      ) : null}
    </div>
  )
}

از قرار دادن چند FormProvider تو در تو برای یک فرم خودداری کنید.

مدیریت Checkbox

Checkbox تکی:

type FormValues = {
  acceptTerms: boolean
}

<input
  type="checkbox"
  {...register('acceptTerms', {
    required:
      'پذیرش شرایط استفاده الزامی است'
  })}
/>

مجموعه Checkboxها:

type FormValues = {
  capabilities: string[]
}
<label>
  <input
    type="checkbox"
    value="text"
    {...register('capabilities')}
  />
  متن
</label>

<label>
  <input
    type="checkbox"
    value="image"
    {...register('capabilities')}
  />
  تصویر
</label>

<label>
  <input
    type="checkbox"
    value="audio"
    {...register('capabilities')}
  />
  صوت
</label>

خروجی:

{
  capabilities: ['text', 'image']
}

مدیریت Radio Button

type FormValues = {
  responseLength:
    | 'short'
    | 'medium'
    | 'long'
}
<label>
  <input
    type="radio"
    value="short"
    {...register('responseLength')}
  />
  کوتاه
</label>

<label>
  <input
    type="radio"
    value="medium"
    {...register('responseLength')}
  />
  متوسط
</label>

<label>
  <input
    type="radio"
    value="long"
    {...register('responseLength')}
  />
  بلند
</label>

مدیریت Select

<select
  {...register('model', {
    required: 'یک مدل انتخاب کنید'
  })}
>
  <option value="">
    انتخاب مدل
  </option>

  <option value="YOUR_MODEL_ID">
    مدل انتخابی درواره
  </option>
</select>

Model IDهای قابل‌استفاده را از صفحه مدل‌های درواره دریافت کنید و مقدار واقعی را جایگزین YOUR_MODEL_ID کنید.

مدیریت File Input

type UploadFormValues = {
  document: FileList
}

<input
  type="file"
  accept=".txt,.pdf"
  {...register('document', {
    required: 'انتخاب فایل الزامی است',
    validate: {
      fileSize: (files) =>
        !files?.[0] ||
        files[0].size <= 5 * 1024 * 1024 ||
        'حجم فایل نباید بیشتر از ۵ مگابایت باشد',

      fileType: (files) =>
        !files?.[0] ||
        [
          'application/pdf',
          'text/plain'
        ].includes(files[0].type) ||
        'نوع فایل مجاز نیست'
    }
  })}
/>

برای ارسال فایل باید از FormData استفاده کنید:

async function onSubmit(
  data: UploadFormValues
) {
  const file = data.document[0]

  const formData = new FormData()
  formData.append('document', file)

  await fetch('/api/upload', {
    method: 'POST',
    body: formData
  })
}

هنگام ارسال FormData، Header مربوط به Content-Type را دستی تنظیم نکنید؛ مرورگر Boundary صحیح را ایجاد می‌کند.

Validation نوع و حجم فایل باید در Backend نیز تکرار شود.

غیرفعال کردن فیلد در React Hook Form

فیلد disabled معمولاً در داده Submit‌شده حضور ندارد:

<input
  disabled
  {...register('email')}
/>

اگر می‌خواهید مقدار قابل‌تغییر نباشد اما همچنان ارسال شود، می‌توانید از readOnly استفاده کنید:

<input
  readOnly
  {...register('email')}
/>

بین disabled و readOnly تفاوت وجود دارد و انتخاب آن‌ها باید براساس رفتار موردنظر انجام شود.

فوکوس روی اولین خطا

React Hook Form می‌تواند هنگام Submit روی اولین فیلد نامعتبر Focus کند:

useForm<FormValues>({
  shouldFocusError: true
})

همچنین می‌توان دستی Focus کرد:

setFocus('email')

برای کارکرد صحیح Focus، Ref فیلد باید به React Hook Form متصل باشد.

فرم کامل Prompt برای API هوش مصنوعی

اکنون یک مثال عملی می‌سازیم که:

  • Prompt را دریافت می‌کند.
  • Model ID را می‌گیرد.
  • Temperature را کنترل می‌کند.
  • ورودی را با Zod اعتبارسنجی می‌کند.
  • داده را به Backend می‌فرستد.
  • Loading و Error را نمایش می‌دهد.
  • کلید API را در Client قرار نمی‌دهد.
// src/components/AiPromptForm.tsx

'use client'

import { useState } from 'react'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'

const promptFormSchema = z.object({
  prompt: z
    .string()
    .trim()
    .min(10, 'حداقل ۱۰ کاراکتر وارد کنید')
    .max(
      4000,
      'متن نمی‌تواند بیشتر از ۴۰۰۰ کاراکتر باشد'
    ),

  model: z
    .string()
    .trim()
    .min(1, 'Model ID الزامی است'),

  temperature: z
    .number()
    .min(0)
    .max(2)
})

type PromptFormInput = z.input<
  typeof promptFormSchema
>

type PromptFormOutput = z.output<
  typeof promptFormSchema
>

type ApiResponse = {
  answer?: string
  error?: string
}

export function AiPromptForm() {
  const [answer, setAnswer] = useState('')

  const {
    register,
    handleSubmit,
    setError,
    watch,
    reset,
    formState: {
      errors,
      isSubmitting
    }
  } = useForm<
    PromptFormInput,
    unknown,
    PromptFormOutput
  >({
    resolver: zodResolver(promptFormSchema),
    mode: 'onTouched',
    defaultValues: {
      prompt: '',
      model: 'YOUR_MODEL_ID',
      temperature: 0.3
    }
  })

  const prompt = watch('prompt')

  async function onSubmit(
    data: PromptFormOutput
  ) {
    setAnswer('')

    try {
      const response = await fetch('/api/chat', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
      })

      const result =
        (await response.json()) as ApiResponse

      if (!response.ok) {
        setError('root.server', {
          type: 'server',
          message:
            result.error ||
            'دریافت پاسخ ناموفق بود'
        })

        return
      }

      if (!result.answer) {
        setError('root.server', {
          type: 'server',
          message:
            'پاسخ معتبری دریافت نشد'
        })

        return
      }

      setAnswer(result.answer)
    } catch {
      setError('root.server', {
        type: 'network',
        message:
          'ارتباط با سرور برقرار نشد'
      })
    }
  }

  return (
    <section>
      <form onSubmit={handleSubmit(onSubmit)}>
        <div>
          <label htmlFor="prompt">
            پرامپت
          </label>

          <textarea
            id="prompt"
            rows={8}
            maxLength={4000}
            placeholder="درخواست خود را دقیق بنویسید..."
            {...register('prompt')}
            aria-invalid={Boolean(errors.prompt)}
          />

          <p aria-live="polite">
            {(prompt?.length ?? 0)
              .toLocaleString('fa-IR')}
            {' از '}
            ۴٬۰۰۰ کاراکتر
          </p>

          {errors.prompt ? (
            <p role="alert">
              {errors.prompt.message}
            </p>
          ) : null}
        </div>

        <div>
          <label htmlFor="model">
            Model ID
          </label>

          <input
            id="model"
            {...register('model')}
            aria-invalid={Boolean(errors.model)}
          />

          {errors.model ? (
            <p role="alert">
              {errors.model.message}
            </p>
          ) : null}
        </div>

        <div>
          <label htmlFor="temperature">
            Temperature
          </label>

          <input
            id="temperature"
            type="number"
            min="0"
            max="2"
            step="0.1"
            {...register('temperature', {
              valueAsNumber: true
            })}
            aria-invalid={
              Boolean(errors.temperature)
            }
          />

          {errors.temperature ? (
            <p role="alert">
              {errors.temperature.message}
            </p>
          ) : null}
        </div>

        {errors.root?.server ? (
          <p role="alert">
            {errors.root.server.message}
          </p>
        ) : null}

        <div>
          <button
            type="submit"
            disabled={isSubmitting}
          >
            {isSubmitting
              ? 'در حال پردازش...'
              : 'ارسال درخواست'}
          </button>

          <button
            type="button"
            onClick={() => {
              reset()
              setAnswer('')
            }}
            disabled={isSubmitting}
          >
            پاک کردن
          </button>
        </div>
      </form>

      {answer ? (
        <article aria-live="polite">
          <h2>پاسخ مدل</h2>
          <p>{answer}</p>
        </article>
      ) : null}
    </section>
  )
}

Route Handler امن در Next.js

کلید API باید در فایل .env.local قرار گیرد:

DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY

Route Handler:

// src/app/api/chat/route.ts

import { z } from 'zod'

const requestSchema = z.object({
  prompt: z
    .string()
    .trim()
    .min(10)
    .max(4000),

  model: z
    .string()
    .trim()
    .min(1),

  temperature: z
    .number()
    .min(0)
    .max(2)
})

type DarvarehResponse = {
  choices?: Array<{
    message?: {
      content?: string
    }
  }>
}

export async function POST(request: Request) {
  try {
    const body: unknown = await request.json()
    const result = requestSchema.safeParse(body)

    if (!result.success) {
      return Response.json(
        {
          error: 'اطلاعات فرم معتبر نیست'
        },
        {
          status: 400
        }
      )
    }

    const apiKey =
      process.env.DARVAREH_API_KEY

    if (!apiKey) {
      console.error(
        'DARVAREH_API_KEY is missing'
      )

      return Response.json(
        {
          error: 'تنظیمات سرویس کامل نیست'
        },
        {
          status: 500
        }
      )
    }

    const {
      prompt,
      model,
      temperature
    } = result.data

    const controller = new AbortController()

    const timeoutId = setTimeout(() => {
      controller.abort()
    }, 30_000)

    try {
      const response = await fetch(
        'https://api.darvareh.ir/v1/chat/completions',
        {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${apiKey}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model,
            temperature,
            messages: [
              {
                role: 'user',
                content: prompt
              }
            ]
          }),
          signal: controller.signal,
          cache: 'no-store'
        }
      )

      if (!response.ok) {
        console.error(
          'Darvareh request failed',
          response.status
        )

        return Response.json(
          {
            error:
              response.status === 429
                ? 'تعداد درخواست‌ها زیاد است؛ کمی بعد دوباره تلاش کنید'
                : 'دریافت پاسخ از سرویس ناموفق بود'
          },
          {
            status:
              response.status === 429
                ? 429
                : 502
          }
        )
      }

      const data =
        (await response.json()) as DarvarehResponse

      const answer =
        data.choices?.[0]?.message?.content?.trim()

      if (!answer) {
        return Response.json(
          {
            error: 'پاسخ معتبری دریافت نشد'
          },
          {
            status: 502
          }
        )
      }

      return Response.json({
        answer
      })
    } finally {
      clearTimeout(timeoutId)
    }
  } catch (error) {
    if (
      error instanceof Error &&
      error.name === 'AbortError'
    ) {
      return Response.json(
        {
          error:
            'زمان انتظار درخواست به پایان رسید'
        },
        {
          status: 504
        }
      )
    }

    console.error('Chat route error', error)

    return Response.json(
      {
        error:
          'خطایی در پردازش درخواست رخ داد'
      },
      {
        status: 500
      }
    )
  }
}

کلید API نباید در Client Component، فایل عمومی، Repository یا متغیر دارای پیشوند NEXT_PUBLIC_ قرار گیرد.

برای دریافت Model ID و بررسی مدل‌های قابل‌استفاده به صفحه مدل‌های درواره مراجعه کنید.

تست فرم React Hook Form

فرم را می‌توان با Vitest و React Testing Library آزمایش کرد:

import {
  render,
  screen
} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {
  describe,
  expect,
  it,
  vi
} from 'vitest'

import {
  SimpleForm
} from './SimpleForm'

describe('SimpleForm', () => {
  it('برای فیلد خالی خطا نمایش می‌دهد', async () => {
    const user = userEvent.setup()
    const onSubmit = vi.fn()

    render(
      <SimpleForm onSubmit={onSubmit} />
    )

    await user.click(
      screen.getByRole('button', {
        name: 'ارسال'
      })
    )

    expect(
      await screen.findByRole('alert')
    ).toHaveTextContent(
      'متن درخواست الزامی است'
    )

    expect(onSubmit).not.toHaveBeenCalled()
  })

  it('اطلاعات معتبر را ارسال می‌کند', async () => {
    const user = userEvent.setup()
    const onSubmit = vi.fn()

    render(
      <SimpleForm onSubmit={onSubmit} />
    )

    await user.type(
      screen.getByLabelText('متن درخواست'),
      'یک عنوان برای مقاله پیشنهاد بده'
    )

    await user.click(
      screen.getByRole('button', {
        name: 'ارسال'
      })
    )

    expect(onSubmit).toHaveBeenCalledWith(
      {
        prompt:
          'یک عنوان برای مقاله پیشنهاد بده'
      },
      expect.anything()
    )
  })
})

در تست فرم بهتر است رفتار قابل‌مشاهده کاربر بررسی شود:

  • آیا خطا نمایش داده می‌شود؟
  • آیا فیلد نامعتبر مشخص است؟
  • آیا Submit برای داده نامعتبر اجرا نمی‌شود؟
  • آیا داده معتبر به تابع Submit می‌رسد؟
  • آیا Loading نمایش داده می‌شود؟
  • آیا خطای Backend قابل‌فهم است؟

بهینه‌سازی Performance فرم

React Hook Form برای کاهش Re-render طراحی شده است، اما معماری نامناسب همچنان می‌تواند عملکرد فرم را کاهش دهد.

راهکارهای پیشنهادی:

  • از watch() بدون نام در کل فرم بزرگ استفاده نکنید.
  • برای بخش‌های جدا از useWatch استفاده کنید.
  • کامپوننت‌های سنگین را از فرم جدا کنید.
  • Validation پیچیده را بدون نیاز روی هر onChange اجرا نکنید.
  • برای Inputهای استاندارد از register استفاده کنید.
  • Controller را فقط برای Controlled Componentها به کار ببرید.
  • از field.id به‌عنوان Key در useFieldArray استفاده کنید.
  • فرم بسیار بزرگ را به بخش‌های منطقی تقسیم کنید.
  • Context فرم را فقط در محدوده لازم قرار دهید.
  • از ذخیره هم‌زمان تمام مقادیر فرم در State جداگانه خودداری کنید.

اشتباهات رایج در React Hook Form

استفاده هم‌زمان از register و value

این کد می‌تواند باعث تداخل مدیریت State شود:

<input
  value={name}
  {...register('name')}
/>

یا فیلد را با register مدیریت کنید یا اگر Controlled است از Controller استفاده کنید.

فراموش کردن defaultValues

نبود مقدار اولیه مشخص می‌تواند باعث رفتار نامشخص Reset، Dirty State یا Warning مربوط به Controlled و Uncontrolled شود.

اعتماد به Validation سمت Client

Validation Client قابل‌دور زدن است. Backend باید ورودی را دوباره بررسی کند.

استفاده از disabled برای فیلدی که باید ارسال شود

فیلد Disabled معمولاً در خروجی فرم قرار نمی‌گیرد. در صورت نیاز از readOnly یا مقدار جایگزین استفاده کنید.

استفاده از Index به‌عنوان Key

در useFieldArray از field.id استفاده کنید:

key={field.id}

قراردادن کلید API در فرم

React Hook Form محل نگهداری Secret نیست. کلید API باید در Backend یا Route Handler باقی بماند.

اعتبارسنجی Async در هر کاراکتر

بررسی‌هایی مانند موجود بودن Username در سرور را نباید بدون Debounce با هر تغییر اجرا کنید. این کار تعداد درخواست‌ها را افزایش می‌دهد.

ثبت اطلاعات حساس در Console

در محیط Production داده‌هایی مانند رمز عبور، Token، اطلاعات هویتی یا محتوای محرمانه فرم را در Console ثبت نکنید.

استفاده افراطی از Controller

برای Input، Select و Textarea استاندارد معمولاً register کافی است.

ارسال چندباره فرم

دکمه Submit را هنگام isSubmitting غیرفعال کنید و در Backend نیز از راهکار مناسب برای جلوگیری از عملیات تکراری استفاده کنید.

چک‌لیست ساخت فرم حرفه‌ای

  • آیا Type تمام فیلدها تعریف شده است؟
  • آیا defaultValues مشخص‌اند؟
  • آیا Label هر فیلد به Input متصل است؟
  • آیا پیام خطا واضح و نزدیک فیلد نمایش داده می‌شود؟
  • آیا aria-invalid و role="alert" استفاده شده‌اند؟
  • آیا Validation سمت Client و Server وجود دارد؟
  • آیا Submit Async خطا را مدیریت می‌کند؟
  • آیا دکمه هنگام ارسال غیرفعال می‌شود؟
  • آیا کاربر Loading State را می‌بیند؟
  • آیا خطاهای Backend به فیلد درست نگاشت می‌شوند؟
  • آیا اطلاعات حساس در Client ذخیره نشده‌اند؟
  • آیا کلید API فقط در Backend قرار دارد؟
  • آیا File Type و File Size در سرور نیز بررسی می‌شوند؟
  • آیا Reset فرم رفتار صحیحی دارد؟
  • آیا فیلدهای پویا Key پایدار دارند؟
  • آیا فرم با Keyboard قابل‌استفاده است؟
  • آیا تست سناریوهای موفق و ناموفق نوشته شده است؟
  • آیا متن دکمه‌ها و خطاها برای کاربر قابل‌فهم است؟

پرسش‌های متداول درباره React Hook Form

آیا React Hook Form فقط برای TypeScript است؟

خیر. این کتابخانه در JavaScript و TypeScript قابل‌استفاده است؛ اما TypeScript ایمنی و تجربه توسعه بهتری فراهم می‌کند.

React Hook Form بهتر است یا Formik؟

هر دو برای مدیریت فرم ساخته شده‌اند. React Hook Form معمولاً بر استفاده از Inputهای Uncontrolled، کاهش Re-render و API مبتنی بر Hook تمرکز دارد. انتخاب نهایی به معماری پروژه و تجربه تیم بستگی دارد.

آیا استفاده از Zod اجباری است؟

خیر. می‌توانید از قوانین داخلی register، تابع validate یا Resolverهای دیگر استفاده کنید. Zod برای تعریف Schema مشترک و Type-safe مفید است.

آیا می‌توان یک Schema را در Client و Server استفاده کرد؟

بله، اگر پروژه Full-Stack مانند Next.js دارید می‌توانید Schema مشترک را در هر دو سمت استفاده کنید. با این حال باید مطمئن شوید فایل مشترک به وابستگی‌های مخصوص سرور یا Client وابسته نیست.

تفاوت register و Controller چیست؟

register برای Inputهای استاندارد HTML مناسب است. Controller برای کامپوننت‌های Controlled و کتابخانه‌های UI که API متفاوتی دارند استفاده می‌شود.

تفاوت watch و getValues چیست؟

watch تغییر مقدار را دنبال کرده و باعث بروزرسانی بخش Subscribe‌شده می‌شود. getValues فقط مقدار فعلی را بدون Subscription دریافت می‌کند.

آیا می‌توان React Hook Form را در Next.js استفاده کرد؟

بله. کامپوننت فرم باید به‌دلیل استفاده از Hook و Event Handler یک Client Component باشد. پردازش امن، Secret و ارتباط با API خارجی باید در Route Handler یا Backend انجام شود.

آیا می‌توان با React Hook Form فرم چت‌بات ساخت؟

بله. Prompt، تنظیمات مدل، فایل و سایر ورودی‌ها را می‌توان با React Hook Form مدیریت کرد و داده معتبر را به Backend فرستاد.

آیا کلید درواره را باید داخل فرم قرار دهیم؟

خیر. کلید API باید در Environment Variable سمت سرور نگهداری شود. Client فقط باید Endpoint داخلی برنامه را فراخوانی کند.

جمع‌بندی

React Hook Form ابزار مناسبی برای مدیریت فرم‌های React و Next.js است. این کتابخانه با APIهایی مانند useForm، register، Controller، useFieldArray، FormProvider و setError بسیاری از نیازهای رایج فرم را پوشش می‌دهد.

مسیر پیشنهادی یادگیری آن چنین است:

  1. ابتدا useForm، register و handleSubmit را یاد بگیرید.
  2. خطاها را با formState.errors نمایش دهید.
  3. مقدار اولیه را با defaultValues مشخص کنید.
  4. Submitهای Async را با isSubmitting مدیریت کنید.
  5. برای فرم‌های پیچیده از Zod استفاده کنید.
  6. Validation را در Backend نیز تکرار کنید.
  7. برای کامپوننت‌های Controlled از Controller استفاده کنید.
  8. فیلدهای پویا را با useFieldArray بسازید.
  9. خطاهای Server را با setError نمایش دهید.
  10. رفتار فرم را با Testing Library آزمایش کنید.

برای ساخت برنامه‌های مبتنی بر هوش مصنوعی می‌توانید فرم React را به Backend متصل کرده و از API یکپارچه درواره استفاده کنید. فهرست مدل‌ها، قابلیت‌ها و Model IDهای قابل‌استفاده در صفحه مدل‌های درواره قرار دارد.

مقالات مرتبط

منابع تکمیلی

برای مطالعه شرایط استفاده و محدودیت‌های مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.

Read more

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی با ترکیب گردش‌کارهای خودکار و مدل‌های هوش مصنوعی، پردازش متن، دسته‌بندی، استخراج اطلاعات و تصمیم‌های پیشنهادی را خودکار می‌کند. در این راهنما، معماری و ساخت نمونه عملی آن با API درواره را می‌آموزید.

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce شیوه‌ای جدید برای خرید اینترنتی است که در آن ایجنت هوش مصنوعی می‌تواند نیاز کاربر را بفهمد، محصولات را جست‌وجو و مقایسه کند و فرایند خرید را پیش ببرد. در این راهنما با معماری، UCP، ACP و پیاده‌سازی آن با API درواره آشنا می‌شوید.