عناصر فرم و کنترل آن‌ها با Controlled Components

در فصل دوم به‌طور مختصر با تفاوت Controlled و Uncontrolled Components آشنا شدیم و دیدیم چگونه مقدار یک input را با State هماهنگ کنیم. در این درس، همان الگو را برای انواع دیگر عناصر فرم مانند textarea، checkbox، radio و select بررسی می‌کنیم، چون هرکدام جزئیات کمی متفاوت دارند.

یادآوری کوتاه: Controlled یعنی چه

در یک عنصر Controlled، مقدار نمایش‌داده‌شده همیشه از State می‌آید، و هر تغییری که کاربر انجام می‌دهد، ابتدا State را به‌روزرسانی می‌کند و سپس همان State جدید، دوباره به عنصر بازگردانده می‌شود. به همین دلیل، در هر لحظه، عنصر فرم دقیقاً بازتابی از State است.

input از نوع text

همان الگویی که پیش‌تر دیدیم:

function NameField() {
  const [name, setName] = useState("");

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

textarea

در HTML معمولی، مقدار textarea بین تگ باز و بسته آن نوشته می‌شود، نه به‌عنوان یک Attribute. اما در JSX، برای هماهنگی با سایر عناصر فرم، textarea هم از همان ویژگی value استفاده می‌کند:

function MessageField() {
  const [message, setMessage] = useState("");

  return (
    <textarea
      value={message}
      onChange={(e) => setMessage(e.target.value)}
    />
  );
}

checkbox

برای checkbox، به‌جای value، از ویژگی checked استفاده می‌شود که یک مقدار Boolean است:

function AgreeCheckbox() {
  const [agreed, setAgreed] = useState(false);

  return (
    <input
      type="checkbox"
      checked={agreed}
      onChange={(e) => setAgreed(e.target.checked)}
    />
  );
}

نکته مهم این است که برای checkbox، مقدار موردنیاز از e.target.checked خوانده می‌شود، نه e.target.value.

radio

برای گروهی از دکمه‌های radio، معمولاً یک State مشترک برای کل گروه نگه داشته می‌شود که مقدار گزینه انتخاب‌شده را ذخیره می‌کند:

function PlanSelector() {
  const [plan, setPlan] = useState("basic");

  return (
    <div>
      <label>
        <input
          type="radio"
          value="basic"
          checked={plan === "basic"}
          onChange={(e) => setPlan(e.target.value)}
        />
        پایه
      </label>
      <label>
        <input
          type="radio"
          value="pro"
          checked={plan === "pro"}
          onChange={(e) => setPlan(e.target.value)}
        />
        حرفه‌ای
      </label>
    </div>
  );
}

در این الگو، هر دکمه radio زمانی انتخاب‌شده در نظر گرفته می‌شود که مقدار State برابر با value همان دکمه باشد.

select

برخلاف HTML معمولی که گزینه انتخاب‌شده در select با ویژگی selected روی خود تگ option مشخص می‌شود، در JSX همان الگوی آشنای value روی خود تگ select استفاده می‌شود:

function CitySelector() {
  const [city, setCity] = useState("tehran");

  return (
    <select value={city} onChange={(e) => setCity(e.target.value)}>
      <option value="tehran">تهران</option>
      <option value="mashhad">مشهد</option>
      <option value="isfahan">اصفهان</option>
    </select>
  );
}

یک نکته مهم: خواندن مقدار درست بسته به نوع عنصر

نکته‌ای که در تمام مثال‌های بالا تکرار شد، این است که رویداد onChange بسته به نوع عنصر، اطلاعات را در جای متفاوتی از آبجکت رویداد قرار می‌دهد: برای عناصر متنی و select از e.target.value، و برای checkbox از e.target.checked استفاده می‌شود. فراموش کردن این تفاوت، یکی از اشتباهات رایج در مدیریت فرم‌هاست.

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

فرم کوچک با چند نوع عنصر Controlled
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

هرکدام از عناصر فرم، برای تبدیل شدن به یک Controlled Component، از ویژگی و منبع داده متفاوتی استفاده می‌کنند: input و textarea از value، checkbox از checked، و select هم از value روی خود تگ select، نه روی option‌ها. در همه موارد، اصل کلی یکسان است: مقدار همیشه از State می‌آید و onChange همان State را به‌روزرسانی می‌کند. در درس بعدی به سراغ Uncontrolled Components و موقعیت‌هایی می‌رویم که این رویکرد جایگزین مناسبی است.