useId — SSR hydration mismatch를 방지하는 표준 패턴
폼 라벨, aria 연결에 unique id가 필요할 때 SSR-안전한 표준 훅. 한 번 호출해 suffix로 다중 id 만들기, Tabs 같은 다중 컨트롤 패턴까지.
폼 라벨과 입력을 연결할 때 unique한 id가 필요합니다. 컴포넌트가 여러 번 마운트될 수 있으므로 hardcoding은 안 되고, Math.random()을 쓰면 SSR과 클라이언트가 다른 값을 만들어 hydration mismatch가 납니다. useId는 React가 제공하는 SSR-안전한 unique id 생성 훅입니다.
1. 기본 사용
1import { useId } from 'react'; 2 3export function Field({ label, ...inputProps }) { 4 const id = useId(); 5 return ( 6 <div> 7 <label htmlFor={id}>{label}</label> 8 <input id={id} {...inputProps} /> 9 </div> 10 ); 11}
useId는 컴포넌트 트리 위치 기반으로 id를 만들어, SSR과 hydration이 같은 값을 보장합니다.
2. 한 컴포넌트에서 여러 id 만들기
useId를 여러 번 부르기보다, 한 번 부르고 suffix를 붙입니다.
1const id = useId(); 2return ( 3 <> 4 <label htmlFor={`${id}-name`}>이름</label> 5 <input id={`${id}-name`} /> 6 <label htmlFor={`${id}-email`}>이메일</label> 7 <input id={`${id}-email`} /> 8 </> 9);
훅 호출은 한 번, id는 여러 개. React 컨벤션입니다.
3. aria 속성 연결
aria-describedby, aria-labelledby 같은 속성에도 동일하게 활용합니다.
1const id = useId(); 2return ( 3 <> 4 <input 5 aria-describedby={`${id}-help`} 6 aria-invalid={hasError} 7 /> 8 <p id={`${id}-help`}>이메일 형식으로 입력해주세요</p> 9 </> 10);
4. CSS selector로 사용 X
useId는 콜론(:)을 포함합니다 (:r0:). CSS selector로 직접 쓰면 escape가 필요합니다.
1const id = useId(); 2// 잘못된 사용 3document.querySelector(`#${id}`); // SyntaxError 4// 올바른 사용 5document.querySelector(`[id="${id}"]`);
CSS와 함께 쓸 일이 있으면 useId보다 hand-coded prefix가 안전합니다.
5. 라이브러리 컴포넌트 안에서
여러 컴포넌트가 prop으로 받은 값을 공유하지만 unique해야 할 때 — 컴포넌트 root에서 한 번 만들고 자식들에 전달합니다.
1function Tabs({ tabs }) { 2 const baseId = useId(); 3 return ( 4 <div> 5 <div role="tablist"> 6 {tabs.map((t, i) => ( 7 <button 8 id={`${baseId}-tab-${i}`} 9 aria-controls={`${baseId}-panel-${i}`} 10 role="tab" 11 > 12 {t.label} 13 </button> 14 ))} 15 </div> 16 {tabs.map((t, i) => ( 17 <div 18 id={`${baseId}-panel-${i}`} 19 aria-labelledby={`${baseId}-tab-${i}`} 20 role="tabpanel" 21 > 22 {t.content} 23 </div> 24 ))} 25 </div> 26 ); 27}
여러 Tabs 인스턴스가 있어도 id가 충돌하지 않습니다.
6. 주의할 점
- 사용자가 직접 입력할 id는 별도로.
useId는 자동 생성용입니다. URL fragment에 들어갈 id는 의미 있는 이름으로 직접 정합니다. - React 18 이상이 필요합니다. 이전 버전에서는 보통
useState(() => Math.random().toString(36))+ suppressHydrationWarning 같은 hack으로 우회했습니다. - List item 키로 사용 X.
useId는 한 번 호출되어 같은 값을 반환합니다. 목록의 각 항목 key로는 데이터 고유 ID를 씁니다.
7. 정리
| 사용처 | 적합성 |
|---|---|
| label + input 연결 | ✅ |
| aria-describedby / labelledby | ✅ |
| Tabs / Accordion 같은 다중 컨트롤 | ✅ |
| List item key | ❌ — 데이터 ID 사용 |
| URL fragment | ❌ — 의미 있는 이름 |
작은 훅이지만 SSR + 접근성이 함께 필요한 곳에서 거의 표준입니다. hand-coded random id를 쓰던 곳을 모두 useId로 바꿀 만합니다.
참고: React 공식 문서 — useId.