목차6개 항목
react-hook-form은 입력값을 React state 대신 ref와 내부 스토어로 관리해, 입력할 때마다 폼 전체가 리렌더링되지 않게 해 주는 폼 라이브러리다. 기본 <input>은 register로 바로 연결하지만, MUI Select처럼 자체적으로 값을 다루는 제어 컴포넌트는 Controller로 감싸서 연결한다. 제어 컴포넌트의 개념은 폼과 제어 컴포넌트 참고.
Controller로 MUI Select 연결하기
Controller의 render가 넘겨주는 field(value, onChange, onBlur, name, ref)를 컴포넌트에 펼쳐 주면 된다.
const orgTypeOptions = [
{ label: "전체", value: "all" },
{ label: "유치원", value: "kindergarten" },
{ label: "대학교", value: "university" },
{ label: "기타", value: "etc" },
]
<Controller
name="orgType"
control={control}
render={({ field }) => (
<FormControl>
<InputLabel id="org-type-label">{placeholder}</InputLabel>
<Select
{...field}
labelId="org-type-label"
label={placeholder}
renderValue={(value) =>
orgTypeOptions.find((option) => option.value === value)?.label ?? placeholder
}
>
{orgTypeOptions.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
</FormControl>
)}
/>renderValue는 선택된 값 대신 화면에 보여 줄 내용을 정한다. 옵션 배열에서find()로 현재 값과 일치하는 항목을 찾아 label을 보여 준다.key는 값 자체가 고유하다면 그대로 쓴다. 인덱스를 섞을 필요는 없다.
문제: 빈 문자열 값은 "선택 안 됨"으로 취급된다
'전체'를 뜻하는 옵션에 value=""를 주면, MUI Select는 빈 문자열을 값이 없는 상태로 본다. 그래서 '전체'를 골라도 renderValue가 호출되지 않고 아무것도 표시되지 않는다.
해결: '전체'에 "all" 같은 실제 값을 주고, API를 호출할 때만 빈 문자열로 바꾼다.
const orgTypeParam = orgType === "all" ? "" : orgType
const { data } = useQuery({
queryKey: ["org", "list", page, keyword, orgTypeParam],
queryFn: () => getOrgList({ page, keyword, orgType: orgTypeParam }),
})다른 방법:
Select에displayEmpty를 주면 값이 비어 있어도renderValue가 호출된다. 빈 값을 그대로 유지해야 한다면 이쪽을 쓴다.
값이 없을 때 placeholder 보여 주기
<Select
{...field}
displayEmpty
renderValue={(value) => (value ? value : placeholder)}
>
<MenuItem value="option1">옵션1</MenuItem>
<MenuItem value="option2">옵션2</MenuItem>
</Select>displayEmpty 덕분에 값이 없는 초기 상태에서도 placeholder가 그려진다.
등록·수정 폼 같이 쓰기: setValue와 reset
등록과 수정을 같은 폼 컴포넌트로 처리할 때, 수정 모드에서는 상세 데이터를 불러온 뒤 필드를 채운다.
useEffect(() => {
if (!detail) return
// 필드 하나씩
setValue("orgType", detail.orgType)
// 또는 한 번에 (기본값까지 갱신되어 isDirty 판단이 정확해진다)
reset({ name: detail.name, orgType: detail.orgType })
}, [detail, setValue, reset])setValue는 값을 바꿔도 기본적으로 리렌더링을 일으키지 않는다. 다음 경우에만 폼 상태 구독자가 다시 렌더링된다.
- 값을 바꾸면서 에러가 생기거나 해소될 때(검증이 켜져 있을 때)
- 처음으로 폼이 dirty 상태(
isDirty: true)가 될 때
setValue(name, value, { shouldValidate: true, shouldDirty: true })처럼 옵션으로 검증·dirty 처리를 함께 요청할 수 있다.
핵심 정리
- 외부 UI 컴포넌트는
Controller+field펼치기로 연결한다. - MUI
Select는 빈 문자열을 "값 없음"으로 본다. '전체'에는"all"같은 값을 주고 요청 직전에 변환하거나,displayEmpty를 쓴다. - 수정 폼 초기값은
reset(여러 필드·기본값 갱신) 또는setValue(한 필드)로 채운다.
참고 자료
react-hook-form을 왜 고르는지(입력마다 리렌더링하지 않는 비제어 방식, 적은 코드량)는 실제 도입기를 읽어 보면 감이 온다.
- 인프랩, 「react-hook-form을 선택한 이유와 적용 과정」(2022)
- 딜리셔스, 「깔끔한 폼 개발과 정시퇴근을 위하여 react-hook-form」(2022)