useId מחזיר מזהה ייחודי לכל מופע של קומפוננטה, ואת אותו מזהה בכל רינדור. השתמשו בו כדי לחבר <label> ל-<input> שלו, או שדה קלט לרמז שלו דרך aria-describedby, כשהקומפוננטה יכולה להופיע יותר מפעם אחת בדף.
אותה קומפוננטה מתרנדרת פעמיים ומקבלת שני מזהים שונים, שמודפסים מתחת לכל שדה. לחצו על הטקסט "Confirm password" והפוקוס קופץ לשדה השני, כי ה-htmlFor שלו תואם רק ל-id של השדה הזה. כתבו קבוע id="password" במקום useId() ושתי התוויות מצביעות על השדה הראשון.
התחביר
const id = useId();
useId לא מקבל ארגומנטים ומחזיר מחרוזת. קראו לו ברמה העליונה של הקומפוננטה, כמו לכל הוק. הפורמט המדויק שלו פנימי והשתנה בין גרסאות: React 18 ייצרה :r1:, ו-React 19.2 מייצרת _r_1_ לקומפוננטה שרונדרה לראשונה בדפדפן, ומזהה שמתחיל ב-_R_, שנבנה ממיקום בעץ, לקומפוננטה שרונדרה בשרת. אף פעם אל תפענחו אותו ואל תסתמכו על הצורה שלו.
למה לא Math.random או מונה
מזהים לנגישות צריכים להתאים בין ה-HTML שהשרת שולח לבין העץ ש-React בונה בדפדפן. שתי הדרכים המתבקשות ליצור מזהה נכשלות שתיהן במבחן הזה.
// Changes on every render, and differs between server and browser
const id = 'field-' + Math.random().toString(36).slice(2);
// The server's counter keeps growing across requests, the browser starts at 0
let nextId = 0;
const id = 'field-' + nextId++;
עם רינדור בצד השרת (Next.js, מצב ה-framework של React Router, כל הגדרה עם hydrateRoot), השרת מדפיס id="field-4817" לתוך ה-HTML, הרינדור הראשון בדפדפן מחשב field-0, ו-React מדווחת על hydration mismatch. useId בונה את המזהה מהמיקום של הקומפוננטה בעץ, שזהה בשני הצדדים.
גם בלי שרת, מזהה שנבנה בזמן הרינדור יכול להשתנות בכל רינדור. הדוגמה הזו מראה את ההבדל בלי שום שרת:
לחצו על הכפתור כמה פעמים. הערך של useId נשאר במקום ואילו מזהה המונה מטפס בכל רינדור, כך שכל מה שהצביע על המזהה הישן (aria-describedby, תווית) מצביע עכשיו על כלום. עטיפת המונה ב-useState(() => nextId++) הייתה מתקנת את הרינדורים מחדש, אבל לא את אי ההתאמה מול השרת.
כמה מזהים מקריאה אחת
קומפוננטה עם כמה שדות לא צריכה כמה קריאות useId. צרו בסיס אחד והוסיפו סיומת לכל אלמנט.
הקלידו מילה בלי @ בשדה האימייל: השגיאה מופיעה וה-aria-describedby של השדה מצביע עליה, כך שקורא מסך מקריא את השגיאה כשהשדה מקבל פוקוס. רנדרו <SignupForm /> פעמיים ב-App וכל עותק מקבל מזהה בסיס משלו.
לא בשביל keys של רשימות
keys ומזהים פותרים בעיות שונות. key אומר ל-React איזה פריט הוא איזה בין רינדורים, ולכן הוא חייב להגיע מהנתונים. useId נותן מזהה אחד לכל מופע של קומפוננטה, ואי אפשר לקרוא לו בתוך map.
// Wrong: breaks the rules of hooks, and the key is unrelated to the item
{todos.map((todo) => <Todo key={useId()} todo={todo} />)}
// Right: the key comes from the data
{todos.map((todo) => <Todo key={todo.id} todo={todo} />)}
כשלנתונים שלכם אין id, צרו אחד כשהפריט נוצר (crypto.randomUUID() ב-event handler שמוסיף אותו), לא בזמן הרינדור. הדף על רשימות ו-keys מסביר למה ה-key חייב להישאר עם הפריט שלו.
כמה roots של React בדף אחד
אם שתי אפליקציות React נפרדות מתרנדרות באותו דף, המזהים שלהן עלולים להתנגש. תנו לכל root קידומת:
createRoot(document.getElementById('cart'), { identifierPrefix: 'cart-' });
createRoot(document.getElementById('chat'), { identifierPrefix: 'chat-' });
עם רינדור בצד השרת, העבירו את אותו identifierPrefix לרנדרר בשרת ול-hydrateRoot, כדי ששני הצדדים ייצרו את אותם מזהים.
טעויות נפוצות
רינדור עץ שונה בשרת ובדפדפן. useId תלוי במיקום של הקומפוננטה, ולכן ענף כמו typeof window === 'undefined' ? <A /> : <B /> מעל שדה יכול להזיז את המזהים בין שני הרינדורים. שמרו על אותו עץ בזמן ה-hydration והחליפו אחריו, באפקט.
חיפוש האלמנט לפי ה-id שלו. document.getElementById(id) עובד, אבל ref הוא הדרך של React להגיע לצומת DOM והוא לא צריך id בכלל.
שימוש בו כערך אקראי. המזהה ייחודי בתוך האפליקציה, לא אקראי, ואפשר לחזות אותו מהעץ. אל תשתמשו בו לטוקנים של אבטחה, למפתחות cache או לכל דבר שנשמר בין סשנים.
מתי להשתמש בו
השתמשו ב-useId בכל פעם שקומפוננטה לשימוש חוזר צריכה מאפיין id: שדות טופס שנבנים מ-קומפוננטה שמשתמשים בה פעמים רבות, tooltip שמקושר עם aria-describedby, דיאלוג עם aria-labelledby, טאבים עם aria-controls. כשמזהה רק מחבר תווית לשדה שלה, אפשר גם לוותר עליו ולקנן את השדה בתוך התווית (<label>Name <input /></label>); השתמשו ב-useId כשאי אפשר לקנן את האלמנטים.
שאלות נפוצות
מה useId עושה ב-React?
הוא מחזיר מחרוזת ייחודית למופע הזה של הקומפוננטה, שנשארת זהה בכל רינדור. משתמשים בה כדי לחבר אלמנטים לפי id: htmlFor על תווית, aria-describedby על שדה קלט, aria-labelledby על דיאלוג.
למה לא להשתמש ב-Math.random() או במונה בשביל מזהים?
הם נותנים ערכים שונים בשרת ובדפדפן, כך שדף שמרונדר בשרת והגרסה שלו אחרי hydration לא מסכימים ו-React מדווחת על hydration mismatch. Math.random() גם משתנה בכל רינדור. useId גוזר את המזהה מהמיקום של הקומפוננטה בעץ, שזהה בשני המקומות.
אפשר להשתמש ב-useId בשביל keys ברשימה?
לא. key חייב להגיע מהנתונים שלכם כדי ש-React תוכל להתאים את אותו פריט בין רינדורים. קוראים ל-useId פעם אחת לכל קומפוננטה, וקריאה לו בתוך map שוברת בכל מקרה את כללי ההוקים. השתמשו ב-id של הפריט עצמו.
איך מקבלים כמה מזהים מקריאה אחת ל-useId?
קוראים ל-useId פעם אחת ומוסיפים סיומות: ${id}-name, ${id}-email. הבסיס ייחודי, ולכן גם המחרוזות עם הסיומות.
אפשר להשתמש במזהה מ-useId בסלקטור של CSS?
עדיף שלא. המזהה נועד לחיבור אלמנטים ב-DOM, והפורמט המדויק שלו הוא פרט פנימי שהשתנה בין גרסאות של React. עצבו עם class, ומצאו אלמנטים עם ref במקום querySelector.