קומפוננטה מבוקרת (controlled) היא אלמנט טופס שהערך שלו חי ב-state של React: מעבירים value ומעדכנים את ה-state ב-onChange, כך ש-React מחליטה מה השדה מציג. קומפוננטה לא מבוקרת (uncontrolled) שומרת את הערך שלה ב-DOM, כמו HTML רגיל: מעבירים defaultValue התחלתי וקוראים את הערך הנוכחי אחר כך דרך ref או FormData.
הקלידו בשני השדות. הפסקה עוקבת אחרי הראשון בכל הקשה, כי כל שינוי קובע state ומרנדר שוב. השדה השני משתנה רק ב-DOM; React לא מרנדרת, ואת הערך שלו רואים רק כש-"Read both" מבקש אותו.
שדות מבוקרים
בשדה מבוקר, ה-state של React הוא מקור האמת היחיד. הזרימה בכל הקשה היא:
- המשתמש מקליד, והדפדפן מפעיל אירוע input.
- ה-handler של
onChangeקורא אתe.target.valueוקורא ל-setter. - React מרנדרת שוב וכותבת את ה-state החדש בחזרה לתוך ה-
valueשל השדה.
מכיוון שהקוד שלכם יושב באמצע הלולאה הזו, אפשר לעשות דברים ששדה לא מבוקר לא יכול: לדחות תווים או לשנות את צורתם, להציג את הערך במקום אחר, להשבית כפתור עד שהערך תקין, או לקבוע את הערך מהקוד (כפתור "Clear", הצעה שהמשתמש לוחץ עליה).
נסו להקליד אותיות: הן אף פעם לא מופיעות, כי ה-handler מסיר כל מה שאינו ספרה לפני שהוא קובע state. כפתור ה-Verify נשאר מושבת עד שיש בדיוק שש ספרות.
שדות מבוקרים הם מה שדוגמאות הטפסים ב-דף הטפסים משתמשות בו, וזו בחירת ברירת המחדל ברוב קוד ה-React.
שדות לא מבוקרים
שדה לא מבוקר עובד כמו HTML רגיל. React מרנדרת אותו פעם אחת עם defaultValue (או defaultChecked ב-checkboxes ורדיו) ואז עוזבת אותו. כדי לקרוא את הערך, או שמחזיקים ref לאלמנט או שקוראים את כל השדות בעלי שם מהטופס בשליחה עם FormData.
אין state לשדות ואין onChange. הערכים נאספים רק כשהטופס נשלח. בטלו את הסימון ב-checkbox ושלחו שוב: המפתח subscribe נעלם, כי checkbox לא מסומן לא נכלל ב-FormData בכלל.
שינוי של defaultValue אחרי הרינדור הראשון לא עושה כלום לשדה, כי React מחילה אותו רק כשהאלמנט נוצר. כדי לאפס טופס לא מבוקר, קראו ל-e.target.reset(), או תנו לטופס key חדש כדי ש-React תיצור שדות חדשים.
שדה שאי אפשר להקליד בו
אם מעבירים value אבל לא onChange, השדה מבוקר, אבל שום דבר אף פעם לא מעדכן את ה-state. React כותבת את אותו ערך בחזרה אחרי כל הקשה, ולכן השדה נראה קפוא.
נסו להקליד בכל שדה. הראשון לא משתנה. השני הוא התיקון לשדה מבוקר, וכשמקלידים בו, השדה הראשון והאחרון עוקבים אחריו, כי שלושתם קוראים את אותו state. השלישי מקבל הקלדה כי הוא לא מבוקר. הרביעי אומר בכוונה שאי אפשר לערוך אותו.
בפיתוח, React רושמת אזהרה על השדה הראשון: "You provided a value prop to a form field without an onChange handler". התצוגה המקדימה רצה כמו build של פרודקשן, אז לא תראו כאן את האזהרה הזו, אבל תראו אותה בשרת הפיתוח שלכם.
מעבר מלא מבוקר למבוקר
React מחליטה אם שדה מבוקר לפי value. אם value הוא undefined או null, השדה לא מבוקר. לכן שדה שה-value שלו מתחיל כ-undefined ואחר כך הופך למחרוזת עובר מלא מבוקר למבוקר במהלך חייו, ו-React מזהירה בפיתוח: "A component is changing an uncontrolled input to be controlled".
האזהרה מצביעה על באג אמיתי. מעבר בכיוון ההפוך, ממחרוזת ל-undefined, הופך את השדה שוב ללא מבוקר, ו-React מפסיקה לכתוב אליו.
הקלידו משהו בשני השדות ולחצו על כל כפתור Clear. השדה השני מתרוקן. הראשון שומר על הטקסט שלו: קביעת ה-state ל-undefined הפכה אותו ללא מבוקר, ולכן React כבר לא שולטת במה שהוא מציג.
המקור הרגיל של undefined הוא נתונים שעוד לא נטענו (value={user.name} לפני שהמשתמש הגיע) או שדה באובייקט שעוד לא קיים. שני תיקונים:
const [name, setName] = useState(''); // start with a string
<input value={user?.name ?? ''} /> // or fall back to one
ב-checkboxes אותו כלל חל על checked: התחילו אותו ב-false, לא undefined.
שדות קבצים תמיד לא מבוקרים
<input type="file"> לא יכול להיות מבוקר, כי דפדפנים לא מאפשרים לדף לקבוע את הערך שלו (דף שיכול לבחור קבצים בדיסק שלכם היה פרצת אבטחה). קראו את הקבצים שנבחרו מ-e.target.files ב-onChange, או דרך ref כשהטופס נשלח.
בחרו קובץ או שניים מהמחשב שלכם; שום דבר לא מועלה, הדוגמה רק מציגה שמות וגדלים. הערך היחיד שמותר לקבוע בשדה קובץ הוא המחרוזת הריקה, וכך כפתור ה-Clear מאפס אותו.
מתי לבחור במה
| מבוקר | לא מבוקר | |
|---|---|---|
| הערך חי ב | state של React | ה-DOM |
| קביעת ערך התחלתי | value / checked | defaultValue / defaultChecked |
| קריאת הערך | בכל זמן, מה-state | עם ref, או FormData בשליחה |
| מרנדר בכל הקשה | כן | לא |
| שינוי הערך מהקוד | קובעים state | כותבים ל-DOM דרך ref |
| מתאים ל | ולידציה חיה, עיצוב טקסט, שדות תלויים | טפסים פשוטים שנקראים בשליחה, שדות קבצים, form actions |
בחרו במבוקר כשהמסך צריך להגיב לערך תוך כדי הקלדה. בחרו בלא מבוקר כשצריך את הערכים רק בסוף, וכך גם עובדים form actions ב-React 19: <form action={fn}> מעביר לפונקציה שלכם FormData ומאפס אחר כך את השדות הלא מבוקרים.
רינדור בכל הקשה נשמע יקר, אבל בטופס רגיל זו לא בעיה. זה הופך לבעיה רק כשעץ גדול ואיטי מתרנדר מחדש יחד עם השדה; אז שמרו את ה-state קרוב לשדה, או העבירו את החלק האיטי לקומפוננטה משלו.
אותן שתי מילים מתארות גם את הקומפוננטות שלכם. קומפוננטה שמקבלת את הערך שלה ו-onChange מההורה דרך props נשלטת על ידי ההורה; קומפוננטה ששומרת state משלה היא לא מבוקרת. העברת state למעלה כדי להפוך קומפוננטה למבוקרת היא בדיוק הנושא של הרמת state למעלה.
שאלות נפוצות
מה זו קומפוננטה מבוקרת (controlled) ב-React?
אלמנט טופס שהערך הנוכחי שלו מגיע מ-state של React, נקבע עם value (או checked) ומתעדכן דרך onChange. ה-state של React הוא מקור האמת היחיד, כך שתמיד יודעים מה הערך ואפשר לשנות אותו מהקוד.
מה זו קומפוננטה לא מבוקרת (uncontrolled) ב-React?
אלמנט טופס ששומר את הערך שלו ב-DOM, כמו HTML רגיל. נותנים לו ערך התחלתי עם defaultValue (או defaultChecked) וקוראים את הערך הנוכחי כשצריך, דרך ref או FormData בשליחה.
למה אני לא יכול להקליד בשדה ה-React שלי?
העברתם value בלי onChange. השדה מבוקר, ולכן React ממשיכה להחזיר את ערך ה-state אחרי כל הקשה. הוסיפו onChange שמעדכן את ה-state, עברו ל-defaultValue, או הוסיפו readOnly אם הוא באמת לא אמור להשתנות.
איך מתקנים את "A component is changing an uncontrolled input to be controlled"?
ה-value של השדה התחיל כ-undefined (או null) ואחר כך הפך למחרוזת. ודאו שהוא תמיד מחרוזת: התחילו את ה-state ב-'', או כתבו value={name ?? ''}.
להשתמש בשדות מבוקרים או לא מבוקרים?
השתמשו בשדות מבוקרים כשה-UI מגיב לערך תוך כדי הקלדה: ולידציה, עיצוב הטקסט, כפתור מושבת, שדות שתלויים זה בזה. השתמשו בשדות לא מבוקרים כשצריך את הערכים רק בשליחה, ובשדות קבצים, שתמיד לא מבוקרים.