useId הוא React Hook ליצירת מזהים ייחודיים שאפשר להעביר למאפייני נגישות.

const id = useId()

Reference

useId()

קראו ל-useId ברמה העליונה של הקומפוננטה כדי ליצור מזהה ייחודי:

import { useId } from 'react';

function PasswordField() {
const passwordHintId = useId();
// ...

ראו דוגמאות נוספות בהמשך.

Parameters

useId לא מקבל פרמטרים.

Returns

useId מחזיר מחרוזת מזהה ייחודית שמשויכת לקריאה הספציפית הזו ל-useId בתוך הקומפוננטה הספציפית הזו.

Caveats

  • useId הוא Hook, לכן אפשר לקרוא לו רק ברמה העליונה של הקומפוננטה או של Hooks משלכם. אי אפשר לקרוא לו בתוך לולאות או תנאים. אם צריך את זה, חלצו קומפוננטה חדשה והעבירו אליה את ה-state.

  • לא צריך להשתמש ב-useId כדי לייצר keys ברשימה. Keys צריכים להיווצר מהנתונים שלכם.


שימוש

Pitfall

אל תקראו ל-useId כדי לייצר keys ברשימה. Keys צריכים להיווצר מהנתונים שלכם.

יצירת מזהים ייחודיים למאפייני נגישות

קראו ל-useId ברמה העליונה של הקומפוננטה כדי ליצור מזהה ייחודי:

import { useId } from 'react';

function PasswordField() {
const passwordHintId = useId();
// ...

לאחר מכן אפשר להעביר את המזהה שנוצר למאפיינים שונים:

<>
<input type="password" aria-describedby={passwordHintId} />
<p id={passwordHintId}>
</>

בואו נעבור על דוגמה כדי לראות מתי זה שימושי.

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

ב-HTML רגיל, הייתם כותבים כך:

<label>
Password:
<input
type="password"
aria-describedby="password-hint"
/>
</label>
<p id="password-hint">
The password should contain at least 18 characters
</p>

אבל hardcoding של IDs כך הוא לא פרקטיקה טובה ב-React. קומפוננטה יכולה להירנדר יותר מפעם אחת בעמוד, אבל IDs חייבים להיות ייחודיים. במקום hardcoding של ID, צרו ID ייחודי עם useId:

import { useId } from 'react';

function PasswordField() {
const passwordHintId = useId();
return (
<>
<label>
Password:
<input
type="password"
aria-describedby={passwordHintId}
/>
</label>
<p id={passwordHintId}>
The password should contain at least 18 characters
</p>
</>
);
}

כעת, גם אם PasswordField מופיעה כמה פעמים על המסך, המזהים שנוצרים לא יתנגשו.

import { useId } from 'react';

function PasswordField() {
  const passwordHintId = useId();
  return (
    <>
      <label>
        Password:
        <input
          type="password"
          aria-describedby={passwordHintId}
        />
      </label>
      <p id={passwordHintId}>
        The password should contain at least 18 characters
      </p>
    </>
  );
}

export default function App() {
  return (
    <>
      <h2>Choose password</h2>
      <PasswordField />
      <h2>Confirm password</h2>
      <PasswordField />
    </>
  );
}

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

Pitfall

עם server rendering, useId דורש עץ קומפוננטות זהה בצד השרת ובצד הלקוח. אם העצים שאתם מרנדרים בשרת ובלקוח לא תואמים בדיוק, המזהים שנוצרים לא יתאימו.

Deep Dive

למה useId עדיף על מונה עולה?

אולי אתם שואלים למה useId עדיף על הגדלת משתנה גלובלי כמו nextId++.

היתרון המרכזי של useId הוא ש-React מבטיחה שהיא עובדת עם server rendering. בזמן רינדור שרת, הקומפוננטות מייצרות פלט HTML. אחר כך, בצד הלקוח, hydration מחבר את event handlers ל-HTML שנוצר. כדי ש-hydration יעבוד, פלט הלקוח חייב להתאים ל-HTML של השרת.

קשה מאוד להבטיח זאת עם מונה עולה, כי סדר ה-hydration של Client Components עשוי לא להתאים לסדר שבו ה-HTML נוצר בשרת. בקריאה ל-useId, אתם מבטיחים שה-hydration יעבוד, ושהפלט יתאים בין השרת ללקוח.

בתוך React, useId נוצר מתוך “parent path” של הקומפוננטה הקוראת. לכן, אם העץ בלקוח ובשרת זהה, ה-”parent path” יתאים בלי קשר לסדר הרינדור.


אם צריך לתת IDs לכמה אלמנטים קשורים, אפשר לקרוא ל-useId כדי ליצור קידומת משותפת עבורם:

import { useId } from 'react';

export default function Form() {
  const id = useId();
  return (
    <form>
      <label htmlFor={id + '-firstName'}>First Name:</label>
      <input id={id + '-firstName'} type="text" />
      <hr />
      <label htmlFor={id + '-lastName'}>Last Name:</label>
      <input id={id + '-lastName'} type="text" />
    </form>
  );
}

כך אפשר להימנע מקריאה ל-useId עבור כל אלמנט בודד שצריך ID ייחודי.


הגדרת קידומת משותפת לכל ה-IDs שנוצרים

אם אתם מרנדרים כמה אפליקציות React עצמאיות באותו עמוד, העבירו identifierPrefix כאופציה לקריאות createRoot או hydrateRoot שלכם. כך מובטח שה-IDs שנוצרים בשתי האפליקציות לא יתנגשו, כי כל מזהה שנוצר עם useId יתחיל בקידומת הייחודית שהגדרתם.

import { createRoot } from 'react-dom/client';
import App from './App.js';
import './styles.css';

const root1 = createRoot(document.getElementById('root1'), {
  identifierPrefix: 'my-first-app-'
});
root1.render(<App />);

const root2 = createRoot(document.getElementById('root2'), {
  identifierPrefix: 'my-second-app-'
});
root2.render(<App />);


שימוש באותה קידומת ID בלקוח ובשרת

אם אתם מרנדרים כמה אפליקציות React עצמאיות באותו עמוד, וחלק מהאפליקציות האלה מרונדרות בשרת, ודאו שה-identifierPrefix שאתם מעבירים לקריאה ל-hydrateRoot בצד לקוח זהה ל-identifierPrefix שאתם מעבירים ל-Server APIs, כמו renderToPipeableStream.

// Server
import { renderToPipeableStream } from 'react-dom/server';

const { pipe } = renderToPipeableStream(
<App />,
{ identifierPrefix: 'react-app1' }
);
// Client
import { hydrateRoot } from 'react-dom/client';

const domNode = document.getElementById('root');
const root = hydrateRoot(
domNode,
reactNode,
{ identifierPrefix: 'react-app1' }
);

אין צורך להעביר identifierPrefix אם יש לכם רק אפליקציית React אחת בעמוד.