DeployU
Interviews / Frontend Engineering / This component has accessibility ID issues. Fix it with `useId`.

Questions

This component has accessibility ID issues. Fix it with `useId`.

practical Hooks & Patterns Interactive Quiz Code Examples

A developer is building a custom Checkbox component. For accessibility, they need to associate the checkbox’s label with the input using the htmlFor attribute on the label and the id attribute on the input.

They’ve tried to generate a unique ID using Math.random() to ensure each instance of the checkbox has a distinct ID. However, in a server-rendered application (or even with multiple instances of the component on the client), this approach leads to:

  • Hydration mismatches: Math.random() generates different numbers on the server and client, causing React to complain.
  • Duplicate IDs: If Math.random() generates the same number twice, or if multiple instances of the component are rendered, you can end up with non-unique IDs, which breaks accessibility.

You’ve been given the CustomCheckbox component.

  1. Explain why using Math.random() (or similar non-deterministic methods) is problematic for generating IDs in server-rendered React applications.
  2. Fix the component using the useId hook to generate stable, unique IDs that are consistent across server and client, ensuring proper accessibility.
Your Code
import React, { useState } from 'react';

// Custom Checkbox component
function CustomCheckbox({ label, checked, onChange }) {
  // THE BUG: Using Math.random() for ID generation.
  // This can cause hydration mismatches in SSR and duplicate IDs.
  const uniqueId = `checkbox-${Math.random().toString(36).substr(2, 9)}`;

  return (
    <div>
      <input
        type="checkbox"
        id={uniqueId}
        checked={checked}
        onChange={onChange}
      />
      <label htmlFor={uniqueId}>{label}</label>
    </div>
  );
}

export default function App() {
  const [isChecked1, setIsChecked1] = useState(false);
  const [isChecked2, setIsChecked2] = useState(true);

  return (
    <div style={{ padding: '20px' }}>
      <h1>Accessibility ID Issues</h1>
      <CustomCheckbox
        label="Enable Feature A"
        checked={isChecked1}
        onChange={() => setIsChecked1(!isChecked1)}
      />
      <CustomCheckbox
        label="Enable Feature B"
        checked={isChecked2}
        onChange={() => setIsChecked2(!isChecked2)}
      />
      <p>Check the console for potential hydration warnings or inspect elements for duplicate IDs.</p>
    </div>
  );
}
Click "Run Code" to see output...
Click "Run Code" to see test results...
How Different Experience Levels Approach This
Junior Engineer
Surface Level

I would use a counter or generate IDs based on the label text to make them unique.

Senior Engineer
Production Ready

I would use the `useId` hook introduced in React 18. It generates unique IDs that are stable across server and client renders, preventing hydration mismatches. The IDs are guaranteed to be unique within the application, making it perfect for accessibility attributes like `id` and `htmlFor`.

The issue with using Math.random() (or Date.now()) for generating IDs in React, especially in server-rendered (SSR) applications, is that these functions are non-deterministic.

  • Hydration Mismatch: In an SSR environment, the server renders the HTML, including the generated IDs. When the client-side React code then “hydrates” this HTML, it re-renders the components. If Math.random() is used, it will likely generate a different random number on the client than it did on the server. React sees this discrepancy in the generated IDs, leading to a hydration mismatch warning (e.g., “Text content did not match server-rendered HTML”).
  • Duplicate IDs: While Math.random() is unlikely to produce duplicates in a single render, it’s not guaranteed. More importantly, if you have multiple instances of the same component, or if the component re-renders, you might accidentally generate the same ID, which breaks the fundamental rule of HTML IDs (they must be unique within the document).

The useId hook, introduced in React 18, is specifically designed to solve this problem. It generates a unique ID that is:

  • Stable: The same ID is generated on both the server and the client for a given component instance, preventing hydration mismatches.
  • Unique: It guarantees uniqueness across the entire application, even for multiple instances of the same component.

Here is the corrected implementation:

import React, { useState, useId } from 'react';

function CustomCheckbox({ label, checked, onChange }) {
  // FIX: Use useId to generate a stable and unique ID.
  const uniqueId = useId();

  return (
    <div>
      <input
        type="checkbox"
        id={uniqueId}
        checked={checked}
        onChange={onChange}
      />
      <label htmlFor={uniqueId}>{label}</label>
    </div>
  );
}

export default function App() {
  const [isChecked1, setIsChecked1] = useState(false);
  const [isChecked2, setIsChecked2] = useState(true);

  return (
    <div style={{ padding: '20px' }}>
      <h1>Accessibility ID Issues</h1>
      <CustomCheckbox
        label="Enable Feature A"
        checked={isChecked1}
        onChange={() => setIsChecked1(!isChecked1)}
      />
      <CustomCheckbox
        label="Enable Feature B"
        checked={isChecked2}
        onChange={() => setIsChecked2(!isChecked2)}
      />
      <p>Check the console for potential hydration warnings or inspect elements for duplicate IDs.</p>
    </div>
  );
}

By replacing Math.random() with useId(), we ensure that each checkbox instance has a unique and stable ID, resolving both hydration mismatches in SSR and potential duplicate ID issues, thereby improving accessibility.

What Makes the Difference?
  • Context over facts: Explains when and why, not just what
  • Real examples: Provides specific use cases from production experience
  • Trade-offs: Acknowledges pros, cons, and decision factors

Practice Question

What is the primary purpose of the `useId` hook in React?