본문으로 건너뛰기
개발 머꼬
개발 노트React
hohyeon.dev17

label과 input을 연결하려고 만든 id 때문에 하이드레이션 경고가 난 이유

  • #Accessibility
  • #Engineering Note
  • #React

문제 발생

접근성을 위해 labelinput을 연결하려고 id를 만들었는데, 서버 렌더링을 켜자 콘솔에 하이드레이션 불일치 경고가 떴습니다.

function Field({ label }) {
  const id = `field-${Math.random().toString(36).slice(2)}`;   // 매번 다르다
  return (
    <>
      <label htmlFor={id}>{label}</label>
      <input id={id} />
    </>
  );
}

전역 카운터로 바꿔 봤지만, 컴포넌트가 렌더되는 순서가 서버와 클라이언트에서 다를 수 있어 여전히 어긋났습니다.

원인 분석

하이드레이션은 서버가 만든 HTML에 클라이언트가 이벤트 핸들러를 붙이는 과정입니다. 이게 성립하려면 클라이언트가 만든 출력이 서버 HTML과 같아야 합니다. 난수나 전역 카운터로 만든 id는 그 조건을 만족하지 못합니다.

useId컴포넌트의 부모 경로(트리 위치) 에서 id를 만듭니다. 위치는 서버와 클라이언트에서 같으므로, 렌더 순서와 무관하게 양쪽이 같은 값을 냅니다. React 공식 문서가 useId의 주된 이점으로 드는 것이 바로 이 서버 렌더링 호환성입니다.

해결 방안

  1. 접근성 속성용 id는 useId로 만듭니다.
function Field({ label }) {
  const id = useId();
  return (
    <>
      <label htmlFor={id}>{label}</label>
      <input id={id} />
    </>
  );
}
  1. 한 컴포넌트에 여러 id가 필요하면 접두사를 붙여 씁니다. useId를 여러 번 호출하지 않아도 됩니다.
const id = useId();
<input id={`${id}-email`} aria-describedby={`${id}-hint`} />
<p id={`${id}-hint`}>회사 메일을 입력해 주세요</p>
  1. 리스트의 key로 쓰지 않습니다. 공식 문서가 명시적으로 금지합니다 — 키는 데이터에서 만들어야 합니다. useId는 데이터가 아니라 트리 위치에서 나오므로, 항목이 재정렬되면 키가 항목을 따라가지 않습니다.
  2. 한 페이지에 React 앱이 둘 이상이면 identifierPrefix를 줍니다. 각 루트가 만드는 id가 겹치지 않게 됩니다.
createRoot(el, { identifierPrefix: "admin-" });
  1. CSS 선택자로 쓰지 않습니다. 생성되는 값에는 콜론 같은 문자가 포함될 수 있어 선택자로 쓰기에 적합하지 않습니다. 스타일은 클래스로 붙입니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.