(썸네일 이미지는 해당 글을 요약 후 ChatGPT 이미지로 생산한 것 입니다.)
이번에 프리랜서로 프로젝트를 진행하면서
Grid + react-hook-form + SelectBox 조합이 자주 사용되어 있는곳을 보았다.
register(`sampleData.SampleList.${rowIdx}.Example`)
화면에서도 잘 동작했고,
폼 값도 문제없이 들어오는 것처럼 보였다.
하지만 프로젝트가 커지고
정렬, 페이지 전환, 행 추가/삭제가 들어가면서
이 방식이 구조적으로 위험하다는 걸 알게 됨.
왜 rowIdx 기반 register가 위험한가?
rowIdx는 화면 기준 인덱스
즉,
- 정렬
- 필터
- 페이지 전환
- 행 삽입 / 삭제
이 중 하나만 발생해도
같은 데이터라도 rowIdx는 언제든 바뀔 수 있다.
하지만 react-hook-form은:
- register된 경로를 기준으로 내부 상태를 유지.
이 말은 곧,
화면에서 보이는 행과
폼 내부에서 관리되는 값이
서로 다른 대상을 가리킬 수 있다는 뜻
실제로 생길 수 있는 문제
예를 들어:
- rowIdx 0 → A 항목
- rowIdx 1 → B 항목
이 상태에서:
- 정렬을 하면
→ B가 위로 올라가 rowIdx 0이 됨
하지만 폼 내부에서는 여전히:
SampleList[0] === A SampleList[1] === B
이렇게 남아 있을 수 있다.
결과적으로:
- 화면에서는 B를 수정했는데
- 실제로는 A의 값이 바뀌는
매우 위험한 상태
이건 UI에서 바로 티가 나지 않아서
더 늦게 발견되는 버그다.
왜 이런 문제가 생기나?
핵심은 이것
- Grid는 화면 인덱스를 기준으로 움직이고
- react-hook-form은 안정적인 key를 원함
즉,
Grid는 “보이는 순서”를 중요하게 보고
Form은 “데이터의 정체성”을 중요하게 본다.
rowIdx는 이 둘을 동시에 만족하지 못한다.
react-hook-form이 원하는 구조
react-hook-form은 원래 이런 구조를 전제로 설계됨
- 고정된 key
- 변하지 않는 id
- FieldArray의 내부 id
그래서 공식적으로도
useFieldArray에서는 field.id를 제공중
fields.map((field, index) => ( <input key={field.id} {...register(`list.${index}.value`)} /> ));
여기서 핵심은:
- index는 form 내부 순서
- field.id는 고유 식별자
Grid와 함께 쓸 때의 현실적인 대안
1️⃣ 화면 rowIdx를 그대로 신뢰하지 않는다
- 정렬/페이지가 있는 Grid라면 특히 위험
2️⃣ row의 고유 id를 기준으로 관리
예:
row.id
row.SampleListId
3️⃣ 화면 index → 실제 form index 매핑
- Grid rowId → form index를 매핑하는 구조
- 또는 form 데이터가 source of truth가 되도록 설계
정리
문제의 핵심
- rowIdx는 UI 기준 값
- react-hook-form은 데이터 기준 값을 원함
결론
Grid + react-hook-form을 함께 쓸 때
rowIdx 기반 register는
처음엔 편하지만, 나중에 반드시 문제를 만듦.