לדלג לתוכן

טפסים בריאקט - מדריך מעשי

טפסים הם אחד הדברים הראשונים שכל מפתח ריאקט צריך לבנות בפועל, ולעיתים קרובות גם אחד המקומות הראשונים שבהם מתגלה בלבול - למה השדה לא מתעדכן, למה הטופס נטען מחדש כשלוחצים שלח, ומה זה בכלל input "מבוקר".

input מבוקר - הדפוס הסטנדרטי בריאקט

בגישה הסטנדרטית של ריאקט, שדה קלט "מבוקר" (controlled input) הוא שדה שהערך שלו נשלט לגמרי על ידי state, לא על ידי ה-DOM עצמו.

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

  return (
    <input
      type="text"
      value={name}
      onChange={(event) => setName(event.target.value)}
    />
  );
}

מה שקורה כאן - כל תו שהמשתמש מקליד מפעיל onChange, ש"אומר" לריאקט מה הערך החדש, ריאקט שומר אותו ב-state, ואז מציג אותו בחזרה בשדה דרך value={name}. זה נשמע כמו הליכה מסובכת סביב עצמכם, אבל היתרון הוא שריאקט תמיד יודע בדיוק מה הערך הנוכחי של השדה, ואפשר להשתמש בו בכל מקום אחר באפליקציה - לוולידציה, לתצוגה חיה, או לשליחה לשרת.

למה לא סתם לתת ל-DOM לנהל את זה

טכנית, אפשר לתת לדפדפן לנהל את הערך של השדה בעצמו (input לא מבוקר), ולקרוא אותו רק כשצריך, בעזרת ref. זה נקרא uncontrolled input, וזה שימושי במקרים ספציפיים, אבל בדרך כלל פחות נוח בריאקט, כי מאבדים את הגישה החיה לערך בכל רגע - למשל, אי אפשר בקלות להציג "נותרו 20 תווים" תוך כדי הקלדה, כי ריאקט לא "יודע" מה נכתב עד שקוראים לזה במפורש.

function UncontrolledForm() {
  const inputRef = useRef(null);

  function handleSubmit(event) {
    event.preventDefault();
    console.log(inputRef.current.value);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input ref={inputRef} type="text" />
    </form>
  );
}

טיפול בטופס עם כמה שדות

כשיש כמה שדות, נוח לנהל אותם באובייקט state אחד במקום משתנה נפרד לכל שדה:

function SignupForm() {
  const [formData, setFormData] = useState({
    name: "",
    email: "",
    password: "",
  });

  function handleChange(event) {
    const { name, value } = event.target;
    setFormData({ ...formData, [name]: value });
  }

  return (
    <form>
      <input name="name" value={formData.name} onChange={handleChange} />
      <input name="email" value={formData.email} onChange={handleChange} />
      <input
        name="password"
        type="password"
        value={formData.password}
        onChange={handleChange}
      />
    </form>
  );
}

השורה [name]: value היא "מפתח מחושב" (computed property name) - היא מאפשרת להשתמש בתכונת name של השדה כדי לקבוע איזה שדה בדיוק ב-state לעדכן, כך שאותה פונקציית handleChange אחת יכולה לשרת את כל שדות הטופס.

מניעת רענון עמוד עם submit

כברירת מחדל, שליחת טופס גורמת לדפדפן לרענן את כל העמוד - התנהגות שירשנו מ-HTML "רגיל". באפליקציית ריאקט כמעט תמיד רוצים למנוע את זה, כדי לטפל בשליחה עם JavaScript:

function handleSubmit(event) {
  event.preventDefault(); // עוצר את הרענון האוטומטי
  console.log("נשלח:", formData);
}

return <form onSubmit={handleSubmit}>...</form>;

event.preventDefault() הוא צעד קריטי ששוכחים לעיתים קרובות, ואז תוהים למה הדף "מרענן את עצמו" ומאבדים את כל הנתונים.

ולידציה בסיסית בלי ספרייה

ולידציה פשוטה אפשר לעשות ישירות עם state ותנאים, בלי ספרייה חיצונית:

function SignupForm() {
  const [email, setEmail] = useState("");
  const [error, setError] = useState("");

  function handleSubmit(event) {
    event.preventDefault();

    if (!email.includes("@")) {
      setError("כתובת אימייל לא תקינה");
      return;
    }

    setError("");
    console.log("נשלח בהצלחה");
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      {error && <p style={{ color: "red" }}>{error}</p>}
      <button type="submit">שלח</button>
    </form>
  );
}

לפרויקטים גדולים יותר עם הרבה שדות וכללי ולידציה מורכבים, ספריות ייעודיות כמו React Hook Form חוסכות המון קוד חוזר, אבל להבין איך זה עובד ידנית קודם הוא בסיס חשוב שעוזר להבין מה בדיוק הספרייה עושה בשבילכם מאחורי הקלעים.

checkbox ו-select - אותו עיקרון, תחביר קצת שונה

תיבות סימון עובדות עם checked במקום value, ומשתמשות בשדה event.target.checked:

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

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

למה זה שווה תרגול

טפסים נראים פשוטים, אבל הם דוגמה מצוינת לכל מה שלמדתם על state - עדכון בלי שינוי ישיר, עבודה עם אובייקטים, ותגובה לאירועי משתמש. אם טפסים מרגישים לכם קלים, זה סימן טוב שהיסודות של ריאקט התיישבו.

בקורס צד לקוח יש פרק מעשי שלם על טפסים, כולל בניית טופס הרשמה אמיתי עם ולידציה ומשוב למשתמש.

לסיכום

input מבוקר בריאקט קושר את הערך של השדה ל-state, דרך value ו-onChange, וזה הדפוס המומלץ ברוב המקרים. חובה לקרוא ל-event.preventDefault() כדי למנוע רענון עמוד בשליחת טופס, ואפשר לבנות ולידציה בסיסית ישירות עם state ותנאים לפני שפונים לספריות חיצוניות.

בונים טופס ומשהו לא מתעדכן כמו שציפיתם? תשתפו בדיסקורד.

הצטרפו לקהילה בדיסקורד