الگوهای رایج در مدیریت فرم‌ها

در طول این فصل، بخش‌های مختلف مدیریت فرم را جداگانه بررسی کردیم: عناصر Controlled، اعتبارسنجی، رویدادها و Submit. در این درس، چند الگوی تکمیلی و رایج را مرور می‌کنیم که در فرم‌های واقعی زیاد به کار می‌روند و معمولاً ترکیبی از همان مفاهیمی هستند که تا اینجا یاد گرفتیم.

Reset کردن فرم پس از ارسال موفق

پس از ارسال موفق یک فرم، معمولاً منطقی است که مقادیر آن به حالت اولیه بازگردند. چون در فرم‌های Controlled، مقدار هر فیلد از State می‌آید، Reset کردن فرم فقط به معنای بازگرداندن State به مقدار اولیه است:

const initialFormData = { name: "", email: "" };

function ContactForm() {
  const [formData, setFormData] = useState(initialFormData);

  function handleSubmit(e) {
    e.preventDefault();
    console.log("ارسال:", formData);
    setFormData(initialFormData);
  }

  return <form onSubmit={handleSubmit}>{/* فیلدها */}</form>;
}

غیرفعال کردن دکمه بر اساس اعتبار فرم

یک الگوی رایج دیگر، محاسبه اعتبار کلی فرم به‌عنوان یک Derived State (که در فصل سوم با آن آشنا شدیم) و استفاده از آن برای غیرفعال کردن دکمه ارسال، پیش از این‌که کاربر حتی Submit را بزند:

function SignupForm() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const isValid = email.includes("@") && password.length >= 6;

  return (
    <form>
      {/* فیلدها */}
      <button type="submit" disabled={!isValid}>
        ثبت‌نام
      </button>
    </form>
  );
}

در این کد، isValid در هر رندر مستقیماً از روی مقدار فعلی email و password محاسبه می‌شود؛ نیازی به یک State جداگانه برای اعتبار کلی فرم نیست.

نمایش خطا فقط پس از تعامل کاربر با فیلد

یک مشکل رایج در اعتبارسنجی لحظه‌ای این است که پیام خطا از همان اولین رندر، پیش از این‌که کاربر اصلاً چیزی تایپ کند، نمایش داده می‌شود. برای جلوگیری از این تجربه آزاردهنده، معمولاً وضعیت «آیا کاربر با این فیلد تعامل داشته یا نه» (معمولاً Touched نامیده می‌شود) هم دنبال می‌شود:

function EmailField() {
  const [email, setEmail] = useState("");
  const [touched, setTouched] = useState(false);

  const showError = touched && !email.includes("@");

  return (
    <div>
      <input
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        onBlur={() => setTouched(true)}
      />
      {showError && <p>ایمیل معتبر نیست</p>}
    </div>
  );
}

در این الگو، از رویداد onBlur (خروج فوکوس از عنصر) برای علامت‌گذاری فیلد به‌عنوان Touched استفاده شده؛ یعنی خطا فقط پس از این‌که کاربر یک‌بار وارد فیلد شده و از آن خارج شده، نمایش داده می‌شود.

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

در فرم‌های بزرگ‌تر که به چند مرحله (مثلاً چند صفحه یا چند بخش) تقسیم می‌شوند، یک الگوی رایج، نگه‌داشتن یک State برای مرحله فعلی، در کنار یک State مشترک برای داده کل فرم است:

function MultiStepForm() {
  const [step, setStep] = useState(1);
  const [formData, setFormData] = useState({ name: "", email: "" });

  return (
    <div>
      {step === 1 && <NameStep formData={formData} setFormData={setFormData} />}
      {step === 2 && <EmailStep formData={formData} setFormData={setFormData} />}

      <button onClick={() => setStep(step - 1)}>قبلی</button>
      <button onClick={() => setStep(step + 1)}>بعدی</button>
    </div>
  );
}

در این ساختار، هر مرحله فقط بخشی از داده کل فرم را نمایش می‌دهد، اما همه مراحل به همان یک منبع داده مشترک متصل هستند؛ الگویی که پیش‌تر در فصل سوم به‌عنوان Lifting State Up با آن آشنا شدیم.

مثال قابل اجرا

فرم با اعتبارسنجی، Touched و Reset
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

الگوهایی مانند Reset کردن فرم پس از ارسال، غیرفعال کردن دکمه بر اساس اعتبار فرم، نمایش خطا فقط پس از تعامل کاربر با فیلد، و مدیریت فرم‌های چندمرحله‌ای، ترکیبی از همان مفاهیم پایه‌ای هستند که در این فصل با آن‌ها آشنا شدیم. با این درس، فصل چهارم از نظر مفهومی به پایان می‌رسد و در ادامه به سراغ آزمون فصل ۴ می‌رویم.